From 62616304d82f77da388c6d98f38e64c714e65dd2 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Mon, 19 Jul 2010 19:56:29 +0000 Subject: [PATCH] Add a MusicStorage interface that can be used to abstract away the details of copying a file to a device. --- src/CMakeLists.txt | 2 ++ src/core/filesystemmusicstorage.cpp | 49 +++++++++++++++++++++++++++ src/core/filesystemmusicstorage.h | 35 +++++++++++++++++++ src/core/musicstorage.cpp | 21 ++++++++++++ src/core/musicstorage.h | 40 ++++++++++++++++++++++ src/core/organise.cpp | 24 +++---------- src/core/organise.h | 5 +-- src/devices/connecteddevice.h | 3 ++ src/devices/devicemanager.cpp | 18 ++++++++++ src/devices/devicemanager.h | 21 ++++++++++++ src/devices/deviceview.cpp | 2 +- src/devices/filesystemdevice.cpp | 1 + src/devices/filesystemdevice.h | 5 ++- src/devices/gpoddevice.cpp | 7 ++++ src/devices/gpoddevice.h | 8 ++++- src/library/librarydirectorymodel.cpp | 16 +++++++++ src/library/librarydirectorymodel.h | 5 +++ src/library/librarymodel.h | 2 +- src/library/libraryview.cpp | 2 +- src/ui/mainwindow.cpp | 2 +- src/ui/organisedialog.cpp | 36 ++++++++++++++------ src/ui/organisedialog.h | 2 +- 22 files changed, 267 insertions(+), 39 deletions(-) create mode 100644 src/core/filesystemmusicstorage.cpp create mode 100644 src/core/filesystemmusicstorage.h create mode 100644 src/core/musicstorage.cpp create mode 100644 src/core/musicstorage.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a95527cbe..b6bd43001 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,11 +35,13 @@ set(SOURCES core/backgroundthread.cpp core/commandlineoptions.cpp core/database.cpp + core/filesystemmusicstorage.cpp core/fht.cpp core/globalshortcutbackend.cpp core/globalshortcuts.cpp core/gnomeglobalshortcutbackend.cpp core/mergedproxymodel.cpp + core/musicstorage.cpp core/networkaccessmanager.cpp core/organise.cpp core/organiseformat.cpp diff --git a/src/core/filesystemmusicstorage.cpp b/src/core/filesystemmusicstorage.cpp new file mode 100644 index 000000000..8965013fb --- /dev/null +++ b/src/core/filesystemmusicstorage.cpp @@ -0,0 +1,49 @@ +/* This file is part of Clementine. + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "filesystemmusicstorage.h" + +#include +#include + +FilesystemMusicStorage::FilesystemMusicStorage(const QString& root) + : root_(root) +{ +} + +bool FilesystemMusicStorage::CopyToStorage( + const QString& source, const QString& destination, + const Song&, bool overwrite, bool remove_original) { + const QString dest_filename = root_ + "/" + destination; + + // Don't do anything if the destination is the same as the source + if (source == dest_filename) + return true; + + // Create directories as required + QDir dir; + dir.mkpath(dest_filename.section('/', 0, -2)); + + // Remove the destination file if it exists and we want to overwrite + if (overwrite && QFile::exists(dest_filename)) + QFile::remove(dest_filename); + + // Copy or move + if (remove_original) + return QFile::rename(source, dest_filename); + else + return QFile::copy(source, dest_filename); +} diff --git a/src/core/filesystemmusicstorage.h b/src/core/filesystemmusicstorage.h new file mode 100644 index 000000000..b9b6764f7 --- /dev/null +++ b/src/core/filesystemmusicstorage.h @@ -0,0 +1,35 @@ +/* This file is part of Clementine. + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef FILESYSTEMMUSICSTORAGE_H +#define FILESYSTEMMUSICSTORAGE_H + +#include "musicstorage.h" + +class FilesystemMusicStorage : public MusicStorage { +public: + FilesystemMusicStorage(const QString& root); + + QString LocalPath() const { return root_; } + + bool CopyToStorage(const QString &source, const QString &destination, + const Song &metadata, bool overwrite, bool remove_original); + +private: + QString root_; +}; + +#endif // FILESYSTEMMUSICSTORAGE_H diff --git a/src/core/musicstorage.cpp b/src/core/musicstorage.cpp new file mode 100644 index 000000000..1e2f3f12c --- /dev/null +++ b/src/core/musicstorage.cpp @@ -0,0 +1,21 @@ +/* This file is part of Clementine. + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "musicstorage.h" + +MusicStorage::MusicStorage() +{ +} diff --git a/src/core/musicstorage.h b/src/core/musicstorage.h new file mode 100644 index 000000000..f40eeb9ca --- /dev/null +++ b/src/core/musicstorage.h @@ -0,0 +1,40 @@ +/* This file is part of Clementine. + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef MUSICSTORAGE_H +#define MUSICSTORAGE_H + +#include "song.h" + +#include + +class MusicStorage { +public: + MusicStorage(); + virtual ~MusicStorage() {} + + static const int kStorageRole = Qt::UserRole + 100; + + virtual QString LocalPath() const { return QString(); } + + virtual bool CopyToStorage(const QString& source, const QString& destination, + const Song& metadata, bool overwrite, + bool remove_original) = 0; +}; + +Q_DECLARE_METATYPE(MusicStorage*); + +#endif // MUSICSTORAGE_H diff --git a/src/core/organise.cpp b/src/core/organise.cpp index d2e179a82..0606fd602 100644 --- a/src/core/organise.cpp +++ b/src/core/organise.cpp @@ -14,6 +14,7 @@ along with Clementine. If not, see . */ +#include "musicstorage.h" #include "organise.h" #include "taskmanager.h" @@ -24,7 +25,7 @@ const int Organise::kBatchSize = 10; -Organise::Organise(TaskManager* task_manager, const QString &destination, +Organise::Organise(TaskManager* task_manager, MusicStorage* destination, const OrganiseFormat &format, bool copy, bool overwrite, const QStringList& files) : thread_(NULL), @@ -95,25 +96,8 @@ void Organise::ProcessSomeFiles() { if (!song.is_valid()) continue; - // Get the destination filename - QString dest_filename = destination_ + "/" + format_.GetFilenameForSong(song); - - // Don't do anything if the destination is the same as the source - if (filename == dest_filename) - continue; - - // Create directories as required - dir.mkpath(dest_filename.section('/', 0, -2)); - - // Remove the destination file if it exists and we want to overwrite - if (overwrite_ && QFile::exists(dest_filename)) - QFile::remove(dest_filename); - - // Copy or move - if (copy_) - QFile::copy(filename, dest_filename); - else - QFile::rename(filename, dest_filename); + destination_->CopyToStorage(filename, format_.GetFilenameForSong(song), + song, overwrite_, !copy_); } QTimer::singleShot(0, this, SLOT(ProcessSomeFiles())); diff --git a/src/core/organise.h b/src/core/organise.h index aa76bc72d..2d9b78245 100644 --- a/src/core/organise.h +++ b/src/core/organise.h @@ -21,13 +21,14 @@ #include "organiseformat.h" +class MusicStorage; class TaskManager; class Organise : public QObject { Q_OBJECT public: - Organise(TaskManager* task_manager, const QString& destination, + Organise(TaskManager* task_manager, MusicStorage* destination, const OrganiseFormat& format, bool copy, bool overwrite, const QStringList& files); @@ -42,8 +43,8 @@ private: QThread* thread_; QThread* original_thread_; TaskManager* task_manager_; + MusicStorage* destination_; - const QString destination_; const OrganiseFormat format_; const bool copy_; const bool overwrite_; diff --git a/src/devices/connecteddevice.h b/src/devices/connecteddevice.h index 329afc5bc..4ccf33ed1 100644 --- a/src/devices/connecteddevice.h +++ b/src/devices/connecteddevice.h @@ -26,6 +26,7 @@ class DeviceLister; class DeviceManager; class LibraryBackend; class LibraryModel; +class MusicStorage; class ConnectedDevice : public QObject { Q_OBJECT @@ -40,6 +41,8 @@ public: QString unique_id() const { return unique_id_; } LibraryModel* model() const { return model_; } + virtual MusicStorage* storage() = 0; + signals: void TaskStarted(int id); void Error(const QString& message); diff --git a/src/devices/devicemanager.cpp b/src/devices/devicemanager.cpp index 4b87a5244..c220860b5 100644 --- a/src/devices/devicemanager.cpp +++ b/src/devices/devicemanager.cpp @@ -27,11 +27,25 @@ #include #include +#include #include const int DeviceManager::kDeviceIconSize = 32; const int DeviceManager::kDeviceIconOverlaySize = 16; +DeviceStateFilterModel::DeviceStateFilterModel(QObject *parent, + DeviceManager::State state) + : QSortFilterProxyModel(parent), + state_(state) +{ +} + +bool DeviceStateFilterModel::filterAcceptsRow(int row, const QModelIndex&) const { + return sourceModel()->index(row, 0).data(DeviceManager::Role_State).toInt() + == state_; +} + + DeviceManager::DeviceInfo::DeviceInfo() : database_id_(-1), task_percentage_(-1) @@ -132,6 +146,10 @@ DeviceManager::DeviceManager(BackgroundThread* database, devices_ << info; } + // This proxy model only shows connected devices + connected_devices_model_ = new DeviceStateFilterModel(this); + connected_devices_model_->setSourceModel(this); + #ifdef Q_WS_X11 AddLister(new DeviceKitLister); #endif diff --git a/src/devices/devicemanager.h b/src/devices/devicemanager.h index 77076ee63..6b53fbef4 100644 --- a/src/devices/devicemanager.h +++ b/src/devices/devicemanager.h @@ -23,6 +23,7 @@ #include #include +#include #include @@ -47,6 +48,8 @@ public: Role_FreeSpace, Role_IconName, Role_UpdatingPercentage, + + LastRole, }; enum State { @@ -61,6 +64,8 @@ public: BackgroundThread* database() const { return database_; } TaskManager* task_manager() const { return task_manager_; } + QAbstractItemModel* connected_devices_model() const { return connected_devices_model_; } + // Get info about devices int GetDatabaseId(int row) const; DeviceLister* GetLister(int row) const; @@ -150,6 +155,8 @@ private: DeviceDatabaseBackend* backend_; TaskManager* task_manager_; + QSortFilterProxyModel* connected_devices_model_; + QIcon not_connected_overlay_; QList listers_; @@ -161,6 +168,20 @@ private: QMap active_tasks_; }; +class DeviceStateFilterModel : public QSortFilterProxyModel { +public: + DeviceStateFilterModel(QObject* parent, DeviceManager::State state = + DeviceManager::State_Connected); + +protected: + bool filterAcceptsRow(int row, const QModelIndex& parent) const; + +private: + DeviceManager::State state_; +}; + + + template void DeviceManager::AddDeviceClass() { QStringList schemes = T::url_schemes(); diff --git a/src/devices/deviceview.cpp b/src/devices/deviceview.cpp index a8c6e8709..18fcc872d 100644 --- a/src/devices/deviceview.cpp +++ b/src/devices/deviceview.cpp @@ -177,7 +177,7 @@ void DeviceView::SetLibrary(LibraryModel* library) { library_ = library; organise_dialog_.reset(new OrganiseDialog(manager_->task_manager())); - organise_dialog_->AddDirectoryModel(library_->directory_model()); + organise_dialog_->SetDestinationModel(library_->directory_model()); } void DeviceView::contextMenuEvent(QContextMenuEvent* e) { diff --git a/src/devices/filesystemdevice.cpp b/src/devices/filesystemdevice.cpp index a55ac0267..cb1051bdf 100644 --- a/src/devices/filesystemdevice.cpp +++ b/src/devices/filesystemdevice.cpp @@ -28,6 +28,7 @@ FilesystemDevice::FilesystemDevice( const QString& unique_id, DeviceManager* manager, int database_id, bool first_time) : ConnectedDevice(url, lister, unique_id, manager, database_id, first_time), + FilesystemMusicStorage(url.toLocalFile()), watcher_(new BackgroundThreadImplementation(this)) { // Create the library watcher diff --git a/src/devices/filesystemdevice.h b/src/devices/filesystemdevice.h index ba2868a1c..09779f708 100644 --- a/src/devices/filesystemdevice.h +++ b/src/devices/filesystemdevice.h @@ -19,11 +19,12 @@ #include "connecteddevice.h" #include "core/backgroundthread.h" +#include "core/filesystemmusicstorage.h" class DeviceManager; class LibraryWatcher; -class FilesystemDevice : public ConnectedDevice { +class FilesystemDevice : public ConnectedDevice, public FilesystemMusicStorage { Q_OBJECT public: @@ -35,6 +36,8 @@ public: static QStringList url_schemes() { return QStringList() << "file"; } + MusicStorage* storage() { return this; } + private: BackgroundThread* watcher_; }; diff --git a/src/devices/gpoddevice.cpp b/src/devices/gpoddevice.cpp index 6ab44e071..619d49c7b 100644 --- a/src/devices/gpoddevice.cpp +++ b/src/devices/gpoddevice.cpp @@ -47,3 +47,10 @@ GPodDevice::~GPodDevice() { } +bool GPodDevice::CopyToStorage( + const QString &source, const QString &destination, + const Song &metadata, bool overwrite, bool remove_original) +{ + return true; +} + diff --git a/src/devices/gpoddevice.h b/src/devices/gpoddevice.h index d4d7a06ff..0df5d0aa3 100644 --- a/src/devices/gpoddevice.h +++ b/src/devices/gpoddevice.h @@ -18,10 +18,11 @@ #define GPODDEVICE_H #include "connecteddevice.h" +#include "core/musicstorage.h" class GPodLoader; -class GPodDevice : public ConnectedDevice { +class GPodDevice : public ConnectedDevice, public MusicStorage { Q_OBJECT public: @@ -33,6 +34,11 @@ public: static QStringList url_schemes() { return QStringList() << "ipod"; } + MusicStorage* storage() { return this; } + + bool CopyToStorage(const QString &source, const QString &destination, + const Song &metadata, bool overwrite, bool remove_original); + private: QThread* loader_thread_; GPodLoader* loader_; diff --git a/src/library/librarydirectorymodel.cpp b/src/library/librarydirectorymodel.cpp index 2033ee044..60cdf5be4 100644 --- a/src/library/librarydirectorymodel.cpp +++ b/src/library/librarydirectorymodel.cpp @@ -16,6 +16,8 @@ #include "librarydirectorymodel.h" #include "librarybackend.h" +#include "core/filesystemmusicstorage.h" +#include "core/musicstorage.h" #include "ui/iconloader.h" LibraryDirectoryModel::LibraryDirectoryModel(LibraryBackend* backend, QObject* parent) @@ -27,10 +29,15 @@ LibraryDirectoryModel::LibraryDirectoryModel(LibraryBackend* backend, QObject* p connect(backend_, SIGNAL(DirectoryDeleted(Directory)), SLOT(DirectoryDeleted(Directory))); } +LibraryDirectoryModel::~LibraryDirectoryModel() { + qDeleteAll(storage_); +} + void LibraryDirectoryModel::DirectoryDiscovered(const Directory &dir) { QStandardItem* item = new QStandardItem(dir.path); item->setData(dir.id, kIdRole); item->setIcon(dir_icon_); + storage_ << new FilesystemMusicStorage(dir.path); appendRow(item); } @@ -38,6 +45,7 @@ void LibraryDirectoryModel::DirectoryDeleted(const Directory &dir) { for (int i=0 ; idata(kIdRole).toInt() == dir.id) { removeRow(i); + delete storage_.takeAt(i); break; } } @@ -60,3 +68,11 @@ void LibraryDirectoryModel::RemoveDirectory(const QModelIndex& index) { backend_->RemoveDirectory(dir); } + +QVariant LibraryDirectoryModel::data(const QModelIndex &index, int role) const { + if (role == MusicStorage::kStorageRole) { + return QVariant::fromValue(storage_[index.row()]); + } + + return QStandardItemModel::data(index, role); +} diff --git a/src/library/librarydirectorymodel.h b/src/library/librarydirectorymodel.h index cd7a6de8e..47669b494 100644 --- a/src/library/librarydirectorymodel.h +++ b/src/library/librarydirectorymodel.h @@ -23,17 +23,21 @@ #include "directory.h" class LibraryBackend; +class MusicStorage; class LibraryDirectoryModel : public QStandardItemModel { Q_OBJECT public: LibraryDirectoryModel(LibraryBackend* backend, QObject* parent = 0); + ~LibraryDirectoryModel(); // To be called by GUIs void AddDirectory(const QString& path); void RemoveDirectory(const QModelIndex& index); + QVariant data(const QModelIndex &index, int role) const; + private slots: // To be called by the backend void DirectoryDiscovered(const Directory& directories); @@ -44,6 +48,7 @@ class LibraryDirectoryModel : public QStandardItemModel { QIcon dir_icon_; LibraryBackend* backend_; + QList storage_; }; #endif // LIBRARYDIRECTORYMODEL_H diff --git a/src/library/librarymodel.h b/src/library/librarymodel.h index d87ad1c7f..76f14a647 100644 --- a/src/library/librarymodel.h +++ b/src/library/librarymodel.h @@ -41,7 +41,7 @@ class LibraryModel : public SimpleTreeModel { LibraryModel(LibraryBackend* backend, QObject* parent = 0); ~LibraryModel(); - enum { + enum Role { Role_Type = Qt::UserRole + 1, Role_ContainerType, Role_SortText, diff --git a/src/library/libraryview.cpp b/src/library/libraryview.cpp index d3966049b..d94042991 100644 --- a/src/library/libraryview.cpp +++ b/src/library/libraryview.cpp @@ -118,7 +118,7 @@ void LibraryView::ReloadSettings() { void LibraryView::SetTaskManager(TaskManager *task_manager) { organise_dialog_.reset(new OrganiseDialog(task_manager)); - organise_dialog_->AddDirectoryModel(library_->directory_model()); + organise_dialog_->SetDestinationModel(library_->directory_model()); } void LibraryView::SetLibrary(LibraryModel *library) { diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 312f15b40..3f696beed 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -203,7 +203,7 @@ MainWindow::MainWindow(NetworkAccessManager* network, Engine::Type engine, QWidg ui_->devices_view->SetDeviceManager(devices_); ui_->devices_view->SetLibrary(library_->model()); - organise_dialog_->AddDirectoryModel(library_->model()->directory_model()); + organise_dialog_->SetDestinationModel(library_->model()->directory_model()); cover_manager_->Init(); diff --git a/src/ui/organisedialog.cpp b/src/ui/organisedialog.cpp index 62ad7a6fb..48261720a 100644 --- a/src/ui/organisedialog.cpp +++ b/src/ui/organisedialog.cpp @@ -16,6 +16,7 @@ #include "organisedialog.h" #include "ui_organisedialog.h" +#include "core/musicstorage.h" #include "core/organise.h" #include @@ -87,9 +88,7 @@ OrganiseDialog::~OrganiseDialog() { delete ui_; } -void OrganiseDialog::AddDirectoryModel(QAbstractItemModel *model) { - // TODO: Add this model to a proxy model that merges different models - // together, eg. from the local library and also removable devices. +void OrganiseDialog::SetDestinationModel(QAbstractItemModel *model) { ui_->destination->setModel(model); } @@ -148,6 +147,15 @@ void OrganiseDialog::InsertTag(const QString &tag) { } void OrganiseDialog::UpdatePreviews() { + const QModelIndex destination = ui_->destination->model()->index( + ui_->destination->currentIndex(), 0); + if (!destination.isValid()) + return; + const MusicStorage* storage = + destination.data(MusicStorage::kStorageRole).value(); + + const bool has_local_destination = !storage->LocalPath().isEmpty(); + // Update the format object format_.set_format(ui_->naming->toPlainText()); format_.set_replace_non_ascii(ui_->replace_ascii->isChecked()); @@ -155,16 +163,19 @@ void OrganiseDialog::UpdatePreviews() { format_.set_replace_the(ui_->replace_the->isChecked()); const bool format_valid = format_.IsValid(); - ui_->buttonBox->button(QDialogButtonBox::Ok)->setEnabled( - format_valid && !ui_->destination->currentText().isEmpty()); + ui_->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(format_valid); if (!format_valid) return; + // Update the previews ui_->preview->clear(); - foreach (const Song& song, preview_songs_) { - QString filename = ui_->destination->currentText() + "/" + - format_.GetFilenameForSong(song); - ui_->preview->addItem(QDir::toNativeSeparators(filename)); + ui_->preview->setVisible(has_local_destination); + if (has_local_destination) { + foreach (const Song& song, preview_songs_) { + QString filename = storage->LocalPath() + "/" + + format_.GetFilenameForSong(song); + ui_->preview->addItem(QDir::toNativeSeparators(filename)); + } } } @@ -202,9 +213,14 @@ void OrganiseDialog::accept() { s.setValue("overwrite", ui_->overwrite->isChecked()); s.setValue("destination", ui_->destination->currentText()); + const QModelIndex destination = ui_->destination->model()->index( + ui_->destination->currentIndex(), 0); + MusicStorage* storage = + destination.data(MusicStorage::kStorageRole).value(); + // It deletes itself when it's finished. Organise* organise = new Organise( - task_manager_, ui_->destination->currentText(), format_, + task_manager_, storage, format_, !ui_->move->isChecked(), ui_->overwrite->isChecked(), filenames_); organise->Start(); diff --git a/src/ui/organisedialog.h b/src/ui/organisedialog.h index 6e55b0f16..bf1757e48 100644 --- a/src/ui/organisedialog.h +++ b/src/ui/organisedialog.h @@ -43,7 +43,7 @@ public: static const char* kDefaultFormat; static const char* kSettingsGroup; - void AddDirectoryModel(QAbstractItemModel* model); + void SetDestinationModel(QAbstractItemModel* model); void SetUrls(const QList& urls); void SetFilenames(const QStringList& filenames);