Convert devices manager(model) to QAbstractItemModel (#6260)

This commit is contained in:
Jonas Kvinge 2019-01-21 10:06:48 +01:00 committed by John Maguire
parent a82cc2f8a6
commit 7e25a7c7e4
9 changed files with 410 additions and 298 deletions

View File

@ -137,6 +137,7 @@ set(SOURCES
devices/deviceview.cpp devices/deviceview.cpp
devices/deviceviewcontainer.cpp devices/deviceviewcontainer.cpp
devices/filesystemdevice.cpp devices/filesystemdevice.cpp
devices/deviceinfo.cpp
engines/devicefinder.cpp engines/devicefinder.cpp
engines/enginebase.cpp engines/enginebase.cpp
@ -454,6 +455,7 @@ set(HEADERS
devices/deviceview.h devices/deviceview.h
devices/deviceviewcontainer.h devices/deviceviewcontainer.h
devices/filesystemdevice.h devices/filesystemdevice.h
devices/deviceinfo.h
engines/enginebase.h engines/enginebase.h
engines/gstengine.h engines/gstengine.h

View File

@ -105,13 +105,16 @@ void ConnectedDevice::FinishDelete(bool) {
MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const { MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const {
int index = manager_->FindDeviceById(unique_id_); int index = manager_->FindDeviceById(unique_id_);
return MusicStorage::TranscodeMode( return MusicStorage::TranscodeMode(
manager_->index(index).data(DeviceManager::Role_TranscodeMode).toInt()); manager_->index(index, 0, QModelIndex())
.data(DeviceManager::Role_TranscodeMode)
.toInt());
} }
Song::FileType ConnectedDevice::GetTranscodeFormat() const { Song::FileType ConnectedDevice::GetTranscodeFormat() const {
int index = manager_->FindDeviceById(unique_id_); int index = manager_->FindDeviceById(unique_id_);
return Song::FileType( return Song::FileType(manager_->index(index, 0, QModelIndex())
manager_->index(index).data(DeviceManager::Role_TranscodeFormat).toInt()); .data(DeviceManager::Role_TranscodeFormat)
.toInt());
} }
void ConnectedDevice::BackendTotalSongCountUpdated(int count) { void ConnectedDevice::BackendTotalSongCountUpdated(int count) {

124
src/devices/deviceinfo.cpp Normal file
View File

@ -0,0 +1,124 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 "devicemanager.h"
#include <memory>
#include <QIcon>
#include <QPixmap>
#include <QString>
#include <QStringList>
#include <QVariant>
#include "config.h"
#include "core/logging.h"
#include "core/simpletreemodel.h"
#include "ui/iconloader.h"
#include "devicedatabasebackend.h"
#include "deviceinfo.h"
#include "devicelister.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) {
if (icons.isEmpty()) {
icon_name_ = "drive-removable-media-usb-pendrive";
icon_ = IconLoader::Load(icon_name_, IconLoader::Base);
return;
}
// Try to load the icon with that exact name first
for (const QVariant& icon : icons) {
if (!icon.value<QPixmap>().isNull()) {
icon_ = QIcon(icon.value<QPixmap>());
return;
} else {
icon_ = IconLoader::Load(icon.toString(), IconLoader::Base);
if (!icon_.isNull()) {
icon_name_ = icon.toString();
return;
}
}
}
QString hint = QString(icons.first().toString() + name_hint).toLower();
// If that failed than try to guess if it's a phone or ipod. Fall back on
// a usb memory stick icon.
if (hint.contains("phone"))
icon_name_ = "phone";
else if (hint.contains("ipod") || hint.contains("apple"))
icon_name_ = "multimedia-player-ipod-standard-monochrome";
else
icon_name_ = "drive-removable-media-usb-pendrive";
icon_ = IconLoader::Load(icon_name_, IconLoader::Base);
}

108
src/devices/deviceinfo.h Normal file
View File

@ -0,0 +1,108 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 DEVICEINFO_H
#define DEVICEINFO_H
#include <memory>
#include <QIcon>
#include <QList>
#include <QString>
#include <QVariant>
#include "core/musicstorage.h"
#include "core/simpletreeitem.h"
#include "core/simpletreemodel.h"
#include "core/song.h"
#include "devicedatabasebackend.h"
#include "library/librarymodel.h"
class ConnectedDevice;
class DeviceLister;
// 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 Clementine
// yet.
// database_id == -1, lister valid, device null
// 3) Physically connected and connected to Clementine
// database_id valid, lister valid, device valid
// Devices in all states will have a unique_id.
class DeviceInfo : public SimpleTreeItem<DeviceInfo> {
public:
enum Type {
Type_Root,
Type_Device,
};
DeviceInfo(SimpleTreeModel<DeviceInfo>* model)
: SimpleTreeItem<DeviceInfo>(Type_Root, model),
database_id_(-1),
size_(0),
transcode_mode_(MusicStorage::Transcode_Unsupported),
transcode_format_(Song::Type_Unknown),
task_percentage_(-1) {}
DeviceInfo(Type type, DeviceInfo* parent = nullptr)
: SimpleTreeItem<DeviceInfo>(type, parent),
database_id_(-1),
size_(0),
transcode_mode_(MusicStorage::Transcode_Unsupported),
transcode_format_(Song::Type_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<ConnectedDevice>
device_; // nullptr if not connected to clementine
QList<Backend> 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

View File

@ -34,9 +34,11 @@
#include "core/database.h" #include "core/database.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/musicstorage.h" #include "core/musicstorage.h"
#include "core/simpletreemodel.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "core/utilities.h" #include "core/utilities.h"
#include "devicedatabasebackend.h" #include "devicedatabasebackend.h"
#include "deviceinfo.h"
#include "devicekitlister.h" #include "devicekitlister.h"
#include "devicestatefiltermodel.h" #include "devicestatefiltermodel.h"
#include "filesystemdevice.h" #include "filesystemdevice.h"
@ -68,106 +70,8 @@ using std::bind;
const int DeviceManager::kDeviceIconSize = 32; const int DeviceManager::kDeviceIconSize = 32;
const int DeviceManager::kDeviceIconOverlaySize = 16; const int DeviceManager::kDeviceIconOverlaySize = 16;
DeviceManager::DeviceInfo::DeviceInfo()
: database_id_(-1),
transcode_mode_(MusicStorage::Transcode_Unsupported),
transcode_format_(Song::Type_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) {
if (icons.isEmpty()) {
icon_name_ = "drive-removable-media-usb-pendrive";
icon_ = IconLoader::Load(icon_name_, IconLoader::Base);
return;
}
// Try to load the icon with that exact name first
for (const QVariant& icon : icons) {
if (!icon.value<QPixmap>().isNull()) {
icon_ = QIcon(icon.value<QPixmap>());
return;
} else {
icon_ = IconLoader::Load(icon.toString(), IconLoader::Base);
if (!icon_.isNull()) {
icon_name_ = icon.toString();
return;
}
}
}
QString hint = QString(icons.first().toString() + name_hint).toLower();
// If that failed than try to guess if it's a phone or ipod. Fall back on
// a usb memory stick icon.
if (hint.contains("phone"))
icon_name_ = "phone";
else if (hint.contains("ipod") || hint.contains("apple"))
icon_name_ = "multimedia-player-ipod-standard-monochrome";
else
icon_name_ = "drive-removable-media-usb-pendrive";
icon_ = IconLoader::Load(icon_name_, IconLoader::Base);
}
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) DeviceManager::DeviceManager(Application* app, QObject* parent)
: QAbstractListModel(parent), : SimpleTreeModel<DeviceInfo>(new DeviceInfo(this), parent),
app_(app), app_(app),
not_connected_overlay_( not_connected_overlay_(
IconLoader::Load("edit-delete", IconLoader::Base)) { IconLoader::Load("edit-delete", IconLoader::Base)) {
@ -227,14 +131,15 @@ DeviceManager::~DeviceManager() {
} }
backend_->deleteLater(); backend_->deleteLater();
delete root_;
} }
void DeviceManager::LoadAllDevices() { void DeviceManager::LoadAllDevices() {
Q_ASSERT(QThread::currentThread() != qApp->thread()); Q_ASSERT(QThread::currentThread() != qApp->thread());
DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices(); DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices();
for (const DeviceDatabaseBackend::Device& device : devices) { for (const DeviceDatabaseBackend::Device& device : devices) {
DeviceInfo info; DeviceInfo* info = new DeviceInfo(DeviceInfo::Type_Root, root_);
info.InitFromDb(device); info->InitFromDb(device);
beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); beginInsertRows(QModelIndex(), devices_.count(), devices_.count());
devices_ << info; devices_ << info;
@ -242,33 +147,30 @@ void DeviceManager::LoadAllDevices() {
} }
} }
int DeviceManager::rowCount(const QModelIndex&) const {
return devices_.count();
}
QVariant DeviceManager::data(const QModelIndex& index, int role) const { QVariant DeviceManager::data(const QModelIndex& index, int role) const {
if (!index.isValid() || index.column() != 0) return QVariant(); if (!index.isValid() || index.column() != 0) return QVariant();
const DeviceInfo& info = devices_[index.row()]; const DeviceInfo* info = IndexToItem(index);
if (!info) return QVariant();
switch (role) { switch (role) {
case Qt::DisplayRole: { case Qt::DisplayRole: {
QString text; QString text;
if (!info.friendly_name_.isEmpty()) if (!info->friendly_name_.isEmpty())
text = info.friendly_name_; text = info->friendly_name_;
else else if (info->BestBackend())
text = info.BestBackend()->unique_id_; text = info->BestBackend()->unique_id_;
if (info.size_) if (info->size_)
text = text + QString(" (%1)").arg(Utilities::PrettySize(info.size_)); text = text + QString(" (%1)").arg(Utilities::PrettySize(info->size_));
if (info.device_.get()) info.device_->Refresh(); if (info->device_.get()) info->device_->Refresh();
return text; return text;
} }
case Qt::DecorationRole: { 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 // Disconnected but remembered
QPainter p(&pixmap); QPainter p(&pixmap);
p.drawPixmap(kDeviceIconSize - kDeviceIconOverlaySize, p.drawPixmap(kDeviceIconSize - kDeviceIconOverlaySize,
@ -280,52 +182,53 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const {
} }
case Role_FriendlyName: case Role_FriendlyName:
return info.friendly_name_; return info->friendly_name_;
case Role_UniqueId: case Role_UniqueId:
return info.BestBackend()->unique_id_; if (!info->BestBackend()) return QString();
return info->BestBackend()->unique_id_;
case Role_IconName: case Role_IconName:
return info.icon_name_; return info->icon_name_;
case Role_Capacity: case Role_Capacity:
case MusicStorage::Role_Capacity: case MusicStorage::Role_Capacity:
return info.size_; return info->size_;
case Role_FreeSpace: case Role_FreeSpace:
case MusicStorage::Role_FreeSpace: case MusicStorage::Role_FreeSpace:
return info.BestBackend()->lister_ return info->BestBackend() && info->BestBackend()->lister_
? info.BestBackend()->lister_->DeviceFreeSpace( ? info->BestBackend()->lister_->DeviceFreeSpace(
info.BestBackend()->unique_id_) info->BestBackend()->unique_id_)
: QVariant(); : QVariant();
case Role_State: case Role_State:
if (info.device_) return State_Connected; if (info->device_) return State_Connected;
if (info.BestBackend()->lister_) { if (info->BestBackend() && info->BestBackend()->lister_) {
if (info.BestBackend()->lister_->DeviceNeedsMount( if (info->BestBackend()->lister_->DeviceNeedsMount(
info.BestBackend()->unique_id_)) info->BestBackend()->unique_id_))
return State_NotMounted; return State_NotMounted;
return State_NotConnected; return State_NotConnected;
} }
return State_Remembered; return State_Remembered;
case Role_UpdatingPercentage: case Role_UpdatingPercentage:
if (info.task_percentage_ == -1) return QVariant(); if (info->task_percentage_ == -1) return QVariant();
return info.task_percentage_; return info->task_percentage_;
case MusicStorage::Role_Storage: case MusicStorage::Role_Storage:
if (!info.device_ && info.database_id_ != -1) if (!info->device_ && info->database_id_ != -1)
const_cast<DeviceManager*>(this)->Connect(index.row()); const_cast<DeviceManager*>(this)->Connect(index.row());
if (!info.device_) return QVariant(); if (!info->device_) return QVariant();
return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info.device_); return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info->device_);
case MusicStorage::Role_StorageForceConnect: case MusicStorage::Role_StorageForceConnect:
if (!info.device_) { if (!info->device_) {
if (info.database_id_ == -1 && if (info->database_id_ == -1 && info->BestBackend() &&
!info.BestBackend()->lister_->DeviceNeedsMount( !info->BestBackend()->lister_->DeviceNeedsMount(
info.BestBackend()->unique_id_)) { info->BestBackend()->unique_id_)) {
if (info.BestBackend()->lister_->AskForScan( if (info->BestBackend()->lister_->AskForScan(
info.BestBackend()->unique_id_)) { info->BestBackend()->unique_id_)) {
std::unique_ptr<QMessageBox> dialog(new QMessageBox( std::unique_ptr<QMessageBox> dialog(new QMessageBox(
QMessageBox::Information, tr("Connect device"), QMessageBox::Information, tr("Connect device"),
tr("This is the first time you have connected this device. " tr("This is the first time you have connected this device. "
@ -342,13 +245,13 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const {
const_cast<DeviceManager*>(this)->Connect(index.row()); const_cast<DeviceManager*>(this)->Connect(index.row());
} }
if (!info.device_) return QVariant(); if (!info->device_) return QVariant();
return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info.device_); return QVariant::fromValue<std::shared_ptr<MusicStorage>>(info->device_);
case Role_MountPath: { 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 #ifdef Q_OS_WIN32
if (ret.startsWith('/')) ret.remove(0, 1); if (ret.startsWith('/')) ret.remove(0, 1);
#endif #endif
@ -356,14 +259,14 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const {
} }
case Role_TranscodeMode: case Role_TranscodeMode:
return info.transcode_mode_; return info->transcode_mode_;
case Role_TranscodeFormat: case Role_TranscodeFormat:
return info.transcode_format_; return info->transcode_format_;
case Role_SongCount: case Role_SongCount:
if (!info.device_) return QVariant(); if (!info->device_) return QVariant();
return info.device_->song_count(); return info->device_->song_count();
default: default:
return QVariant(); return QVariant();
@ -384,7 +287,7 @@ void DeviceManager::AddLister(DeviceLister* lister) {
int DeviceManager::FindDeviceById(const QString& id) const { int DeviceManager::FindDeviceById(const QString& id) const {
for (int i = 0; i < devices_.count(); ++i) { 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; if (backend.unique_id_ == id) return i;
} }
} }
@ -395,7 +298,7 @@ int DeviceManager::FindDeviceByUrl(const QList<QUrl>& urls) const {
if (urls.isEmpty()) return -1; if (urls.isEmpty()) return -1;
for (int i = 0; i < devices_.count(); ++i) { 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; if (!backend.lister_) continue;
QList<QUrl> device_urls = QList<QUrl> device_urls =
@ -416,11 +319,11 @@ void DeviceManager::PhysicalDeviceAdded(const QString& id) {
// Do we have this device already? // Do we have this device already?
int i = FindDeviceById(id); int i = FindDeviceById(id);
if (i != -1) { if (i != -1) {
DeviceInfo& info = devices_[i]; DeviceInfo* info = devices_[i];
for (int backend_index = 0; backend_index < info.backends_.count(); for (int backend_index = 0; backend_index < info->backends_.count();
++backend_index) { ++backend_index) {
if (info.backends_[backend_index].unique_id_ == id) { if (info->backends_[backend_index].unique_id_ == id) {
info.backends_[backend_index].lister_ = lister; info->backends_[backend_index].lister_ = lister;
break; break;
} }
} }
@ -431,25 +334,25 @@ void DeviceManager::PhysicalDeviceAdded(const QString& id) {
i = FindDeviceByUrl(lister->MakeDeviceUrls(id)); i = FindDeviceByUrl(lister->MakeDeviceUrls(id));
if (i != -1) { if (i != -1) {
// Add this device's lister to the existing device // Add this device's lister to the existing device
DeviceInfo& info = devices_[i]; DeviceInfo* info = devices_[i];
info.backends_ << DeviceInfo::Backend(lister, id); info->backends_ << DeviceInfo::Backend(lister, id);
// If the user hasn't saved the device in the DB yet then overwrite the // If the user hasn't saved the device in the DB yet then overwrite the
// device's name and icon etc. // device's name and icon etc.
if (info.database_id_ == -1 && info.BestBackend()->lister_ == lister) { if (info->database_id_ == -1 && info->BestBackend()->lister_ == lister) {
info.friendly_name_ = lister->MakeFriendlyName(id); info->friendly_name_ = lister->MakeFriendlyName(id);
info.size_ = lister->DeviceCapacity(id); info->size_ = lister->DeviceCapacity(id);
info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
} }
emit dataChanged(index(i, 0), index(i, 0)); emit dataChanged(index(i, 0), index(i, 0));
} else { } else {
// It's a completely new device // It's a completely new device
DeviceInfo info; DeviceInfo* info = new DeviceInfo(DeviceInfo::Type_Root, root_);
info.backends_ << DeviceInfo::Backend(lister, id); info->backends_ << DeviceInfo::Backend(lister, id);
info.friendly_name_ = lister->MakeFriendlyName(id); info->friendly_name_ = lister->MakeFriendlyName(id);
info.size_ = lister->DeviceCapacity(id); info->size_ = lister->DeviceCapacity(id);
info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); beginInsertRows(QModelIndex(), devices_.count(), devices_.count());
devices_ << info; devices_ << info;
@ -469,34 +372,35 @@ void DeviceManager::PhysicalDeviceRemoved(const QString& id) {
return; 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 // Keep the structure around, but just "disconnect" it
for (int backend_index = 0; backend_index < info.backends_.count(); for (int backend_index = 0; backend_index < info->backends_.count();
++backend_index) { ++backend_index) {
if (info.backends_[backend_index].unique_id_ == id) { if (info->backends_[backend_index].unique_id_ == id) {
info.backends_[backend_index].lister_ = nullptr; info->backends_[backend_index].lister_ = nullptr;
break; 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)); emit dataChanged(index(i, 0), index(i, 0));
} else { } else {
// If this was the last lister for the device then remove it from the model // 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(); for (int backend_index = 0; backend_index < info->backends_.count();
++backend_index) { ++backend_index) {
if (info.backends_[backend_index].unique_id_ == id) { if (info->backends_[backend_index].unique_id_ == id) {
info.backends_.removeAt(backend_index); info->backends_.removeAt(backend_index);
break; break;
} }
} }
if (info.backends_.isEmpty()) { if (info->backends_.isEmpty()) {
beginRemoveRows(QModelIndex(), i, i); beginRemoveRows(QModelIndex(), i, i);
devices_.removeAt(i); devices_.removeAt(i);
@ -526,31 +430,31 @@ void DeviceManager::PhysicalDeviceChanged(const QString& id) {
} }
std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) { std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
DeviceInfo& info = devices_[row]; DeviceInfo* info = devices_[row];
if (info.device_) // Already connected if (info->device_) // Already connected
return info.device_; return info->device_;
std::shared_ptr<ConnectedDevice> ret; std::shared_ptr<ConnectedDevice> ret;
if (!info.BestBackend()->lister_) // Not physically connected if (!info->BestBackend()->lister_) // Not physically connected
return ret; return ret;
if (info.BestBackend()->lister_->DeviceNeedsMount( if (info->BestBackend()->lister_->DeviceNeedsMount(
info.BestBackend()->unique_id_)) { info->BestBackend()->unique_id_)) {
// Mount the device // Mount the device
info.BestBackend()->lister_->MountDevice(info.BestBackend()->unique_id_); info->BestBackend()->lister_->MountDevice(info->BestBackend()->unique_id_);
return ret; return ret;
} }
bool first_time = (info.database_id_ == -1); bool first_time = (info->database_id_ == -1);
if (first_time) { if (first_time) {
// We haven't stored this device in the database before // 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 // Get the device URLs
QList<QUrl> urls = info.BestBackend()->lister_->MakeDeviceUrls( QList<QUrl> urls = info->BestBackend()->lister_->MakeDeviceUrls(
info.BestBackend()->unique_id_); info->BestBackend()->unique_id_);
if (urls.isEmpty()) return ret; if (urls.isEmpty()) return ret;
// Take the first URL that we have a handler for // Take the first URL that we have a handler for
@ -605,10 +509,10 @@ std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
QMetaObject meta_object = device_classes_.value(device_url.scheme()); QMetaObject meta_object = device_classes_.value(device_url.scheme());
QObject* instance = meta_object.newInstance( QObject* instance = meta_object.newInstance(
Q_ARG(QUrl, device_url), Q_ARG(QUrl, device_url),
Q_ARG(DeviceLister*, info.BestBackend()->lister_), Q_ARG(DeviceLister*, info->BestBackend()->lister_),
Q_ARG(QString, info.BestBackend()->unique_id_), Q_ARG(QString, info->BestBackend()->unique_id_),
Q_ARG(DeviceManager*, this), Q_ARG(Application*, app_), 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<ConnectedDevice*>(instance)); ret.reset(static_cast<ConnectedDevice*>(instance));
if (!ret) { if (!ret) {
@ -616,13 +520,15 @@ std::shared_ptr<ConnectedDevice> DeviceManager::Connect(int row) {
} else { } else {
ret->Init(); ret->Init();
info.device_ = ret; info->device_ = ret;
emit dataChanged(index(row), index(row)); QModelIndex index = ItemToIndex(info);
connect(info.device_.get(), SIGNAL(TaskStarted(int)), if (!index.isValid()) return ret;
emit dataChanged(index, index);
connect(info->device_.get(), SIGNAL(TaskStarted(int)),
SLOT(DeviceTaskStarted(int))); SLOT(DeviceTaskStarted(int)));
connect(info.device_.get(), SIGNAL(SongCountUpdated(int)), connect(info->device_.get(), SIGNAL(SongCountUpdated(int)),
SLOT(DeviceSongCountUpdated(int))); SLOT(DeviceSongCountUpdated(int)));
connect(info.device_.get(), SIGNAL(ConnectFinished(const QString&, bool)), connect(info->device_.get(), SIGNAL(ConnectFinished(const QString&, bool)),
SLOT(DeviceConnectFinished(const QString&, bool))); SLOT(DeviceConnectFinished(const QString&, bool)));
ret->ConnectAsync(); ret->ConnectAsync();
} }
@ -636,44 +542,46 @@ void DeviceManager::DeviceConnectFinished(const QString& id, bool success) {
if (success) { if (success) {
emit DeviceConnected(row); emit DeviceConnected(row);
} else { } else {
devices_[row].device_.reset(); devices_[row]->device_.reset();
} }
} }
} }
std::shared_ptr<ConnectedDevice> DeviceManager::GetConnectedDevice( std::shared_ptr<ConnectedDevice> DeviceManager::GetConnectedDevice(
int row) const { int row) const {
return devices_[row].device_; return devices_[row]->device_;
} }
int DeviceManager::GetDatabaseId(int row) const { int DeviceManager::GetDatabaseId(int row) const {
return devices_[row].database_id_; return devices_[row]->database_id_;
} }
DeviceLister* DeviceManager::GetLister(int row) const { DeviceLister* DeviceManager::GetLister(int row) const {
return devices_[row].BestBackend()->lister_; return devices_[row]->BestBackend()->lister_;
} }
void DeviceManager::Disconnect(int row) { void DeviceManager::Disconnect(int row) {
DeviceInfo& info = devices_[row]; DeviceInfo* info = devices_[row];
if (!info.device_) // Already disconnected if (!info->device_) // Already disconnected
return; return;
info.device_.reset(); info->device_.reset();
emit DeviceDisconnected(row); emit DeviceDisconnected(row);
emit dataChanged(index(row), index(row)); QModelIndex index = ItemToIndex(info);
if (!index.isValid()) return;
emit dataChanged(index, index);
} }
void DeviceManager::Forget(int row) { void DeviceManager::Forget(int row) {
DeviceInfo& info = devices_[row]; DeviceInfo* info = devices_[row];
if (info.database_id_ == -1) return; if (info->database_id_ == -1) return;
if (info.device_) Disconnect(row); if (info->device_) Disconnect(row);
backend_->RemoveDevice(info.database_id_); backend_->RemoveDevice(info->database_id_);
info.database_id_ = -1; info->database_id_ = -1;
if (!info.BestBackend()->lister_) { if (!info->BestBackend()->lister_) {
// It's not attached any more so remove it from the list // It's not attached any more so remove it from the list
beginRemoveRows(QModelIndex(), row, row); beginRemoveRows(QModelIndex(), row, row);
devices_.removeAt(row); devices_.removeAt(row);
@ -689,11 +597,11 @@ void DeviceManager::Forget(int row) {
} else { } else {
// It's still attached, set the name and icon back to what they were // It's still attached, set the name and icon back to what they were
// originally // originally
const QString id = info.BestBackend()->unique_id_; const QString id = info->BestBackend()->unique_id_;
info.friendly_name_ = info.BestBackend()->lister_->MakeFriendlyName(id); info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id);
info.LoadIcon(info.BestBackend()->lister_->DeviceIcons(id), info->LoadIcon(info->BestBackend()->lister_->DeviceIcons(id),
info.friendly_name_); info->friendly_name_);
dataChanged(index(row, 0), index(row, 0)); dataChanged(index(row, 0), index(row, 0));
} }
@ -703,16 +611,16 @@ void DeviceManager::SetDeviceOptions(int row, const QString& friendly_name,
const QString& icon_name, const QString& icon_name,
MusicStorage::TranscodeMode mode, MusicStorage::TranscodeMode mode,
Song::FileType format) { Song::FileType format) {
DeviceInfo& info = devices_[row]; DeviceInfo* info = devices_[row];
info.friendly_name_ = friendly_name; info->friendly_name_ = friendly_name;
info.LoadIcon(QVariantList() << icon_name, friendly_name); info->LoadIcon(QVariantList() << icon_name, friendly_name);
info.transcode_mode_ = mode; info->transcode_mode_ = mode;
info.transcode_format_ = format; info->transcode_format_ = format;
emit dataChanged(index(row, 0), index(row, 0)); emit dataChanged(index(row, 0), index(row, 0));
if (info.database_id_ != -1) if (info->database_id_ != -1)
backend_->SetDeviceOptions(info.database_id_, friendly_name, icon_name, backend_->SetDeviceOptions(info->database_id_, friendly_name, icon_name,
mode, format); mode, format);
} }
@ -721,11 +629,13 @@ void DeviceManager::DeviceTaskStarted(int id) {
if (!device) return; if (!device) return;
for (int i = 0; i < devices_.count(); ++i) { for (int i = 0; i < devices_.count(); ++i) {
DeviceInfo& info = devices_[i]; DeviceInfo* info = devices_[i];
if (info.device_.get() == device) { if (info->device_.get() == device) {
active_tasks_[id] = index(i); QModelIndex index = ItemToIndex(info);
info.task_percentage_ = 0; if (!index.isValid()) continue;
emit dataChanged(index(i), index(i)); active_tasks_[id] = index;
info->task_percentage_ = 0;
emit dataChanged(index, index);
return; return;
} }
} }
@ -741,11 +651,11 @@ void DeviceManager::TasksChanged() {
QPersistentModelIndex index = active_tasks_[task.id]; QPersistentModelIndex index = active_tasks_[task.id];
if (!index.isValid()) continue; if (!index.isValid()) continue;
DeviceInfo& info = devices_[index.row()]; DeviceInfo* info = IndexToItem(index);
if (task.progress_max) 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 else
info.task_percentage_ = 0; info->task_percentage_ = 0;
emit dataChanged(index, index); emit dataChanged(index, index);
finished_tasks.removeAll(index); finished_tasks.removeAll(index);
} }
@ -753,8 +663,8 @@ void DeviceManager::TasksChanged() {
for (const QPersistentModelIndex& index : finished_tasks) { for (const QPersistentModelIndex& index : finished_tasks) {
if (!index.isValid()) continue; if (!index.isValid()) continue;
DeviceInfo& info = devices_[index.row()]; DeviceInfo* info = devices_[index.row()];
info.task_percentage_ = -1; info->task_percentage_ = -1;
emit dataChanged(index, index); emit dataChanged(index, index);
active_tasks_.remove(active_tasks_.key(index)); active_tasks_.remove(active_tasks_.key(index));
@ -766,13 +676,14 @@ void DeviceManager::UnmountAsync(int row) {
} }
void DeviceManager::Unmount(int row) { void DeviceManager::Unmount(int row) {
DeviceInfo& info = devices_[row]; DeviceInfo* info = devices_[row];
if (info.database_id_ != -1 && !info.device_) return; if (info->database_id_ != -1 && !info->device_) return;
if (info.device_) Disconnect(row); if (info->device_) Disconnect(row);
if (info.BestBackend()->lister_) if (info->BestBackend()->lister_)
info.BestBackend()->lister_->UnmountDevice(info.BestBackend()->unique_id_); info->BestBackend()->lister_->UnmountDevice(
info->BestBackend()->unique_id_);
} }
void DeviceManager::DeviceSongCountUpdated(int count) { void DeviceManager::DeviceSongCountUpdated(int count) {
@ -782,5 +693,13 @@ void DeviceManager::DeviceSongCountUpdated(int count) {
int row = FindDeviceById(device->unique_id()); int row = FindDeviceById(device->unique_id());
if (row == -1) return; if (row == -1) return;
emit dataChanged(index(row), index(row)); QModelIndex index = ItemToIndex(devices_[row]);
if (!index.isValid()) return;
emit dataChanged(index, index);
}
void DeviceManager::LazyPopulate(DeviceInfo* parent, bool signal) {
if (parent->lazy_loaded) return;
parent->lazy_loaded = true;
} }

View File

@ -22,20 +22,20 @@
#include <memory> #include <memory>
#include <QAbstractListModel> #include <QAbstractItemModel>
#include <QIcon> #include <QIcon>
#include <QThreadPool> #include <QThreadPool>
#include "core/simpletreemodel.h"
#include "deviceinfo.h"
#include "library/librarymodel.h" #include "library/librarymodel.h"
class Application; class Application;
class ConnectedDevice; class ConnectedDevice;
class Database;
class DeviceLister; class DeviceLister;
class DeviceStateFilterModel; class DeviceStateFilterModel;
class TaskManager;
class DeviceManager : public QAbstractListModel { class DeviceManager : public SimpleTreeModel<DeviceInfo> {
Q_OBJECT Q_OBJECT
public: public:
@ -90,9 +90,8 @@ class DeviceManager : public QAbstractListModel {
MusicStorage::TranscodeMode mode, MusicStorage::TranscodeMode mode,
Song::FileType format); Song::FileType format);
// QAbstractListModel // QAbstractItemModel
int rowCount(const QModelIndex& parent) const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex& index, int role) const;
public slots: public slots:
void Unmount(int row); void Unmount(int row);
@ -111,56 +110,11 @@ signals:
void LoadAllDevices(); void LoadAllDevices();
void DeviceConnectFinished(const QString& id, bool success); void DeviceConnectFinished(const QString& id, bool success);
protected:
void LazyPopulate(DeviceInfo* item) { LazyPopulate(item, true); }
void LazyPopulate(DeviceInfo* item, bool signal);
private: 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 Clementine
// yet.
// database_id == -1, lister valid, device null
// 3) Physically connected and connected to Clementine
// 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<ConnectedDevice>
device_; // nullptr if not connected to clementine
QList<Backend> 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); void AddLister(DeviceLister* lister);
template <typename T> template <typename T>
@ -178,7 +132,7 @@ signals:
QIcon not_connected_overlay_; QIcon not_connected_overlay_;
QList<DeviceLister*> listers_; QList<DeviceLister*> listers_;
QList<DeviceInfo> devices_; QList<DeviceInfo*> devices_;
QMultiMap<QString, QMetaObject> device_classes_; QMultiMap<QString, QMetaObject> device_classes_;

View File

@ -97,7 +97,7 @@ void DeviceProperties::ShowDevice(int row) {
ui_->transcode_format->model()->sort(0); ui_->transcode_format->model()->sort(0);
} }
index_ = manager_->index(row); index_ = manager_->index(row, 0, QModelIndex());
// Basic information // Basic information
ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString()); ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString());

View File

@ -304,7 +304,7 @@ void DeviceView::DeviceConnected(int row) {
if (!device) return; if (!device) return;
QModelIndex sort_idx = QModelIndex sort_idx =
sort_model_->mapFromSource(app_->device_manager()->index(row)); sort_model_->mapFromSource(app_->device_manager()->index(row, 0));
QSortFilterProxyModel* sort_model = QSortFilterProxyModel* sort_model =
new QSortFilterProxyModel(device->model()); new QSortFilterProxyModel(device->model());
@ -319,7 +319,7 @@ void DeviceView::DeviceConnected(int row) {
void DeviceView::DeviceDisconnected(int row) { void DeviceView::DeviceDisconnected(int row) {
merged_model_->RemoveSubModel( merged_model_->RemoveSubModel(
sort_model_->mapFromSource(app_->device_manager()->index(row))); sort_model_->mapFromSource(app_->device_manager()->index(row, 0)));
} }
void DeviceView::Forget() { void DeviceView::Forget() {

View File

@ -39,8 +39,10 @@ FilesystemDevice::FilesystemDevice(const QUrl& url, DeviceLister* lister,
watcher_thread_->start(QThread::IdlePriority); watcher_thread_->start(QThread::IdlePriority);
watcher_->set_device_name( watcher_->set_device_name(
manager->data(manager->index(manager->FindDeviceById(unique_id)), manager
DeviceManager::Role_FriendlyName).toString()); ->data(manager->index(manager->FindDeviceById(unique_id), 0),
DeviceManager::Role_FriendlyName)
.toString());
watcher_->set_backend(backend_); watcher_->set_backend(backend_);
watcher_->set_task_manager(app_->task_manager()); watcher_->set_task_manager(app_->task_manager());