Add optional delete from disk in collection and playlist

Fixes #284
This commit is contained in:
Jonas Kvinge 2020-08-19 22:02:35 +02:00
parent 9b14df6b27
commit 653a35496d
19 changed files with 411 additions and 87 deletions

View File

@ -169,6 +169,7 @@ set(SOURCES
dialogs/trackselectiondialog.cpp
dialogs/addstreamdialog.cpp
dialogs/userpassdialog.cpp
dialogs/deleteconfirmationdialog.cpp
widgets/autoexpandingtreeview.cpp
widgets/busyindicator.cpp
@ -367,6 +368,7 @@ set(HEADERS
dialogs/trackselectiondialog.h
dialogs/addstreamdialog.h
dialogs/userpassdialog.h
dialogs/deleteconfirmationdialog.h
widgets/autoexpandingtreeview.h
widgets/busyindicator.h

View File

@ -48,6 +48,7 @@
#include "core/iconloader.h"
#include "core/mimedata.h"
#include "core/utilities.h"
#include "core/deletefiles.h"
#include "collection.h"
#include "collectionbackend.h"
#include "collectiondirectorymodel.h"
@ -61,7 +62,9 @@
# include "device/devicestatefiltermodel.h"
#endif
#include "dialogs/edittagdialog.h"
#include "dialogs/deleteconfirmationdialog.h"
#include "organize/organizedialog.h"
#include "organize/organizeerrordialog.h"
#include "settings/collectionsettingspage.h"
CollectionView::CollectionView(QWidget *parent)
@ -73,7 +76,23 @@ CollectionView::CollectionView(QWidget *parent)
total_album_count_(-1),
nomusic_(":/pictures/nomusic.png"),
context_menu_(nullptr),
is_in_keyboard_search_(false)
action_load_(nullptr),
action_add_to_playlist_(nullptr),
action_add_to_playlist_enqueue_(nullptr),
action_add_to_playlist_enqueue_next_(nullptr),
action_open_in_new_playlist_(nullptr),
action_organize_(nullptr),
#ifndef Q_OS_WIN
action_copy_to_device_(nullptr),
#endif
action_edit_track_(nullptr),
action_edit_tracks_(nullptr),
action_rescan_songs_(nullptr),
action_show_in_browser_(nullptr),
action_show_in_various_(nullptr),
action_no_show_in_various_(nullptr),
is_in_keyboard_search_(false),
delete_files_(false)
{
setItemDelegate(new CollectionItemDelegate(this));
@ -211,6 +230,8 @@ void CollectionView::ReloadSettings() {
app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool());
}
delete_files_ = settings.value("delete_files", false).toBool();
settings.endGroup();
}
@ -316,41 +337,41 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
if (!context_menu_) {
context_menu_ = new QMenu(this);
add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AddToPlaylist()));
load_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, SLOT(Load()));
open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
action_add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AddToPlaylist()));
action_load_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, SLOT(Load()));
action_open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
context_menu_->addSeparator();
add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue to play next"), this, SLOT(AddToPlaylistEnqueueNext()));
action_add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
action_add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue to play next"), this, SLOT(AddToPlaylistEnqueueNext()));
context_menu_->addSeparator();
organize_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(Organize()));
action_organize_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(Organize()));
#ifndef Q_OS_WIN
copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDevice()));
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDevice()));
#endif
//delete_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(Delete()));
action_delete_files_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(Delete()));
context_menu_->addSeparator();
edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks()));
edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, SLOT(EditTracks()));
show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
action_edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks()));
action_edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, SLOT(EditTracks()));
action_show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
context_menu_->addSeparator();
rescan_songs_ = context_menu_->addAction(tr("Rescan song(s)"), this, SLOT(RescanSongs()));
action_rescan_songs_ = context_menu_->addAction(tr("Rescan song(s)"), this, SLOT(RescanSongs()));
context_menu_->addSeparator();
show_in_various_ = context_menu_->addAction( tr("Show in various artists"), this, SLOT(ShowInVarious()));
no_show_in_various_ = context_menu_->addAction( tr("Don't show in various artists"), this, SLOT(NoShowInVarious()));
action_show_in_various_ = context_menu_->addAction( tr("Show in various artists"), this, SLOT(ShowInVarious()));
action_no_show_in_various_ = context_menu_->addAction( tr("Don't show in various artists"), this, SLOT(NoShowInVarious()));
context_menu_->addSeparator();
context_menu_->addMenu(filter_->menu());
#ifndef Q_OS_WIN
copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), copy_to_device_, SLOT(setDisabled(bool)));
action_copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), action_copy_to_device_, SLOT(setDisabled(bool)));
#endif
}
@ -376,34 +397,45 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0;
// in all modes
load_->setEnabled(songs_selected > 0);
add_to_playlist_->setEnabled(songs_selected > 0);
open_in_new_playlist_->setEnabled(songs_selected > 0);
add_to_playlist_enqueue_->setEnabled(songs_selected > 0);
action_load_->setEnabled(songs_selected > 0);
action_add_to_playlist_->setEnabled(songs_selected > 0);
action_open_in_new_playlist_->setEnabled(songs_selected > 0);
action_add_to_playlist_enqueue_->setEnabled(songs_selected > 0);
// if neither edit_track not edit_tracks are available, we show disabled edit_track element
edit_track_->setVisible(regular_editable == 1);
edit_track_->setEnabled(regular_editable == 1);
edit_tracks_->setVisible(regular_editable > 1);
edit_tracks_->setEnabled(regular_editable > 1);
action_edit_track_->setVisible(regular_editable == 1);
action_edit_track_->setEnabled(regular_editable == 1);
action_edit_tracks_->setVisible(regular_editable > 1);
action_edit_tracks_->setEnabled(regular_editable > 1);
rescan_songs_->setVisible(regular_editable > 0);
rescan_songs_->setEnabled(regular_editable > 0);
action_rescan_songs_->setVisible(regular_editable > 0);
action_rescan_songs_->setEnabled(regular_editable > 0);
organize_->setVisible(regular_elements == regular_editable);
action_organize_->setVisible(regular_elements == regular_editable);
#ifndef Q_OS_WIN
copy_to_device_->setVisible(regular_elements == regular_editable);
action_copy_to_device_->setVisible(regular_elements == regular_editable);
#endif
//delete_->setVisible(regular_elements_only);
show_in_various_->setVisible(regular_elements_only);
no_show_in_various_->setVisible(regular_elements_only);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
action_delete_files_->setVisible(regular_elements == regular_editable && delete_files_);
#else
action_delete_files_->setVisible(false);
#endif
action_show_in_various_->setVisible(regular_elements_only);
action_no_show_in_various_->setVisible(regular_elements_only);
// only when all selected items are editable
organize_->setEnabled(regular_elements == regular_editable);
action_organize_->setEnabled(regular_elements == regular_editable);
#ifndef Q_OS_WIN
copy_to_device_->setEnabled(regular_elements == regular_editable);
action_copy_to_device_->setEnabled(regular_elements == regular_editable);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
action_delete_files_->setEnabled(regular_elements == regular_editable && delete_files_);
#else
action_delete_files_->setEnabled(false);
#endif
//delete_->setEnabled(regular_elements == regular_editable);
context_menu_->popup(e->globalPos());
@ -619,3 +651,33 @@ int CollectionView::TotalArtists() {
int CollectionView::TotalAlbums() {
return total_album_count_;
}
void CollectionView::Delete() {
if (!delete_files_) return;
SongList selected_songs = GetSelectedSongs();
QStringList files;
for (const Song &song : selected_songs) {
files << song.url().toString();
}
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
// We can cheat and always take the storage of the first directory, since they'll all be FilesystemMusicStorage in a collection and deleting doesn't check the actual directory.
std::shared_ptr<MusicStorage> storage = app_->collection_model()->directory_model()->index(0, 0).data(MusicStorage::Role_Storage).value<std::shared_ptr<MusicStorage>>();
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList)));
delete_files->Start(selected_songs);
}
void CollectionView::DeleteFilesFinished(const SongList &songs_with_errors) {
if (songs_with_errors.isEmpty()) return;
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
dialog->Show(OrganizeErrorDialog::Type_Delete, songs_with_errors);
// It deletes itself when the user closes it
}

View File

@ -109,6 +109,8 @@ class CollectionView : public AutoExpandingTreeView {
void ShowInBrowser();
void ShowInVarious();
void NoShowInVarious();
void Delete();
void DeleteFilesFinished(const SongList &songs_with_errors);
private:
void RecheckIsEmpty();
@ -128,27 +130,28 @@ class CollectionView : public AutoExpandingTreeView {
QMenu *context_menu_;
QModelIndex context_menu_index_;
QAction *load_;
QAction *add_to_playlist_;
QAction *add_to_playlist_enqueue_;
QAction *add_to_playlist_enqueue_next_;
QAction *open_in_new_playlist_;
QAction *organize_;
QAction *action_load_;
QAction *action_add_to_playlist_;
QAction *action_add_to_playlist_enqueue_;
QAction *action_add_to_playlist_enqueue_next_;
QAction *action_open_in_new_playlist_;
QAction *action_organize_;
#ifndef Q_OS_WIN
QAction *copy_to_device_;
QAction *action_copy_to_device_;
#endif
QAction *delete_;
QAction *edit_track_;
QAction *edit_tracks_;
QAction *rescan_songs_;
QAction *show_in_browser_;
QAction *show_in_various_;
QAction *no_show_in_various_;
QAction *action_edit_track_;
QAction *action_edit_tracks_;
QAction *action_rescan_songs_;
QAction *action_show_in_browser_;
QAction *action_show_in_various_;
QAction *action_no_show_in_various_;
QAction *action_delete_files_;
std::unique_ptr<OrganizeDialog> organize_dialog_;
std::unique_ptr<EditTagDialog> edit_tag_dialog_;
bool is_in_keyboard_search_;
bool delete_files_;
// Save focus
Song last_selected_song_;

View File

@ -34,10 +34,11 @@
const int DeleteFiles::kBatchSize = 50;
DeleteFiles::DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage)
DeleteFiles::DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage, const bool use_trash)
: thread_(nullptr),
task_manager_(task_manager),
storage_(storage),
use_trash_(use_trash),
started_(false),
task_id_(0),
progress_(0) {
@ -112,6 +113,7 @@ void DeleteFiles::ProcessSomeFiles() {
MusicStorage::DeleteJob job;
job.metadata_ = song;
job.use_trash_ = use_trash_;
if (!storage_->DeleteFromStorage(job)) {
songs_with_errors_ << song;

View File

@ -38,7 +38,7 @@ class DeleteFiles : public QObject {
Q_OBJECT
public:
explicit DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage);
explicit DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage, const bool use_trash);
~DeleteFiles() override;
static const int kBatchSize;
@ -59,6 +59,7 @@ signals:
std::shared_ptr<MusicStorage> storage_;
SongList songs_;
bool use_trash_;
bool started_;

View File

@ -108,6 +108,17 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
QString path = job.metadata_.url().toLocalFile();
QFileInfo fileInfo(path);
if (job.use_trash_) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
if (fileInfo.isDir())
return Utilities::MoveToTrashRecursive(path);
else
return QFile::moveToTrash(path);
#else
return false;
#endif
}
if (fileInfo.isDir())
return Utilities::RemoveRecursive(path);
else

View File

@ -91,6 +91,8 @@
#include "database.h"
#include "player.h"
#include "appearance.h"
#include "filesystemmusicstorage.h"
#include "deletefiles.h"
#include "engine/enginetype.h"
#include "engine/enginebase.h"
#include "engine/engine_fwd.h"
@ -100,6 +102,7 @@
#include "dialogs/trackselectiondialog.h"
#include "dialogs/edittagdialog.h"
#include "dialogs/addstreamdialog.h"
#include "dialogs/deleteconfirmationdialog.h"
#include "organize/organizedialog.h"
#include "widgets/fancytabwidget.h"
#include "widgets/playingwidget.h"
@ -261,15 +264,16 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
playlist_play_pause_(nullptr),
playlist_stop_after_(nullptr),
playlist_undoredo_(nullptr),
playlist_organize_(nullptr),
playlist_copy_url_(nullptr),
playlist_show_in_collection_(nullptr),
playlist_copy_to_collection_(nullptr),
playlist_move_to_collection_(nullptr),
playlist_open_in_browser_(nullptr),
playlist_organize_(nullptr),
#ifndef Q_OS_WIN
playlist_copy_to_device_(nullptr),
#endif
playlist_open_in_browser_(nullptr),
playlist_copy_url_(nullptr),
playlist_delete_(nullptr),
playlist_queue_(nullptr),
playlist_queue_play_next_(nullptr),
playlist_skip_(nullptr),
@ -286,7 +290,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour_Append),
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
exit_count_(0)
exit_count_(0),
delete_files_(false)
{
qLog(Debug) << "Starting";
@ -659,16 +664,16 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
playlist_menu_->addAction(ui_->action_add_files_to_transcoder);
#endif
playlist_menu_->addSeparator();
playlist_copy_url_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy URL(s)..."), this, SLOT(PlaylistCopyUrl()));
playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in collection..."), this, SLOT(ShowInCollection()));
playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser()));
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(PlaylistMoveToCollection()));
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(PlaylistCopyToCollection()));
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, SLOT(PlaylistMoveToCollection()));
#if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN)
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(PlaylistCopyToDevice()));
#endif
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(PlaylistCopyToCollection()));
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, SLOT(PlaylistMoveToCollection()));
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(PlaylistMoveToCollection()));
playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser()));
playlist_open_in_browser_->setVisible(false);
playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in collection..."), this, SLOT(ShowInCollection()));
playlist_copy_url_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy URL(s)..."), this, SLOT(PlaylistCopyUrl()));
playlist_delete_ = playlist_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(PlaylistDelete()));
playlist_menu_->addSeparator();
playlistitem_actions_separator_ = playlist_menu_->addSeparator();
playlist_menu_->addAction(ui_->action_clear_playlist);
@ -990,6 +995,10 @@ void MainWindow::ReloadSettings() {
}
}
s.beginGroup(PlaylistSettingsPage::kSettingsGroup);
delete_files_ = s.value("delete_files", false).toBool();
s.endGroup();
osd_->ReloadSettings();
album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings_.value("search_for_cover_auto", true).toBool());
@ -1733,6 +1742,7 @@ void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex
playlist_copy_to_device_->setVisible(false);
#endif
playlist_organize_->setVisible(false);
playlist_delete_->setVisible(false);
playlist_copy_url_->setVisible(selected > 0);
@ -1805,6 +1815,10 @@ void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex
playlist_copy_to_device_->setVisible(editable > 0);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
playlist_delete_->setVisible(delete_files_ && editable > 0);
#endif
// Remove old item actions, if any.
for (QAction *action : playlistitem_actions_) {
playlist_menu_->removeAction(action);
@ -2850,3 +2864,39 @@ void MainWindow::Love() {
if (tray_icon_) tray_icon_->LoveStateChanged(false);
}
void MainWindow::PlaylistDelete() {
if (!delete_files_) return;
SongList selected_songs;
QStringList files;
bool is_current_item = false;
for (const QModelIndex &proxy_idx : ui_->playlist->view()->selectionModel()->selectedRows()) {
QModelIndex source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_idx);
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
if (!item || !item->Metadata().url().isLocalFile()) continue;
selected_songs << item->Metadata();
files << item->Metadata().url().toLocalFile();
if (item == app_->player()->GetCurrentItem()) is_current_item = true;
}
if (selected_songs.isEmpty()) return;
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
if (app_->player()->GetState() == Engine::Playing && app_->playlist_manager()->current()->rowCount() == selected_songs.count()) {
app_->player()->Stop();
}
ui_->playlist->view()->RemoveSelected();
if (app_->player()->GetState() == Engine::Playing && is_current_item) {
app_->player()->Next();
}
std::shared_ptr<MusicStorage> storage(new FilesystemMusicStorage("/"));
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList)));
delete_files->Start(selected_songs);
}

View File

@ -264,6 +264,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void ExitFinished();
void PlaylistDelete();
private:
void SaveSettings();
@ -331,15 +333,16 @@ class MainWindow : public QMainWindow, public PlatformInterface {
QAction *playlist_play_pause_;
QAction *playlist_stop_after_;
QAction *playlist_undoredo_;
QAction *playlist_organize_;
QAction *playlist_copy_url_;
QAction *playlist_show_in_collection_;
QAction *playlist_copy_to_collection_;
QAction *playlist_move_to_collection_;
QAction *playlist_open_in_browser_;
QAction *playlist_organize_;
#ifndef Q_OS_WIN
QAction *playlist_copy_to_device_;
#endif
QAction *playlist_open_in_browser_;
QAction *playlist_copy_url_;
QAction *playlist_delete_;
QAction *playlist_queue_;
QAction* playlist_queue_play_next_;
QAction *playlist_skip_;
@ -369,6 +372,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
Song song_playing_;
QImage image_original_;
int exit_count_;
bool delete_files_;
};

View File

@ -56,6 +56,7 @@ class MusicStorage {
typedef std::function<void(float progress)> ProgressFunction;
struct CopyJob {
CopyJob() : overwrite_(false), mark_as_listened_(false), remove_original_(false), albumcover_(false) {}
QString source_;
QString destination_;
Song metadata_;
@ -70,7 +71,9 @@ class MusicStorage {
};
struct DeleteJob {
DeleteJob() : use_trash_(false) {}
Song metadata_;
bool use_trash_;
};
virtual QString LocalPath() const { return QString(); }

View File

@ -253,6 +253,30 @@ QString MakeTempDir(const QString template_name) {
}
bool MoveToTrashRecursive(const QString &path) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
QDir dir(path);
for (const QString &child : dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Hidden)) {
if (!MoveToTrashRecursive(path + "/" + child))
return false;
}
for (const QString &child : dir.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden)) {
if (!QFile::moveToTrash(path + "/" + child))
return false;
}
return dir.rmdir(path);
#else
return false;
#endif
}
bool RemoveRecursive(const QString &path) {
QDir dir(path);

View File

@ -64,6 +64,7 @@ quint64 FileSystemFreeSpace(const QString &path);
QString MakeTempDir(const QString template_name = QString());
bool MoveToTrashRecursive(const QString &path);
bool RemoveRecursive(const QString &path);
bool CopyRecursive(const QString &source, const QString &destination);
bool Copy(QIODevice *source, QIODevice *destination);

View File

@ -421,7 +421,7 @@ void DeviceView::Delete() {
std::shared_ptr<MusicStorage> storage = device_index.data(MusicStorage::Role_Storage).value<std::shared_ptr<MusicStorage>>();
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage);
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, false);
connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList)));
delete_files->Start(GetSelectedSongs());

View File

@ -0,0 +1,114 @@
/*
* Strawberry Music Player
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <algorithm>
#include <QGuiApplication>
#include <QScreen>
#include <QStringList>
#include <QStyle>
#include <QFont>
#include <QDialogButtonBox>
#include <QAbstractButton>
#include <QPushButton>
#include <QGridLayout>
#include <QScrollArea>
#include <QAbstractItemView>
#include <QListWidget>
#include <QLabel>
#include "deleteconfirmationdialog.h"
DeleteConfirmationDialog::DeleteConfirmationDialog(const QStringList &files, QWidget *parent) : QDialog(parent, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint), button_box_(new QDialogButtonBox(this)) {
setModal(true);
setWindowTitle(tr("Delete files"));
setWindowIcon(style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, this));
QLabel *label_icon = new QLabel(this);
label_icon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, this).pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this), style()->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this)));
label_icon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QLabel *label_text_top = new QLabel(this);
QFont label_text_top_font = label_text_top->font();
label_text_top_font.setBold(true);
label_text_top_font.setPointSize(label_text_top_font.pointSize() + 4);
label_text_top->setTextInteractionFlags(Qt::TextInteractionFlags(style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, this)));
label_text_top->setContentsMargins(0, 0, 0, 0);
label_text_top->setFont(label_text_top_font);
label_text_top->setText(tr("The following files will be deleted from disk:"));
QListWidget *list = new QListWidget(this);
list->setSelectionMode(QAbstractItemView::NoSelection);
list->addItems(files);
QLabel *label_text_bottom = new QLabel(this);
QFont label_text_bottom_font = label_text_bottom->font();
label_text_bottom_font.setBold(true);
label_text_bottom_font.setPointSize(label_text_bottom_font.pointSize() + 4);
label_text_bottom->setTextInteractionFlags(Qt::TextInteractionFlags(style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, this)));
label_text_bottom->setContentsMargins(0, 0, 0, 0);
label_text_bottom->setFont(label_text_bottom_font);
label_text_bottom->setText(tr("Are you sure you want to continue?"));
button_box_->setStandardButtons(QDialogButtonBox::Yes|QDialogButtonBox::Cancel);
connect(button_box_, SIGNAL(clicked(QAbstractButton*)), this, SLOT(ButtonClicked(QAbstractButton*)));
// Add layout
QGridLayout *grid = new QGridLayout(this);
grid->addWidget(label_icon, 0, 0, 2, 1, Qt::AlignTop);
grid->addWidget(label_text_top, 0, 1, 1, 1);
grid->addWidget(list, 1, 1, 1, 2);
grid->addWidget(label_text_bottom, 2, 1, 1, 2);
grid->addWidget(button_box_, 3, 1, 1, 2, Qt::AlignRight);
grid->setSizeConstraint(QLayout::SetNoConstraint);
setLayout(grid);
// Set size of dialog
int max_width = 0;
int max_height = 0;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QScreen *screen = QWidget::screen();
#else
QScreen *screen = (window() && window()->windowHandle() ? window()->windowHandle()->screen() : QGuiApplication::primaryScreen());
#endif
if (screen) {
max_width = screen->geometry().size().width() / 0.5;
max_height = static_cast<int>(float(screen->geometry().size().height()) / float(1.5));
}
int min_width = std::min(list->sizeHintForColumn(0) + 100, max_width);
int min_height = std::min((list->sizeHintForRow(0) * list->count()) + 160, max_height);
setMinimumSize(min_width, min_height);
adjustSize();
setMinimumSize(0, 0);
}
void DeleteConfirmationDialog::ButtonClicked(QAbstractButton *button) {
done(button_box_->standardButton(button));
}
QDialogButtonBox::StandardButton DeleteConfirmationDialog::warning(const QStringList &files, QWidget *parent) {
DeleteConfirmationDialog box(files, parent);
return static_cast<QDialogButtonBox::StandardButton>(box.exec());
}

View File

@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DELETECONFIRMATIONDIALOG_H
#define DELETECONFIRMATIONDIALOG_H
#include <QDialog>
#include <QDialogButtonBox>
#include <QStringList>
class DeleteConfirmationDialog : public QDialog {
Q_OBJECT
public:
DeleteConfirmationDialog(const QStringList &files, QWidget *parent = nullptr);
static QDialogButtonBox::StandardButton warning(const QStringList &files, QWidget *parent = nullptr);
private slots:
void ButtonClicked(QAbstractButton *button);
private:
QDialogButtonBox *button_box_;
};
#endif // DELETECONFIRMATIONDIALOG_H

View File

@ -178,6 +178,8 @@ void CollectionSettingsPage::Load() {
ui_->spinbox_disk_cache_size->setValue(s.value(kSettingsDiskCacheSize, kSettingsDiskCacheSizeDefault).toInt());
ui_->combobox_disk_cache_size->setCurrentIndex(s.value(kSettingsDiskCacheSizeUnit, static_cast<int>(CacheSizeUnit_MB)).toInt());
ui_->checkbox_delete_files->setChecked(s.value("delete_files", false).toBool());
s.endGroup();
DiskCacheEnable(ui_->checkbox_disk_cache->checkState());
@ -227,6 +229,8 @@ void CollectionSettingsPage::Save() {
s.setValue(kSettingsDiskCacheSize, ui_->spinbox_disk_cache_size->value());
s.setValue(kSettingsDiskCacheSizeUnit, ui_->combobox_disk_cache_size->currentIndex());
s.setValue("delete_files", ui_->checkbox_delete_files->isChecked());
s.endGroup();
}

View File

@ -450,6 +450,13 @@ If there are no matches then it will use the largest image in the directory.</st
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_delete_files">
<property name="text">
<string>Enable delete files in the right click context menu</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>

View File

@ -83,6 +83,8 @@ void PlaylistSettingsPage::Load() {
ui_->checkbox_editmetadatainline->setChecked(s.value("editmetadatainline", false).toBool());
ui_->checkbox_writemetadata->setChecked(s.value(Playlist::kWriteMetadata, false).toBool());
ui_->checkbox_delete_files->setChecked(s.value("delete_files", false).toBool());
s.endGroup();
Init(ui_->layout_playlistsettingspage->parentWidget());
@ -118,6 +120,7 @@ void PlaylistSettingsPage::Save() {
s.setValue(Playlist::kPathType, static_cast<int>(path));
s.setValue("editmetadatainline", ui_->checkbox_editmetadatainline->isChecked());
s.setValue(Playlist::kWriteMetadata, ui_->checkbox_writemetadata->isChecked());
s.setValue("delete_files", ui_->checkbox_delete_files->isChecked());
s.endGroup();
}

View File

@ -63,6 +63,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_delete_files">
<property name="text">
<string>Enable delete files in the right click context menu</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupbox_paths">
<property name="title">

View File

@ -39,6 +39,7 @@
#include "core/filesystemmusicstorage.h"
#include "core/iconloader.h"
#include "core/mimedata.h"
#include "dialogs/deleteconfirmationdialog.h"
#include "fileview.h"
#include "fileviewlist.h"
#include "ui_fileview.h"
@ -85,9 +86,7 @@ FileView::FileView(QWidget *parent)
connect(ui_->list, SIGNAL(CopyToCollection(QList<QUrl>)), SIGNAL(CopyToCollection(QList<QUrl>)));
connect(ui_->list, SIGNAL(MoveToCollection(QList<QUrl>)), SIGNAL(MoveToCollection(QList<QUrl>)));
connect(ui_->list, SIGNAL(CopyToDevice(QList<QUrl>)), SIGNAL(CopyToDevice(QList<QUrl>)));
#ifdef HAVE_GSTREAMER
connect(ui_->list, SIGNAL(Delete(QStringList)), SLOT(Delete(QStringList)));
#endif
connect(ui_->list, SIGNAL(EditTags(QList<QUrl>)), SIGNAL(EditTags(QList<QUrl>)));
QString filter(FileView::kFileFilter);
@ -231,40 +230,24 @@ void FileView::UndoCommand::undo() {
void FileView::Delete(const QStringList &filenames) {
#ifdef HAVE_GSTREAMER
if (filenames.isEmpty()) return;
if (filenames.isEmpty())
return;
if (DeleteConfirmationDialog::warning(filenames) != QDialogButtonBox::Yes) return;
if (QMessageBox::warning(this, tr("Delete files"),
tr("These files will be deleted from disk, are you sure you want to continue?"),
QMessageBox::Yes, QMessageBox::Cancel) != QMessageBox::Yes)
return;
DeleteFiles *delete_files = new DeleteFiles(task_manager_, storage_);
DeleteFiles *delete_files = new DeleteFiles(task_manager_, storage_, true);
connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList)));
delete_files->Start(filenames);
#else
Q_UNUSED(filenames)
#endif
}
void FileView::DeleteFinished(const SongList &songs_with_errors) {
#ifdef HAVE_GSTREAMER
if (songs_with_errors.isEmpty()) return;
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
dialog->Show(OrganizeErrorDialog::Type_Delete, songs_with_errors);
// It deletes itself when the user closes it
#else
Q_UNUSED(songs_with_errors)
#endif
}
void FileView::showEvent(QShowEvent *e) {