From 404283be19b88daf16beb1b3640c0bea4dc9c022 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sat, 29 Dec 2018 02:57:22 +0100 Subject: [PATCH] Convert devicemanager to QAbstractItemModel --- src/CMakeLists.txt | 1 + src/device/afcdevice.cpp | 4 +- src/device/afcdevice.h | 2 +- src/device/cddadevice.cpp | 3 +- src/device/cddadevice.h | 2 +- src/device/cddalister.cpp | 6 +- src/device/cddalister.h | 2 +- src/device/connecteddevice.cpp | 5 +- src/device/connecteddevice.h | 5 +- src/device/deviceinfo.cpp | 141 +++++++++++ src/device/deviceinfo.h | 121 +++++++++ src/device/devicekitlister.cpp | 6 +- src/device/devicekitlister.h | 2 +- src/device/devicelister.h | 2 +- src/device/devicemanager.cpp | 431 +++++++++++++------------------- src/device/devicemanager.h | 69 ++--- src/device/deviceproperties.cpp | 7 +- src/device/deviceproperties.h | 1 + src/device/deviceview.cpp | 11 +- src/device/deviceview.h | 2 +- src/device/filesystemdevice.cpp | 5 +- src/device/filesystemdevice.h | 2 +- src/device/giolister.cpp | 33 ++- src/device/giolister.h | 6 +- src/device/gpoddevice.cpp | 4 +- src/device/gpoddevice.h | 11 +- src/device/ilister.cpp | 3 +- src/device/ilister.h | 2 +- src/device/mtpconnection.cpp | 19 +- src/device/mtpdevice.cpp | 11 +- src/device/mtpdevice.h | 2 +- src/device/mtploader.cpp | 17 +- src/device/mtploader.h | 7 +- src/device/udisks2lister.cpp | 6 +- src/device/udisks2lister.h | 2 +- 35 files changed, 574 insertions(+), 379 deletions(-) create mode 100644 src/device/deviceinfo.cpp create mode 100644 src/device/deviceinfo.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4b8133a72..c3b2a88c1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -684,6 +684,7 @@ optional_source(UNIX device/deviceviewcontainer.cpp device/deviceview.cpp device/deviceproperties.cpp + device/deviceinfo.cpp HEADERS device/connecteddevice.h device/devicedatabasebackend.h diff --git a/src/device/afcdevice.cpp b/src/device/afcdevice.cpp index 98ba870d5..07014e3e5 100644 --- a/src/device/afcdevice.cpp +++ b/src/device/afcdevice.cpp @@ -44,7 +44,7 @@ AfcDevice::~AfcDevice() { Utilities::RemoveRecursive(local_path_); } -void AfcDevice::Init() { +bool AfcDevice::Init() { // Make a new temporary directory for the iTunesDB. We copy it off the iPod so that libgpod can have a local directory to use. local_path_ = Utilities::MakeTempDir(); @@ -59,6 +59,8 @@ void AfcDevice::Init() { connect(loader_thread_, SIGNAL(started()), transfer_, SLOT(CopyFromDevice())); loader_thread_->start(); + return true; + } void AfcDevice::CopyFinished(bool success) { diff --git a/src/device/afcdevice.h b/src/device/afcdevice.h index 07323100f..151464a40 100644 --- a/src/device/afcdevice.h +++ b/src/device/afcdevice.h @@ -44,7 +44,7 @@ public: Q_INVOKABLE AfcDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time); ~AfcDevice(); - void Init(); + bool Init(); static QStringList url_schemes() { return QStringList() << "afc"; } diff --git a/src/device/cddadevice.cpp b/src/device/cddadevice.cpp index 289e5e841..9d83e1082 100644 --- a/src/device/cddadevice.cpp +++ b/src/device/cddadevice.cpp @@ -44,10 +44,11 @@ CddaDevice::CddaDevice(const QUrl &url, DeviceLister *lister, const QString &uni CddaDevice::~CddaDevice() {} -void CddaDevice::Init() { +bool CddaDevice::Init() { song_count_ = 0; // Reset song count, in case it was already set cdda_song_loader_.LoadSongs(); + return true; } diff --git a/src/device/cddadevice.h b/src/device/cddadevice.h index 4da2878b8..77502d58e 100644 --- a/src/device/cddadevice.h +++ b/src/device/cddadevice.h @@ -50,7 +50,7 @@ class CddaDevice : public ConnectedDevice { Q_INVOKABLE CddaDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time); ~CddaDevice(); - void Init(); + bool Init(); void Refresh(); bool CopyToStorage(const MusicStorage::CopyJob&) { return false; } bool DeleteFromStorage(const MusicStorage::DeleteJob&) { return false; } diff --git a/src/device/cddalister.cpp b/src/device/cddalister.cpp index 936713644..0ae257578 100644 --- a/src/device/cddalister.cpp +++ b/src/device/cddalister.cpp @@ -102,7 +102,7 @@ void CddaLister::UnmountDevice(const QString &id) { void CddaLister::UpdateDeviceFreeSpace(const QString&) {} -void CddaLister::Init() { +bool CddaLister::Init() { cdio_init(); #ifdef Q_OS_MACOS @@ -113,7 +113,7 @@ void CddaLister::Init() { char** devices = cdio_get_devices(DRIVER_DEVICE); if (!devices) { qLog(Debug) << "No CD devices found"; - return; + return false; } for (; *devices != nullptr; ++devices) { QString device(*devices); @@ -133,4 +133,6 @@ void CddaLister::Init() { } } + return true; + } diff --git a/src/device/cddalister.h b/src/device/cddalister.h index 4f7ddc045..8e6165ced 100644 --- a/src/device/cddalister.h +++ b/src/device/cddalister.h @@ -53,7 +53,7 @@ class CddaLister : public DeviceLister { QList MakeDeviceUrls(const QString&); void UnmountDevice(const QString&); void UpdateDeviceFreeSpace(const QString&); - void Init(); + bool Init(); private: QStringList devices_list_; diff --git a/src/device/connecteddevice.cpp b/src/device/connecteddevice.cpp index 88e0df2e1..e4d408c0e 100644 --- a/src/device/connecteddevice.cpp +++ b/src/device/connecteddevice.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -110,12 +111,12 @@ void ConnectedDevice::FinishDelete(bool) { MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const { int index = manager_->FindDeviceById(unique_id_); - return MusicStorage::TranscodeMode(manager_->index(index).data(DeviceManager::Role_TranscodeMode).toInt()); + return MusicStorage::TranscodeMode(manager_->index(index, 0, QModelIndex()).data(DeviceManager::Role_TranscodeMode).toInt()); } Song::FileType ConnectedDevice::GetTranscodeFormat() const { int index = manager_->FindDeviceById(unique_id_); - return Song::FileType(manager_->index(index).data(DeviceManager::Role_TranscodeFormat).toInt()); + return Song::FileType(manager_->index(index, 0, QModelIndex()).data(DeviceManager::Role_TranscodeFormat).toInt()); } void ConnectedDevice::BackendTotalSongCountUpdated(int count) { diff --git a/src/device/connecteddevice.h b/src/device/connecteddevice.h index 22e10624f..65b5b2535 100644 --- a/src/device/connecteddevice.h +++ b/src/device/connecteddevice.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,7 +49,7 @@ class ConnectedDevice : public QObject, ConnectedDevice(const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, Application *app, int database_id, bool first_time); ~ConnectedDevice(); - virtual void Init() = 0; + virtual bool Init() = 0; // For some devices (e.g. CD devices) we don't have callbacks to be notified when something change: // we can call this method to refresh device's state virtual void Refresh() {} @@ -67,7 +68,7 @@ class ConnectedDevice : public QObject, virtual void Eject(); -signals: + signals: void TaskStarted(int id); void SongCountUpdated(int count); diff --git a/src/device/deviceinfo.cpp b/src/device/deviceinfo.cpp new file mode 100644 index 000000000..f19181629 --- /dev/null +++ b/src/device/deviceinfo.cpp @@ -0,0 +1,141 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "core/iconloader.h" +#include "core/logging.h" +#include "core/simpletreemodel.h" + +#include "deviceinfo.h" +#include "devicedatabasebackend.h" + +DeviceDatabaseBackend::Device DeviceInfo::SaveToDb() const { + + DeviceDatabaseBackend::Device ret; + ret.friendly_name_ = friendly_name_; + ret.size_ = size_; + ret.id_ = database_id_; + ret.icon_name_ = icon_name_; + ret.transcode_mode_ = transcode_mode_; + ret.transcode_format_ = transcode_format_; + + QStringList unique_ids; + for (const Backend &backend : backends_) { + unique_ids << backend.unique_id_; + } + ret.unique_id_ = unique_ids.join(","); + + return ret; + +} + +void DeviceInfo::InitFromDb(const DeviceDatabaseBackend::Device &dev) { + + database_id_ = dev.id_; + friendly_name_ = dev.friendly_name_; + size_ = dev.size_; + transcode_mode_ = dev.transcode_mode_; + transcode_format_ = dev.transcode_format_; + + QStringList icon_names = dev.icon_name_.split(','); + QVariantList icons; + for (const QString &icon_name : icon_names) { + icons << icon_name; + } + + LoadIcon(icons, friendly_name_); + + QStringList unique_ids = dev.unique_id_.split(','); + for (const QString &id : unique_ids) { + backends_ << Backend(nullptr, id); + } + +} + +const DeviceInfo::Backend *DeviceInfo::BestBackend() const { + + int best_priority = -1; + const Backend *ret = nullptr; + + for (int i = 0; i < backends_.count(); ++i) { + if (backends_[i].lister_ && backends_[i].lister_->priority() > best_priority) { + best_priority = backends_[i].lister_->priority(); + ret = &(backends_[i]); + } + } + + if (!ret && !backends_.isEmpty()) return &(backends_[0]); + return ret; + +} + +void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) { + + icon_name_ = "device"; + + if (icons.isEmpty()) { + icon_ = IconLoader::Load(icon_name_); + return; + } + + // Try to load the icon with that exact name first + for (const QVariant &icon : icons) { + if (!icon.value().isNull()) { + icon_ = QIcon(icon.value()); + return; + } + else { + if (!icon.toString().isEmpty()) icon_ = IconLoader::Load(icon.toString()); + if (!icon_.isNull()) { + icon_name_ = icon.toString(); + return; + } + } + } + + QString hint = QString(icons.first().toString() + name_hint).toLower(); + + if (hint.contains("phone")) + icon_name_ = "device-phone"; + else if (hint.contains("ipod") || hint.contains("apple")) + icon_name_ = "device-ipod"; + else if ((hint.contains("usb")) && (hint.contains("reader"))) + icon_name_ = "device-usb-flash"; + else if (hint.contains("usb")) + icon_name_ = "device-usb-drive"; + else + icon_name_ = "device"; + + icon_ = IconLoader::Load(icon_name_); + +} + + diff --git a/src/device/deviceinfo.h b/src/device/deviceinfo.h new file mode 100644 index 000000000..068c15dbf --- /dev/null +++ b/src/device/deviceinfo.h @@ -0,0 +1,121 @@ +/* + * Strawberry Music Player + * This code was part of Clementine. + * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef DEVICEINFO_H +#define DEVICEINFO_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "core/song.h" +#include "core/musicstorage.h" +#include "core/simpletreemodel.h" +#include "core/simpletreeitem.h" +#include "collection/collectionmodel.h" +#include "devicedatabasebackend.h" +#include "devicelister.h" + +class Application; +class ConnectedDevice; +class DeviceLister; +class DeviceStateFilterModel; + +// Devices can be in three different states: +// 1) Remembered in the database but not physically connected at the moment. +// database_id valid, lister null, device null +// 2) Physically connected but the user hasn't "connected" it to Strawberry +// yet. +// database_id == -1, lister valid, device null +// 3) Physically connected and connected to Strawberry +// database_id valid, lister valid, device valid +// Devices in all states will have a unique_id. + +class DeviceInfo : public SimpleTreeItem { + + public: + enum Type { + Type_Root, + Type_Device, + }; + + DeviceInfo(SimpleTreeModel *model) + : SimpleTreeItem(Type_Root, model), + database_id_(-1), + size_(0), + transcode_mode_(MusicStorage::Transcode_Unsupported), + transcode_format_(Song::FileType_Unknown), + task_percentage_(-1) {} + + DeviceInfo(Type type, DeviceInfo *parent = nullptr) + : SimpleTreeItem(type, parent), + database_id_(-1), + size_(0), + transcode_mode_(MusicStorage::Transcode_Unsupported), + transcode_format_(Song::FileType_Unknown), + task_percentage_(-1) {} + + // A device can be discovered in different ways (devicekit, gio, etc.) + // Sometimes the same device is discovered more than once. In this case the device will have multiple "backends". + struct Backend { + Backend(DeviceLister *lister = nullptr, const QString &id = QString()) + : lister_(lister), unique_id_(id) {} + + DeviceLister *lister_; // nullptr if not physically connected + QString unique_id_; + }; + + // Serialising to the database + void InitFromDb(const DeviceDatabaseBackend::Device &dev); + DeviceDatabaseBackend::Device SaveToDb() const; + + // Tries to load a good icon for the device. Sets icon_name_ and icon_. + void LoadIcon(const QVariantList &icons, const QString &name_hint); + + // Gets the best backend available (the one with the highest priority) + const Backend *BestBackend() const; + + int database_id_; // -1 if not remembered in the database + std::shared_ptr device_; // nullptr if not connected + QList backends_; + + QString friendly_name_; + quint64 size_; + + QString icon_name_; + QIcon icon_; + + MusicStorage::TranscodeMode transcode_mode_; + Song::FileType transcode_format_; + + int task_percentage_; + +}; + +#endif // DEVICEINFO_H diff --git a/src/device/devicekitlister.cpp b/src/device/devicekitlister.cpp index 6836c9ada..89db1f7c1 100644 --- a/src/device/devicekitlister.cpp +++ b/src/device/devicekitlister.cpp @@ -48,7 +48,7 @@ QString DeviceKitLister::DeviceData::unique_id() const { return QString("DeviceKit/%1/%2/%3/%4").arg(drive_serial, drive_vendor, drive_model).arg(device_size); } -void DeviceKitLister::Init() { +bool DeviceKitLister::Init() { interface_.reset(new OrgFreedesktopUDisksInterface(OrgFreedesktopUDisksInterface::staticInterfaceName(), "/org/freedesktop/UDisks", QDBusConnection::systemBus())); @@ -59,7 +59,7 @@ void DeviceKitLister::Init() { if (!reply.isValid()) { qLog(Warning) << "Error enumerating DeviceKit-disks devices:" << reply.error().name() << reply.error().message(); interface_.reset(); - return; + return false; } // Listen for changes @@ -85,6 +85,8 @@ void DeviceKitLister::Init() { emit DeviceAdded(id); } + return true; + } QStringList DeviceKitLister::DeviceUniqueIDs() { diff --git a/src/device/devicekitlister.h b/src/device/devicekitlister.h index 48efa8a92..4a25e1d22 100644 --- a/src/device/devicekitlister.h +++ b/src/device/devicekitlister.h @@ -69,7 +69,7 @@ class DeviceKitLister : public DeviceLister { void UpdateDeviceFreeSpace(const QString &id); protected: - void Init(); + bool Init(); private slots: void DBusDeviceAdded(const QDBusObjectPath &path); diff --git a/src/device/devicelister.h b/src/device/devicelister.h index aa3d56630..770d39915 100644 --- a/src/device/devicelister.h +++ b/src/device/devicelister.h @@ -82,7 +82,7 @@ signals: void DeviceMounted(const QString &id, int request_id, bool success); protected: - virtual void Init() = 0; + virtual bool Init() = 0; QUrl MakeUrlFromLocalPath(const QString &path) const; bool IsIpod(const QString &path) const; diff --git a/src/device/devicemanager.cpp b/src/device/devicemanager.cpp index 04a559abf..a6c9ed7fe 100644 --- a/src/device/devicemanager.cpp +++ b/src/device/devicemanager.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,9 +29,7 @@ #include #include #include -#include -#include -#include +#include #include #include #include @@ -39,9 +38,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -56,33 +53,18 @@ #include "core/musicstorage.h" #include "core/taskmanager.h" #include "core/utilities.h" +#include "core/simpletreemodel.h" #include "filesystemdevice.h" #include "connecteddevice.h" #include "devicelister.h" #include "devicedatabasebackend.h" #include "devicestatefiltermodel.h" +#include "deviceinfo.h" #if defined(HAVE_AUDIOCD) && defined(HAVE_GSTREAMER) # include "cddalister.h" # include "cddadevice.h" #endif - -#if defined(Q_OS_MACOS) and defined(HAVE_LIBMTP) -# include "macdevicelister.h" -#endif -#ifdef HAVE_LIBGPOD -# include "gpoddevice.h" -#endif -#ifdef HAVE_GIO -# include "giolister.h" -#endif -#ifdef HAVE_IMOBILEDEVICE -# include "afcdevice.h" -# include "ilister.h" -#endif -#ifdef HAVE_LIBMTP -# include "mtpdevice.h" -#endif #ifdef HAVE_DBUS # ifdef HAVE_DEVICEKIT # include "devicekitlister.h" @@ -91,123 +73,33 @@ # include "udisks2lister.h" # endif #endif +#ifdef HAVE_LIBMTP +# include "mtpdevice.h" +#endif +#ifdef HAVE_IMOBILEDEVICE +# include "afcdevice.h" +# include "ilister.h" +#endif +#if defined(Q_OS_MACOS) and defined(HAVE_LIBMTP) +# include "macdevicelister.h" +#endif +#ifdef HAVE_LIBGPOD +# include "gpoddevice.h" +#endif +#ifdef HAVE_GIO +# include "giolister.h" // Needs to be last because of #undef signals. +#endif using std::bind; const int DeviceManager::kDeviceIconSize = 32; const int DeviceManager::kDeviceIconOverlaySize = 16; -DeviceManager::DeviceInfo::DeviceInfo() - : database_id_(-1), - transcode_mode_(MusicStorage::Transcode_Unsupported), - transcode_format_(Song::FileType_Unknown), - task_percentage_(-1) {} - -DeviceDatabaseBackend::Device DeviceManager::DeviceInfo::SaveToDb() const { - - DeviceDatabaseBackend::Device ret; - ret.friendly_name_ = friendly_name_; - ret.size_ = size_; - ret.id_ = database_id_; - ret.icon_name_ = icon_name_; - ret.transcode_mode_ = transcode_mode_; - ret.transcode_format_ = transcode_format_; - - QStringList unique_ids; - for (const Backend &backend : backends_) { - unique_ids << backend.unique_id_; - } - ret.unique_id_ = unique_ids.join(","); - - return ret; - -} - -void DeviceManager::DeviceInfo::InitFromDb(const DeviceDatabaseBackend::Device &dev) { - - database_id_ = dev.id_; - friendly_name_ = dev.friendly_name_; - size_ = dev.size_; - transcode_mode_ = dev.transcode_mode_; - transcode_format_ = dev.transcode_format_; - - QStringList icon_names = dev.icon_name_.split(','); - QVariantList icons; - for (const QString &icon_name : icon_names) { - icons << icon_name; - } - - LoadIcon(icons, friendly_name_); - - QStringList unique_ids = dev.unique_id_.split(','); - for (const QString &id : unique_ids) { - backends_ << Backend(nullptr, id); - } - -} - -void DeviceManager::DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) { - - icon_name_ = "device"; - - if (icons.isEmpty()) { - icon_ = IconLoader::Load(icon_name_); - return; - } - - // Try to load the icon with that exact name first - for (const QVariant &icon : icons) { - if (!icon.value().isNull()) { - icon_ = QIcon(icon.value()); - return; - } - else { - if (!icon.toString().isEmpty()) icon_ = IconLoader::Load(icon.toString()); - if (!icon_.isNull()) { - icon_name_ = icon.toString(); - return; - } - } - } - - QString hint = QString(icons.first().toString() + name_hint).toLower(); - - if (hint.contains("phone")) - icon_name_ = "device-phone"; - else if (hint.contains("ipod") || hint.contains("apple")) - icon_name_ = "device-ipod"; - else if ((hint.contains("usb")) && (hint.contains("reader"))) - icon_name_ = "device-usb-flash"; - else if (hint.contains("usb")) - icon_name_ = "device-usb-drive"; - else - icon_name_ = "device"; - - icon_ = IconLoader::Load(icon_name_); - -} - -const DeviceManager::DeviceInfo::Backend *DeviceManager::DeviceInfo::BestBackend() const { - - int best_priority = -1; - const Backend *ret = nullptr; - - for (int i = 0; i < backends_.count(); ++i) { - if (backends_[i].lister_ && backends_[i].lister_->priority() > best_priority) { - best_priority = backends_[i].lister_->priority(); - ret = &(backends_[i]); - } - } - - if (!ret && !backends_.isEmpty()) return &(backends_[0]); - return ret; -} - DeviceManager::DeviceManager(Application *app, QObject *parent) - : QAbstractListModel(parent), - app_(app), - not_connected_overlay_(IconLoader::Load("edit-delete")) -{ + : SimpleTreeModel(new DeviceInfo(this), parent), + app_(app), + not_connected_overlay_(IconLoader::Load("edit-delete")) { + thread_pool_.setMaxThreadCount(1); connect(app_->task_manager(), SIGNAL(TasksChanged()), SLOT(TasksChanged())); @@ -270,11 +162,11 @@ DeviceManager::~DeviceManager() { void DeviceManager::LoadAllDevices() { Q_ASSERT(QThread::currentThread() != qApp->thread()); + DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices(); for (const DeviceDatabaseBackend::Device &device : devices) { - DeviceInfo info; - info.InitFromDb(device); - + DeviceInfo *info = new DeviceInfo(DeviceInfo::Type_Root, root_); + info->InitFromDb(device); beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); devices_ << info; endInsertRows(); @@ -282,34 +174,30 @@ void DeviceManager::LoadAllDevices() { } -int DeviceManager::rowCount(const QModelIndex&) const { - return devices_.count(); -} - QVariant DeviceManager::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.column() != 0) return QVariant(); - const DeviceInfo &info = devices_[index.row()]; + const DeviceInfo *info = IndexToItem(index); switch (role) { case Qt::DisplayRole: { QString text; - if (!info.friendly_name_.isEmpty()) - text = info.friendly_name_; + if (!info->friendly_name_.isEmpty()) + text = info->friendly_name_; else - text = info.BestBackend()->unique_id_; + text = info->BestBackend()->unique_id_; - if (info.size_) - text = text + QString(" (%1)").arg(Utilities::PrettySize(info.size_)); - if (info.device_.get()) info.device_->Refresh(); + if (info->size_) + text = text + QString(" (%1)").arg(Utilities::PrettySize(info->size_)); + if (info->device_.get()) info->device_->Refresh(); return text; } case Qt::DecorationRole: { - QPixmap pixmap = info.icon_.pixmap(kDeviceIconSize); + QPixmap pixmap = info->icon_.pixmap(kDeviceIconSize); - if (info.backends_.isEmpty() || !info.BestBackend()->lister_) { + if (info->backends_.isEmpty() || !info->BestBackend()->lister_) { // Disconnected but remembered QPainter p(&pixmap); p.drawPixmap(kDeviceIconSize - kDeviceIconOverlaySize, kDeviceIconSize - kDeviceIconOverlaySize, not_connected_overlay_.pixmap(kDeviceIconOverlaySize)); @@ -319,45 +207,45 @@ QVariant DeviceManager::data(const QModelIndex &index, int role) const { } case Role_FriendlyName: - return info.friendly_name_; + return info->friendly_name_; case Role_UniqueId: - return info.BestBackend()->unique_id_; + return info->BestBackend()->unique_id_; case Role_IconName: - return info.icon_name_; + return info->icon_name_; case Role_Capacity: case MusicStorage::Role_Capacity: - return info.size_; + return info->size_; case Role_FreeSpace: case MusicStorage::Role_FreeSpace: - return info.BestBackend()->lister_ ? info.BestBackend()->lister_->DeviceFreeSpace(info.BestBackend()->unique_id_) : QVariant(); + return info->BestBackend()->lister_ ? info->BestBackend()->lister_->DeviceFreeSpace(info->BestBackend()->unique_id_) : QVariant(); case Role_State: - if (info.device_) return State_Connected; - if (info.BestBackend()->lister_) { - if (info.BestBackend()->lister_->DeviceNeedsMount(info.BestBackend()->unique_id_)) return State_NotMounted; + if (info->device_) return State_Connected; + if (info->BestBackend()->lister_) { + if (info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) return State_NotMounted; return State_NotConnected; } return State_Remembered; case Role_UpdatingPercentage: - if (info.task_percentage_ == -1) return QVariant(); - return info.task_percentage_; + if (info->task_percentage_ == -1) return QVariant(); + return info->task_percentage_; case MusicStorage::Role_Storage: - if (!info.device_ && info.database_id_ != -1) + if (!info->device_ && info->database_id_ != -1) const_cast(this)->Connect(index.row()); - if (!info.device_) return QVariant(); - return QVariant::fromValue>(info.device_); + if (!info->device_) return QVariant(); + return QVariant::fromValue>(info->device_); case MusicStorage::Role_StorageForceConnect: - if (!info.device_) { - if (info.database_id_ == -1 && !info.BestBackend()->lister_->DeviceNeedsMount(info.BestBackend()->unique_id_)) { + if (!info->device_) { + if (info->database_id_ == -1 && !info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) { - if (info.BestBackend()->lister_->AskForScan(info.BestBackend()->unique_id_)) { + if (info->BestBackend()->lister_->AskForScan(info->BestBackend()->unique_id_)) { std::unique_ptr dialog(new QMessageBox(QMessageBox::Information, tr("Connect device"), tr("This is the first time you have connected this device. Strawberry will now scan the device to find music files - this may take some time."), QMessageBox::Cancel)); QPushButton *connect = dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole); dialog->exec(); @@ -368,13 +256,13 @@ QVariant DeviceManager::data(const QModelIndex &index, int role) const { const_cast(this)->Connect(index.row()); } - if (!info.device_) return QVariant(); - return QVariant::fromValue>(info.device_); + if (!info->device_) return QVariant(); + return QVariant::fromValue>(info->device_); case Role_MountPath: { - if (!info.device_) return QVariant(); + if (!info->device_) return QVariant(); - QString ret = info.device_->url().path(); + QString ret = info->device_->url().path(); #ifdef Q_OS_WIN32 if (ret.startsWith('/')) ret.remove(0, 1); #endif @@ -382,14 +270,14 @@ QVariant DeviceManager::data(const QModelIndex &index, int role) const { } case Role_TranscodeMode: - return info.transcode_mode_; + return info->transcode_mode_; case Role_TranscodeFormat: - return info.transcode_format_; + return info->transcode_format_; case Role_SongCount: - if (!info.device_) return QVariant(); - return info.device_->song_count(); + if (!info->device_) return QVariant(); + return info->device_->song_count(); default: return QVariant(); @@ -410,7 +298,7 @@ void DeviceManager::AddLister(DeviceLister *lister) { int DeviceManager::FindDeviceById(const QString &id) const { for (int i = 0; i < devices_.count(); ++i) { - for (const DeviceInfo::Backend &backend : devices_[i].backends_) { + for (const DeviceInfo::Backend &backend : devices_[i]->backends_) { if (backend.unique_id_ == id) return i; } } @@ -424,7 +312,7 @@ int DeviceManager::FindDeviceByUrl(const QList &urls) const { if (urls.isEmpty()) return -1; for (int i = 0; i < devices_.count(); ++i) { - for (const DeviceInfo::Backend &backend : devices_[i].backends_) { + for (const DeviceInfo::Backend &backend : devices_[i]->backends_) { if (!backend.lister_) continue; QList device_urls = backend.lister_->MakeDeviceUrls(backend.unique_id_); @@ -447,10 +335,10 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) { // Do we have this device already? int i = FindDeviceById(id); if (i != -1) { - DeviceInfo &info = devices_[i]; - for (int backend_index = 0 ; backend_index < info.backends_.count() ; ++backend_index) { - if (info.backends_[backend_index].unique_id_ == id) { - info.backends_[backend_index].lister_ = lister; + DeviceInfo *info = devices_[i]; + for (int backend_index = 0 ; backend_index < info->backends_.count() ; ++backend_index) { + if (info->backends_[backend_index].unique_id_ == id) { + info->backends_[backend_index].lister_ = lister; break; } } @@ -462,25 +350,25 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) { i = FindDeviceByUrl(lister->MakeDeviceUrls(id)); if (i != -1) { // Add this device's lister to the existing device - DeviceInfo &info = devices_[i]; - info.backends_ << DeviceInfo::Backend(lister, id); + DeviceInfo *info = devices_[i]; + info->backends_ << DeviceInfo::Backend(lister, id); // If the user hasn't saved the device in the DB yet then overwrite the device's name and icon etc. - if (info.database_id_ == -1 && info.BestBackend()->lister_ == lister) { - info.friendly_name_ = lister->MakeFriendlyName(id); - info.size_ = lister->DeviceCapacity(id); - info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); + if (info->database_id_ == -1 && info->BestBackend()->lister_ == lister) { + info->friendly_name_ = lister->MakeFriendlyName(id); + info->size_ = lister->DeviceCapacity(id); + info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_); } emit dataChanged(index(i, 0), index(i, 0)); } else { // It's a completely new device - DeviceInfo info; - info.backends_ << DeviceInfo::Backend(lister, id); - info.friendly_name_ = lister->MakeFriendlyName(id); - info.size_ = lister->DeviceCapacity(id); - info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); + DeviceInfo *info = new DeviceInfo(DeviceInfo::Type_Root, root_); + info->backends_ << DeviceInfo::Backend(lister, id); + info->friendly_name_ = lister->MakeFriendlyName(id); + info->size_ = lister->DeviceCapacity(id); + info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_); beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); devices_ << info; @@ -502,33 +390,33 @@ void DeviceManager::PhysicalDeviceRemoved(const QString &id) { return; } - DeviceInfo &info = devices_[i]; + DeviceInfo *info = devices_[i]; - if (info.database_id_ != -1) { + if (info->database_id_ != -1) { // Keep the structure around, but just "disconnect" it - for (int backend_index = 0 ; backend_index < info.backends_.count() ; ++backend_index) { - if (info.backends_[backend_index].unique_id_ == id) { - info.backends_[backend_index].lister_ = nullptr; + for (int backend_index = 0 ; backend_index < info->backends_.count() ; ++backend_index) { + if (info->backends_[backend_index].unique_id_ == id) { + info->backends_[backend_index].lister_ = nullptr; break; } } - if (info.device_ && info.device_->lister() == lister) info.device_.reset(); + if (info->device_ && info->device_->lister() == lister) info->device_.reset(); - if (!info.device_) emit DeviceDisconnected(i); + if (!info->device_) emit DeviceDisconnected(i); emit dataChanged(index(i, 0), index(i, 0)); } else { // If this was the last lister for the device then remove it from the model - for (int backend_index = 0 ; backend_index < info.backends_.count() ; ++backend_index) { - if (info.backends_[backend_index].unique_id_ == id) { - info.backends_.removeAt(backend_index); + for (int backend_index = 0 ; backend_index < info->backends_.count() ; ++backend_index) { + if (info->backends_[backend_index].unique_id_ == id) { + info->backends_.removeAt(backend_index); break; } } - if (info.backends_.isEmpty()) { + if (info->backends_.isEmpty()) { beginRemoveRows(QModelIndex(), i, i); devices_.removeAt(i); @@ -557,33 +445,34 @@ void DeviceManager::PhysicalDeviceChanged(const QString &id) { } // TODO + } std::shared_ptr DeviceManager::Connect(int row) { - DeviceInfo &info = devices_[row]; - if (info.device_) // Already connected - return info.device_; + DeviceInfo *info = devices_[row]; + if (info->device_) // Already connected + return info->device_; std::shared_ptr ret; - if (!info.BestBackend()->lister_) // Not physically connected + if (!info->BestBackend()->lister_) // Not physically connected return ret; - if (info.BestBackend()->lister_->DeviceNeedsMount(info.BestBackend()->unique_id_)) { + if (info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) { // Mount the device - info.BestBackend()->lister_->MountDevice(info.BestBackend()->unique_id_); + info->BestBackend()->lister_->MountDevice(info->BestBackend()->unique_id_); return ret; } - bool first_time = (info.database_id_ == -1); + bool first_time = (info->database_id_ == -1); if (first_time) { // We haven't stored this device in the database before - info.database_id_ = backend_->AddDevice(info.SaveToDb()); + info->database_id_ = backend_->AddDevice(info->SaveToDb()); } // Get the device URLs - QList urls = info.BestBackend()->lister_->MakeDeviceUrls(info.BestBackend()->unique_id_); + QList urls = info->BestBackend()->lister_->MakeDeviceUrls(info->BestBackend()->unique_id_); if (urls.isEmpty()) return ret; // Take the first URL that we have a handler for @@ -630,65 +519,70 @@ std::shared_ptr DeviceManager::Connect(int row) { QMetaObject meta_object = device_classes_.value(device_url.scheme()); QObject *instance = meta_object.newInstance( Q_ARG(QUrl, device_url), - Q_ARG(DeviceLister*, info.BestBackend()->lister_), - Q_ARG(QString, info.BestBackend()->unique_id_), + Q_ARG(DeviceLister*, info->BestBackend()->lister_), + Q_ARG(QString, info->BestBackend()->unique_id_), Q_ARG(DeviceManager*, this), Q_ARG(Application*, app_), - Q_ARG(int, info.database_id_), Q_ARG(bool, first_time)); + Q_ARG(int, info->database_id_), Q_ARG(bool, first_time)); + ret.reset(static_cast(instance)); if (!ret) { qLog(Warning) << "Could not create device for" << device_url.toString(); + return ret; } - else { - ret->Init(); - info.device_ = ret; - emit dataChanged(index(row), index(row)); - connect(info.device_.get(), SIGNAL(TaskStarted(int)), SLOT(DeviceTaskStarted(int))); - connect(info.device_.get(), SIGNAL(SongCountUpdated(int)), SLOT(DeviceSongCountUpdated(int))); + bool result = ret->Init(); + if (!result) { + qLog(Warning) << "Could not connect to device" << device_url.toString(); + return ret; } + info->device_ = ret; + QModelIndex index = ItemToIndex(info); + emit dataChanged(index, index); + connect(info->device_.get(), SIGNAL(TaskStarted(int)), SLOT(DeviceTaskStarted(int))); + connect(info->device_.get(), SIGNAL(SongCountUpdated(int)), SLOT(DeviceSongCountUpdated(int))); emit DeviceConnected(row); - return ret; } std::shared_ptr DeviceManager::GetConnectedDevice(int row) const { - return devices_[row].device_; + return devices_[row]->device_; } int DeviceManager::GetDatabaseId(int row) const { - return devices_[row].database_id_; + return devices_[row]->database_id_; } DeviceLister *DeviceManager::GetLister(int row) const { - return devices_[row].BestBackend()->lister_; + return devices_[row]->BestBackend()->lister_; } void DeviceManager::Disconnect(int row) { - DeviceInfo &info = devices_[row]; - if (!info.device_) // Already disconnected + DeviceInfo *info = devices_[row]; + if (!info->device_) // Already disconnected return; - info.device_.reset(); + info->device_.reset(); emit DeviceDisconnected(row); - emit dataChanged(index(row), index(row)); + QModelIndex index = ItemToIndex(info); + emit dataChanged(index, index); } void DeviceManager::Forget(int row) { - DeviceInfo &info = devices_[row]; - if (info.database_id_ == -1) return; + DeviceInfo *info = devices_[row]; + if (info->database_id_ == -1) return; - if (info.device_) Disconnect(row); + if (info->device_) Disconnect(row); - backend_->RemoveDevice(info.database_id_); - info.database_id_ = -1; + backend_->RemoveDevice(info->database_id_); + info->database_id_ = -1; - if (!info.BestBackend()->lister_) { + if (!info->BestBackend()->lister_) { // It's not attached any more so remove it from the list beginRemoveRows(QModelIndex(), row, row); devices_.removeAt(row); @@ -704,10 +598,10 @@ void DeviceManager::Forget(int row) { } else { // It's still attached, set the name and icon back to what they were originally - const QString id = info.BestBackend()->unique_id_; + const QString id = info->BestBackend()->unique_id_; - info.friendly_name_ = info.BestBackend()->lister_->MakeFriendlyName(id); - info.LoadIcon(info.BestBackend()->lister_->DeviceIcons(id), info.friendly_name_); + info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id); + info->LoadIcon(info->BestBackend()->lister_->DeviceIcons(id), info->friendly_name_); dataChanged(index(row, 0), index(row, 0)); } @@ -716,16 +610,16 @@ void DeviceManager::Forget(int row) { void DeviceManager::SetDeviceOptions(int row, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format) { - DeviceInfo &info = devices_[row]; - info.friendly_name_ = friendly_name; - info.LoadIcon(QVariantList() << icon_name, friendly_name); - info.transcode_mode_ = mode; - info.transcode_format_ = format; + DeviceInfo *info = devices_[row]; + info->friendly_name_ = friendly_name; + info->LoadIcon(QVariantList() << icon_name, friendly_name); + info->transcode_mode_ = mode; + info->transcode_format_ = format; emit dataChanged(index(row, 0), index(row, 0)); - if (info.database_id_ != -1) - backend_->SetDeviceOptions(info.database_id_, friendly_name, icon_name, mode, format); + if (info->database_id_ != -1) + backend_->SetDeviceOptions(info->database_id_, friendly_name, icon_name, mode, format); } @@ -735,11 +629,12 @@ void DeviceManager::DeviceTaskStarted(int id) { if (!device) return; for (int i = 0; i < devices_.count(); ++i) { - DeviceInfo &info = devices_[i]; - if (info.device_.get() == device) { - active_tasks_[id] = index(i); - info.task_percentage_ = 0; - emit dataChanged(index(i), index(i)); + DeviceInfo *info = devices_[i]; + if (info->device_.get() == device) { + QModelIndex index = ItemToIndex(info); + active_tasks_[id] = index; + info->task_percentage_ = 0; + emit dataChanged(index, index); return; } } @@ -757,20 +652,22 @@ void DeviceManager::TasksChanged() { QPersistentModelIndex index = active_tasks_[task.id]; if (!index.isValid()) continue; - DeviceInfo &info = devices_[index.row()]; + DeviceInfo *info = IndexToItem(index); if (task.progress_max) - info.task_percentage_ = float(task.progress) / task.progress_max * 100; + info->task_percentage_ = float(task.progress) / task.progress_max * 100; else - info.task_percentage_ = 0; + info->task_percentage_ = 0; + emit dataChanged(index, index); finished_tasks.removeAll(index); + } for (const QPersistentModelIndex &index : finished_tasks) { if (!index.isValid()) continue; - DeviceInfo &info = devices_[index.row()]; - info.task_percentage_ = -1; + DeviceInfo *info = devices_[index.row()]; + info->task_percentage_ = -1; emit dataChanged(index, index); active_tasks_.remove(active_tasks_.key(index)); @@ -784,13 +681,13 @@ void DeviceManager::UnmountAsync(int row) { void DeviceManager::Unmount(int row) { - DeviceInfo &info = devices_[row]; - if (info.database_id_ != -1 && !info.device_) return; + DeviceInfo *info = devices_[row]; + if (info->database_id_ != -1 && !info->device_) return; - if (info.device_) Disconnect(row); + if (info->device_) Disconnect(row); - if (info.BestBackend()->lister_) - info.BestBackend()->lister_->UnmountDevice(info.BestBackend()->unique_id_); + if (info->BestBackend()->lister_) + info->BestBackend()->lister_->UnmountDevice(info->BestBackend()->unique_id_); } @@ -802,7 +699,25 @@ void DeviceManager::DeviceSongCountUpdated(int count) { int row = FindDeviceById(device->unique_id()); if (row == -1) return; - emit dataChanged(index(row), index(row)); + QModelIndex index = ItemToIndex(devices_[row]); + emit dataChanged(index, index); } +void DeviceManager::LazyPopulate(DeviceInfo *parent, bool signal) { + if (parent->lazy_loaded) return; + parent->lazy_loaded = true; +} + +DeviceInfo *DeviceManager::ItemFromRow(int row) { + return devices_[row]; +} + +QString DeviceManager::DeviceNameByID(QString unique_id) { + + int row = FindDeviceById(unique_id); + DeviceInfo *info = devices_[row]; + QModelIndex index = ItemToIndex(info); + return data(index, DeviceManager::Role_FriendlyName).toString(); + +} diff --git a/src/device/devicemanager.h b/src/device/devicemanager.h index ef26700a4..572db7e9c 100644 --- a/src/device/devicemanager.h +++ b/src/device/devicemanager.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,28 +31,29 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include +#include #include #include "core/song.h" #include "core/musicstorage.h" +#include "core/simpletreemodel.h" #include "collection/collectionmodel.h" #include "devicedatabasebackend.h" +#include "deviceinfo.h" class Application; class ConnectedDevice; class DeviceLister; class DeviceStateFilterModel; -class DeviceManager : public QAbstractListModel { +class DeviceManager : public SimpleTreeModel { Q_OBJECT public: @@ -90,8 +92,10 @@ class DeviceManager : public QAbstractListModel { DeviceLister *GetLister(int row) const; std::shared_ptr GetConnectedDevice(int row) const; + DeviceInfo *ItemFromRow(int row); int FindDeviceById(const QString &id) const; int FindDeviceByUrl(const QList &url) const; + QString DeviceNameByID(QString unique_id); // Actions on devices std::shared_ptr Connect(int row); @@ -101,14 +105,13 @@ class DeviceManager : public QAbstractListModel { void SetDeviceOptions(int row, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format); - // QAbstractListModel - int rowCount(const QModelIndex &parent = QModelIndex()) const; + // QAbstractItemModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; public slots: void Unmount(int row); -signals: + signals: void DeviceConnected(int row); void DeviceDisconnected(int row); @@ -121,55 +124,11 @@ signals: void DeviceSongCountUpdated(int count); void LoadAllDevices(); + protected: + void LazyPopulate(DeviceInfo *item) { LazyPopulate(item, true); } + void LazyPopulate(DeviceInfo *item, bool signal); + private: - // Devices can be in three different states: - // 1) Remembered in the database but not physically connected at the moment. - // database_id valid, lister null, device null - // 2) Physically connected but the user hasn't "connected" it to Strawberry - // yet. - // database_id == -1, lister valid, device null - // 3) Physically connected and connected to Strawberry - // database_id valid, lister valid, device valid - // Devices in all states will have a unique_id. - struct DeviceInfo { - DeviceInfo(); - - // A device can be discovered in different ways (devicekit, gio, etc.) - // Sometimes the same device is discovered more than once. In this case the device will have multiple "backends". - struct Backend { - Backend(DeviceLister *lister = nullptr, const QString &id = QString()) - : lister_(lister), unique_id_(id) {} - - DeviceLister *lister_; // nullptr if not physically connected - QString unique_id_; - }; - - // Serialising to the database - void InitFromDb(const DeviceDatabaseBackend::Device &dev); - DeviceDatabaseBackend::Device SaveToDb() const; - - // Tries to load a good icon for the device. Sets icon_name_ and icon_. - void LoadIcon(const QVariantList &icons, const QString &name_hint); - - // Gets the best backend available (the one with the highest priority) - const Backend *BestBackend() const; - - int database_id_; // -1 if not remembered in the database - std::shared_ptr - device_; // nullptr if not connected - QList backends_; - - QString friendly_name_; - quint64 size_; - - QString icon_name_; - QIcon icon_; - - MusicStorage::TranscodeMode transcode_mode_; - Song::FileType transcode_format_; - - int task_percentage_; - }; void AddLister(DeviceLister *lister); template void AddDeviceClass(); @@ -185,7 +144,7 @@ signals: QIcon not_connected_overlay_; QList listers_; - QList devices_; + QList devices_; QMultiMap device_classes_; diff --git a/src/device/deviceproperties.cpp b/src/device/deviceproperties.cpp index 0087f3b66..c473b1695 100644 --- a/src/device/deviceproperties.cpp +++ b/src/device/deviceproperties.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -56,6 +55,7 @@ #include "connecteddevice.h" #include "devicelister.h" #include "devicemanager.h" +#include "deviceinfo.h" #include "deviceproperties.h" #ifdef HAVE_GSTREAMER # include "transcoder/transcoder.h" @@ -117,7 +117,7 @@ void DeviceProperties::ShowDevice(int row) { #endif } - index_ = manager_->index(row); + index_ = manager_->index(row, 0, QModelIndex()); // Basic information ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString()); @@ -321,4 +321,3 @@ void DeviceProperties::UpdateFormatsFinished(QFuture future) { ui_->formats_stack->setCurrentWidget(ui_->formats_page); } - diff --git a/src/device/deviceproperties.h b/src/device/deviceproperties.h index 8725b258c..a61fe07f6 100644 --- a/src/device/deviceproperties.h +++ b/src/device/deviceproperties.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/device/deviceview.cpp b/src/device/deviceview.cpp index a038a7385..1d5fe7bde 100644 --- a/src/device/deviceview.cpp +++ b/src/device/deviceview.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,6 +65,7 @@ #include "connecteddevice.h" #include "devicelister.h" #include "devicemanager.h" +#include "deviceinfo.h" #include "deviceproperties.h" #include "deviceview.h" @@ -259,7 +261,6 @@ void DeviceView::contextMenuEvent(QContextMenuEvent *e) { bool is_filesystem_device = false; if (parent_device_index.isValid()) { std::shared_ptr device = app_->device_manager()->GetConnectedDevice(parent_device_index.row()); - qLog(Debug) << device->LocalPath(); if (device && !device->LocalPath().isEmpty()) is_filesystem_device = true; } @@ -306,7 +307,9 @@ void DeviceView::DeviceConnected(int row) { std::shared_ptr device = app_->device_manager()->GetConnectedDevice(row); if (!device) return; - QModelIndex sort_idx = sort_model_->mapFromSource(app_->device_manager()->index(row)); + DeviceInfo *info = app_->device_manager()->ItemFromRow(row); + QModelIndex index = app_->device_manager()->ItemToIndex(info); + QModelIndex sort_idx = sort_model_->mapFromSource(index); QSortFilterProxyModel *sort_model = new QSortFilterProxyModel(device->model()); sort_model->setSourceModel(device->model()); @@ -320,7 +323,9 @@ void DeviceView::DeviceConnected(int row) { } void DeviceView::DeviceDisconnected(int row) { - merged_model_->RemoveSubModel(sort_model_->mapFromSource(app_->device_manager()->index(row))); + DeviceInfo *info = app_->device_manager()->ItemFromRow(row); + QModelIndex index = app_->device_manager()->ItemToIndex(info); + merged_model_->RemoveSubModel(sort_model_->mapFromSource(index)); } void DeviceView::Forget() { diff --git a/src/device/deviceview.h b/src/device/deviceview.h index 7531337b5..d7fc397df 100644 --- a/src/device/deviceview.h +++ b/src/device/deviceview.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,7 +30,6 @@ #include #include #include -#include #include #include #include diff --git a/src/device/filesystemdevice.cpp b/src/device/filesystemdevice.cpp index 362ce011f..48b6553a4 100644 --- a/src/device/filesystemdevice.cpp +++ b/src/device/filesystemdevice.cpp @@ -42,7 +42,7 @@ FilesystemDevice::FilesystemDevice(const QUrl &url, DeviceLister *lister, const watcher_->moveToThread(watcher_thread_); watcher_thread_->start(QThread::IdlePriority); - watcher_->set_device_name(manager->data(manager->index(manager->FindDeviceById(unique_id)), DeviceManager::Role_FriendlyName).toString()); + watcher_->set_device_name(manager->DeviceNameByID(unique_id)); watcher_->set_backend(backend_); watcher_->set_task_manager(app_->task_manager()); @@ -58,9 +58,10 @@ FilesystemDevice::FilesystemDevice(const QUrl &url, DeviceLister *lister, const } -void FilesystemDevice::Init() { +bool FilesystemDevice::Init() { InitBackendDirectory(url_.toLocalFile(), first_time_); model_->Init(); + return true; } FilesystemDevice::~FilesystemDevice() { diff --git a/src/device/filesystemdevice.h b/src/device/filesystemdevice.h index 9f8162895..1b2130096 100644 --- a/src/device/filesystemdevice.h +++ b/src/device/filesystemdevice.h @@ -50,7 +50,7 @@ public: int database_id, bool first_time); ~FilesystemDevice(); - void Init(); + bool Init(); static QStringList url_schemes() { return QStringList() << "file"; } diff --git a/src/device/giolister.cpp b/src/device/giolister.cpp index f730d552e..52bc708e0 100644 --- a/src/device/giolister.cpp +++ b/src/device/giolister.cpp @@ -25,12 +25,16 @@ #include #include +#include +#include #include +#include #include #include #include #include #include +#include #include "core/logging.h" #include "core/signalchecker.h" @@ -85,7 +89,7 @@ void GioLister::VolumeMountFinished(GObject *object, GAsyncResult *result, gpoin OperationFinished(std::bind(g_volume_mount_finish, _1, _2, _3), object, result); } -void GioLister::Init() { +bool GioLister::Init() { monitor_.reset_without_add(g_volume_monitor_get()); @@ -116,6 +120,8 @@ void GioLister::Init() { signals_.append(CHECKED_GCONNECT(monitor_, "mount-changed", &MountChangedCallback, this)); signals_.append(CHECKED_GCONNECT(monitor_, "mount-removed", &MountRemovedCallback, this)); + return true; + } GioLister::~GioLister() { @@ -191,10 +197,13 @@ QList GioLister::MakeDeviceUrls(const QString &id) { QString mount_point; QString uri; + QString unix_device; + { QMutexLocker l(&mutex_); mount_point = devices_[id].mount_path; uri = devices_[id].mount_uri; + unix_device = devices_[id].volume_unix_device; } // gphoto2 gives invalid hostnames with []:, characters in @@ -204,12 +213,22 @@ QList GioLister::MakeDeviceUrls(const QString &id) { QList ret; - // Special case for file:// GIO URIs - we have to check whether they point to an ipod. - if (url.isValid() && url.scheme() == "file") { - ret << MakeUrlFromLocalPath(url.path()); - } - else { - ret << url; + if (url.isValid()) { + QRegExp device_re("usb/(\\d+)/(\\d+)"); + if (device_re.indexIn(unix_device) >= 0) { + QUrlQuery url_query(url); + url_query.addQueryItem("busnum", device_re.cap(1)); + url_query.addQueryItem("devnum", device_re.cap(2)); + url.setQuery(url_query); + } + + // Special case for file:// GIO URIs - we have to check whether they point to an ipod. + if (url.scheme() == "file") { + ret << MakeUrlFromLocalPath(url.path()); + } + else { + ret << url; + } } ret << MakeUrlFromLocalPath(mount_point); diff --git a/src/device/giolister.h b/src/device/giolister.h index 6d52f7beb..93c70fd4f 100644 --- a/src/device/giolister.h +++ b/src/device/giolister.h @@ -25,7 +25,7 @@ // Work around compile issue with glib >= 2.25 #ifdef signals -#undef signals +# undef signals #endif #include @@ -34,8 +34,8 @@ #include #include -#include #include +#include #include #include #include @@ -74,7 +74,7 @@ class GioLister : public DeviceLister { void UpdateDeviceFreeSpace(const QString &id); protected: - void Init(); + bool Init(); private: struct DeviceInfo { diff --git a/src/device/gpoddevice.cpp b/src/device/gpoddevice.cpp index 50e3710ce..87755dfa5 100644 --- a/src/device/gpoddevice.cpp +++ b/src/device/gpoddevice.cpp @@ -51,7 +51,7 @@ GPodDevice::GPodDevice(const QUrl &url, DeviceLister *lister, const QString &uni loader_(nullptr), db_(nullptr) {} -void GPodDevice::Init() { +bool GPodDevice::Init() { InitBackendDirectory(url_.path(), first_time_); model_->Init(); @@ -65,6 +65,8 @@ void GPodDevice::Init() { connect(loader_thread_, SIGNAL(started()), loader_, SLOT(LoadDatabase())); loader_thread_->start(); + return true; + } GPodDevice::~GPodDevice() {} diff --git a/src/device/gpoddevice.h b/src/device/gpoddevice.h index e08febf44..70c58ee8f 100644 --- a/src/device/gpoddevice.h +++ b/src/device/gpoddevice.h @@ -46,8 +46,11 @@ class GPodLoader; class GPodDevice : public ConnectedDevice, public virtual MusicStorage { Q_OBJECT + + signals: + void Error(const QString &message); -public: + public: Q_INVOKABLE GPodDevice( const QUrl &url, DeviceLister *lister, const QString &unique_id, DeviceManager *manager, @@ -55,7 +58,7 @@ public: int database_id, bool first_time); ~GPodDevice(); - void Init(); + bool Init(); static QStringList url_schemes() { return QStringList() << "ipod"; } @@ -69,9 +72,6 @@ public: bool DeleteFromStorage(const DeleteJob &job); void FinishDelete(bool success); - signals: - void Error(const QString &message); - protected slots: void LoadFinished(Itdb_iTunesDB *db); @@ -98,4 +98,3 @@ public: }; #endif // GPODDEVICE_H - diff --git a/src/device/ilister.cpp b/src/device/ilister.cpp index 705f00ad0..2e8b2070d 100644 --- a/src/device/ilister.cpp +++ b/src/device/ilister.cpp @@ -37,8 +37,9 @@ iLister::iLister() { iLister::~iLister() { } -void iLister::Init() { +bool iLister::Init() { idevice_event_subscribe(&EventCallback, reinterpret_cast(this)); + return true; } void iLister::EventCallback(const idevice_event_t *event, void *context) { diff --git a/src/device/ilister.h b/src/device/ilister.h index 6e9d217d5..cc9d1472e 100644 --- a/src/device/ilister.h +++ b/src/device/ilister.h @@ -78,7 +78,7 @@ class iLister : public DeviceLister { QString bt_mac; }; - virtual void Init(); + virtual bool Init(); static void EventCallback(const idevice_event_t *event, void *context); diff --git a/src/device/mtpconnection.cpp b/src/device/mtpconnection.cpp index d521d7b88..05b1a3406 100644 --- a/src/device/mtpconnection.cpp +++ b/src/device/mtpconnection.cpp @@ -38,15 +38,24 @@ MtpConnection::MtpConnection(const QUrl &url) : device_(nullptr) { // Parse the URL QRegExp host_re("^usb-(\\d+)-(\\d+)$"); - if (host_re.indexIn(hostname) == -1) { + unsigned int bus_location = 0; + unsigned int device_num = 0; + + QUrlQuery url_query(url); + + if (host_re.indexIn(hostname) >= 0) { + bus_location = host_re.cap(1).toUInt(); + device_num = host_re.cap(2).toUInt(); + } + else if (url_query.hasQueryItem("busnum")) { + bus_location = url_query.queryItemValue("busnum").toUInt(); + device_num = url_query.queryItemValue("devnum").toUInt(); + } + else { qLog(Warning) << "Invalid MTP device:" << hostname; return; } - const unsigned int bus_location = host_re.cap(1).toInt(); - const unsigned int device_num = host_re.cap(2).toInt(); - - QUrlQuery url_query(url); if (url_query.hasQueryItem("vendor")) { LIBMTP_raw_device_t *raw_device = (LIBMTP_raw_device_t*)malloc(sizeof(LIBMTP_raw_device_t)); raw_device->device_entry.vendor = url_query.queryItemValue("vendor").toLatin1().data(); diff --git a/src/device/mtpdevice.cpp b/src/device/mtpdevice.cpp index 9597657c5..a5d95d1bb 100644 --- a/src/device/mtpdevice.cpp +++ b/src/device/mtpdevice.cpp @@ -61,12 +61,17 @@ MtpDevice::MtpDevice(const QUrl &url, DeviceLister *lister, const QString &uniqu MtpDevice::~MtpDevice() {} -void MtpDevice::Init() { +bool MtpDevice::Init() { InitBackendDirectory("/", first_time_, false); - model_->Init(); loader_ = new MtpLoader(url_, app_->task_manager(), backend_, shared_from_this()); + if (!loader_->Init()) { + delete loader_; + loader_ = nullptr; + return false; + } + model_->Init(); loader_->moveToThread(loader_thread_); connect(loader_, SIGNAL(Error(QString)), SIGNAL(Error(QString))); @@ -77,6 +82,8 @@ void MtpDevice::Init() { db_busy_.lock(); loader_thread_->start(); + return true; + } void MtpDevice::LoadFinished() { diff --git a/src/device/mtpdevice.h b/src/device/mtpdevice.h index 6add271b2..6ccf7c555 100644 --- a/src/device/mtpdevice.h +++ b/src/device/mtpdevice.h @@ -53,7 +53,7 @@ class MtpDevice : public ConnectedDevice { static QStringList url_schemes() { return QStringList() << "mtp" << "gphoto2"; } - void Init(); + bool Init(); bool GetSupportedFiletypes(QList* ret); int GetFreeSpace(); diff --git a/src/device/mtploader.cpp b/src/device/mtploader.cpp index bd1cb0b80..8a2dddc76 100644 --- a/src/device/mtploader.cpp +++ b/src/device/mtploader.cpp @@ -40,7 +40,14 @@ MtpLoader::MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBacke original_thread_ = thread(); } -MtpLoader::~MtpLoader() {} +MtpLoader::~MtpLoader() { + delete connection_; +} + +bool MtpLoader::Init() { + connection_ = new MtpConnection(url_); + return connection_->is_valid(); +} void MtpLoader::LoadDatabase() { @@ -58,15 +65,9 @@ void MtpLoader::LoadDatabase() { bool MtpLoader::TryLoad() { - MtpConnection dev(url_); - if (!dev.is_valid()) { - emit Error(tr("Error connecting MTP device")); - return false; - } - // Load the list of songs on the device SongList songs; - LIBMTP_track_t *tracks = LIBMTP_Get_Tracklisting_With_Callback(dev.device(), nullptr, nullptr); + LIBMTP_track_t* tracks = LIBMTP_Get_Tracklisting_With_Callback(connection_->device(), nullptr, nullptr); while (tracks) { LIBMTP_track_t *track = tracks; diff --git a/src/device/mtploader.h b/src/device/mtploader.h index 9f80a852d..8e52ba7e4 100644 --- a/src/device/mtploader.h +++ b/src/device/mtploader.h @@ -34,6 +34,7 @@ class TaskManager; class CollectionBackend; class ConnectedDevice; +class MtpConnection; class MtpLoader : public QObject { Q_OBJECT @@ -42,10 +43,12 @@ class MtpLoader : public QObject { MtpLoader(const QUrl &url, TaskManager *task_manager, CollectionBackend *backend, std::shared_ptr device); ~MtpLoader(); + bool Init(); + public slots: void LoadDatabase(); -signals: + signals: void Error(const QString &message); void TaskStarted(int task_id); void LoadFinished(); @@ -60,7 +63,7 @@ signals: QUrl url_; TaskManager *task_manager_; CollectionBackend *backend_; + MtpConnection *connection_; }; #endif // MTPLOADER_H - diff --git a/src/device/udisks2lister.cpp b/src/device/udisks2lister.cpp index 413e3a7f2..179cc026b 100644 --- a/src/device/udisks2lister.cpp +++ b/src/device/udisks2lister.cpp @@ -162,7 +162,7 @@ void Udisks2Lister::UpdateDeviceFreeSpace(const QString &id) { emit DeviceChanged(id); } -void Udisks2Lister::Init() { +bool Udisks2Lister::Init() { udisks2_interface_.reset(new OrgFreedesktopDBusObjectManagerInterface(udisks2_service_, "/org/freedesktop/UDisks2", QDBusConnection::systemBus())); @@ -172,7 +172,7 @@ void Udisks2Lister::Init() { if (!reply.isValid()) { qLog(Warning) << "Error enumerating udisks2 devices:" << reply.error().name() << reply.error().message(); udisks2_interface_.reset(); - return; + return false; } for (const QDBusObjectPath &path : reply.value().keys()) { @@ -191,6 +191,8 @@ void Udisks2Lister::Init() { connect(udisks2_interface_.get(), SIGNAL(InterfacesAdded(QDBusObjectPath, InterfacesAndProperties)), SLOT(DBusInterfaceAdded(QDBusObjectPath, InterfacesAndProperties))); connect(udisks2_interface_.get(), SIGNAL(InterfacesRemoved(QDBusObjectPath, QStringList)), SLOT(DBusInterfaceRemoved(QDBusObjectPath, QStringList))); + return true; + } void Udisks2Lister::DBusInterfaceAdded(const QDBusObjectPath &path, const InterfacesAndProperties &interfaces) { diff --git a/src/device/udisks2lister.h b/src/device/udisks2lister.h index 74a6efc91..e3ca3fd46 100644 --- a/src/device/udisks2lister.h +++ b/src/device/udisks2lister.h @@ -68,7 +68,7 @@ class Udisks2Lister : public DeviceLister { void UpdateDeviceFreeSpace(const QString &id) override; protected: - void Init() override; + bool Init() override; private slots: void DBusInterfaceAdded(const QDBusObjectPath &path, const InterfacesAndProperties &ifaces);