Add a MusicStorage interface that can be used to abstract away the details of copying a file to a device.

This commit is contained in:
David Sansome 2010-07-19 19:56:29 +00:00
parent d108841e36
commit 62616304d8
22 changed files with 267 additions and 39 deletions

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "filesystemmusicstorage.h"
#include <QDir>
#include <QFile>
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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

21
src/core/musicstorage.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "musicstorage.h"
MusicStorage::MusicStorage()
{
}

40
src/core/musicstorage.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef MUSICSTORAGE_H
#define MUSICSTORAGE_H
#include "song.h"
#include <QMetaType>
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

View File

@ -14,6 +14,7 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#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()));

View File

@ -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_;

View File

@ -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);

View File

@ -27,11 +27,25 @@
#include <QIcon>
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QUrl>
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>* 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

View File

@ -23,6 +23,7 @@
#include <QAbstractListModel>
#include <QIcon>
#include <QSortFilterProxyModel>
#include <boost/shared_ptr.hpp>
@ -47,6 +48,8 @@ public:
Role_FreeSpace,
Role_IconName,
Role_UpdatingPercentage,
LastRole,
};
enum State {
@ -61,6 +64,8 @@ public:
BackgroundThread<Database>* 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<DeviceLister*> listers_;
@ -161,6 +168,20 @@ private:
QMap<int, QPersistentModelIndex> 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 <typename T>
void DeviceManager::AddDeviceClass() {
QStringList schemes = T::url_schemes();

View File

@ -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) {

View File

@ -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<LibraryWatcher, LibraryWatcher>(this))
{
// Create the library watcher

View File

@ -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<LibraryWatcher>* watcher_;
};

View File

@ -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;
}

View File

@ -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_;

View File

@ -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 ; i<rowCount() ; ++i) {
if (item(i, 0)->data(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);
}

View File

@ -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<MusicStorage*> storage_;
};
#endif // LIBRARYDIRECTORYMODEL_H

View File

@ -41,7 +41,7 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
LibraryModel(LibraryBackend* backend, QObject* parent = 0);
~LibraryModel();
enum {
enum Role {
Role_Type = Qt::UserRole + 1,
Role_ContainerType,
Role_SortText,

View File

@ -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) {

View File

@ -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();

View File

@ -16,6 +16,7 @@
#include "organisedialog.h"
#include "ui_organisedialog.h"
#include "core/musicstorage.h"
#include "core/organise.h"
#include <QDir>
@ -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<MusicStorage*>();
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<MusicStorage*>();
// 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();

View File

@ -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<QUrl>& urls);
void SetFilenames(const QStringList& filenames);