From bdedc51b2dc6ef66266561fc5a273f84f76c01e3 Mon Sep 17 00:00:00 2001 From: Raine Makelainen Date: Mon, 16 Jul 2018 17:13:05 +0300 Subject: [PATCH] [systemsettings] Expose format, unmount, and mount from partitionmodel. Contributes to JB#40936 --- src/logging.cpp | 1 + src/logging_p.h | 1 + src/partition.cpp | 5 + src/partition.h | 24 ++- src/partition_p.h | 4 + src/partitionmanager.cpp | 107 +++++----- src/partitionmanager_p.h | 12 ++ src/partitionmodel.cpp | 76 +++++++ src/partitionmodel.h | 41 +++- src/src.pro | 3 + src/udisks2block.cpp | 176 +++++++++++++++++ src/udisks2block_p.h | 91 +++++++++ src/udisks2defines.h | 37 ++-- src/udisks2job.cpp | 20 +- src/udisks2job_p.h | 2 + src/udisks2monitor.cpp | 414 +++++++++++++++++++++++++++++++++------ src/udisks2monitor_p.h | 46 ++++- 17 files changed, 935 insertions(+), 125 deletions(-) create mode 100644 src/udisks2block.cpp create mode 100644 src/udisks2block_p.h diff --git a/src/logging.cpp b/src/logging.cpp index 5e0ea7f..e98d39f 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -33,3 +33,4 @@ Q_LOGGING_CATEGORY(lcVpnLog, "org.sailfishos.settings.vpn", QtWarningMsg) Q_LOGGING_CATEGORY(lcDeveloperModeLog, "org.sailfishos.settings.developermode", QtWarningMsg) +Q_LOGGING_CATEGORY(lcMemoryCardLog, "org.sailfishos.settings.memorycard", QtWarningMsg) diff --git a/src/logging_p.h b/src/logging_p.h index a789872..5f60411 100644 --- a/src/logging_p.h +++ b/src/logging_p.h @@ -36,5 +36,6 @@ Q_DECLARE_LOGGING_CATEGORY(lcVpnLog) Q_DECLARE_LOGGING_CATEGORY(lcDeveloperModeLog) +Q_DECLARE_LOGGING_CATEGORY(lcMemoryCardLog) #endif diff --git a/src/partition.cpp b/src/partition.cpp index 7b6fda1..3b5080d 100644 --- a/src/partition.cpp +++ b/src/partition.cpp @@ -101,6 +101,11 @@ QString Partition::deviceName() const return d ? d->deviceName : QString(); } +QString Partition::deviceLabel() const +{ + return d ? d->deviceLabel : QString(); +} + QString Partition::mountPath() const { return d ? d->mountPath : QString(); diff --git a/src/partition.h b/src/partition.h index e8c4bf9..7b5d969 100644 --- a/src/partition.h +++ b/src/partition.h @@ -33,6 +33,7 @@ #define PARTITION_H #include +#include #include @@ -59,11 +60,31 @@ class SYSTEMSETTINGS_EXPORT Partition Unmounted, Mounting, Mounted, - Unmounting + Unmounting, + Formatting, + Formatted }; Q_DECLARE_FLAGS(StorageTypes, StorageType) + enum Error { + ErrorFailed, + ErrorCancelled, + ErrorAlreadyCancelled, + ErrorNotAuthorized, + ErrorNotAuthorizedCanObtain, + ErrorNotAuthorizedDismissed, + ErrorAlreadyMounted, + ErrorNotMounted, + ErrorOptionNotPermitted, + ErrorMountedByOtherUser, + ErrorAlreadyUnmounting, + ErrorNotSupported, + ErrorTimedout, + ErrorWouldWakeup, + ErrorDeviceBusy + }; + Partition(); Partition(const Partition &partition); Partition &operator =(const Partition &partition); @@ -83,6 +104,7 @@ class SYSTEMSETTINGS_EXPORT Partition QString devicePath() const; QString deviceName() const; + QString deviceLabel() const; QString mountPath() const; QString filesystemType() const; diff --git a/src/partition_p.h b/src/partition_p.h index 31aaf09..c84d0af 100644 --- a/src/partition_p.h +++ b/src/partition_p.h @@ -50,6 +50,7 @@ class PartitionPrivate : public QSharedData , canMount(false) , mountFailed(false) , deviceRoot(false) + , valid(false) { } @@ -62,6 +63,7 @@ class PartitionPrivate : public QSharedData QString deviceName; QString devicePath; + QString deviceLabel; QString mountPath; QString filesystemType; QString activeState; @@ -74,6 +76,8 @@ class PartitionPrivate : public QSharedData bool canMount; bool mountFailed; bool deviceRoot; + // If valid, only mount status and available bytes will be checked + bool valid; }; #endif diff --git a/src/partitionmanager.cpp b/src/partitionmanager.cpp index 1505187..1b309d7 100644 --- a/src/partitionmanager.cpp +++ b/src/partitionmanager.cpp @@ -31,6 +31,7 @@ #include "partitionmanager_p.h" #include "udisks2monitor_p.h" +#include "logging_p.h" #include #include @@ -43,6 +44,8 @@ static const auto userName = QString(qgetenv("USER")); static const auto externalMountPath = QString("/run/media/%1/").arg(userName); +static const QRegularExpression externalMedia(QString("^%1$").arg(externalDevice)); + PartitionManagerPrivate *PartitionManagerPrivate::sharedInstance = nullptr; PartitionManagerPrivate::PartitionManagerPrivate() @@ -51,6 +54,11 @@ PartitionManagerPrivate::PartitionManagerPrivate() sharedInstance = this; m_udisksMonitor.reset(new UDisks2::Monitor(this)); + connect(m_udisksMonitor.data(), &UDisks2::Monitor::status, this, &PartitionManagerPrivate::status); + connect(m_udisksMonitor.data(), &UDisks2::Monitor::errorMessage, this, &PartitionManagerPrivate::errorMessage); + connect(m_udisksMonitor.data(), &UDisks2::Monitor::mountError, this, &PartitionManagerPrivate::mountError); + connect(m_udisksMonitor.data(), &UDisks2::Monitor::unmountError, this, &PartitionManagerPrivate::unmountError); + connect(m_udisksMonitor.data(), &UDisks2::Monitor::formatError, this, &PartitionManagerPrivate::formatError); QExplicitlySharedDataPointer root(new PartitionPrivate(this)); root->storageType = Partition::System; @@ -97,6 +105,8 @@ PartitionManagerPrivate::PartitionManagerPrivate() if (root->status == Partition::Mounted) { m_root = Partition(QExplicitlySharedDataPointer(root)); } + + m_udisksMonitor->getBlockDevices(); } PartitionManagerPrivate::~PartitionManagerPrivate() @@ -156,7 +166,6 @@ void PartitionManagerPrivate::refresh() partitionFile.readLine(); static const QRegularExpression whitespace(QStringLiteral("\\s+")); - static const QRegularExpression externalMedia(QString("^%1$").arg(externalDevice)); static const QRegularExpression deviceRoot(QStringLiteral("^mmcblk\\d+$")); while (!partitionFile.atEnd()) { @@ -234,14 +243,16 @@ void PartitionManagerPrivate::refresh(const Partitions &partitions, Partitions & for (auto partition : partitions) { // Reset properties to the unmounted defaults. If the partition is mounted these will be restored // by the refresh. - partition->status = partition->activeState == QStringLiteral("activating") - ? Partition::Mounting - : Partition::Unmounted; partition->bytesFree = 0; partition->bytesAvailable = 0; - partition->canMount = false; - partition->readOnly = true; - partition->filesystemType.clear(); + if (!partition->valid) { + partition->status = partition->activeState == QStringLiteral("activating") + ? Partition::Mounting + : Partition::Unmounted; + partition->canMount = false; + partition->readOnly = true; + partition->filesystemType.clear(); + } } FILE *mtab = setmntent("/etc/mtab", "r"); @@ -256,9 +267,11 @@ void PartitionManagerPrivate::refresh(const Partitions &partitions, Partitions & const QString mountPath = QString::fromUtf8(mountEntry.mnt_dir); const QString devicePath = QString::fromUtf8(mountEntry.mnt_fsname); + for (auto partition : partitions) { - if ((partition->status == Partition::Mounted || partition->status == Partition::Mounting) - && (partition->storageType != Partition::External || partition->mountPath.startsWith(externalMountPath))) { + if (partition->valid || ((partition->status == Partition::Mounted || partition->status == Partition::Mounting) && + (partition->storageType != Partition::External || + partition->mountPath.startsWith(externalMountPath)))) { continue; } @@ -279,21 +292,6 @@ void PartitionManagerPrivate::refresh(const Partitions &partitions, Partitions & } endmntent(mtab); - - blkid_cache cache = nullptr; - - // Query filesystems supported by this device - // Note this will only find filesystems supported either directly by the - // kernel, or by modules already loaded. - QStringList supportedFs; - QFile filesystems(QStringLiteral("/proc/filesystems")); - if (filesystems.open(QIODevice::ReadOnly)) { - QString line = filesystems.readLine(); - while (line.length() > 0) { - supportedFs << line.trimmed().split('\t').last(); - line = filesystems.readLine(); - } - } for (auto partition : partitions) { if (partition->status == Partition::Mounted) { @@ -312,33 +310,54 @@ void PartitionManagerPrivate::refresh(const Partitions &partitions, Partitions & partition->bytesFree = bytesFree; partition->bytesAvailable = bytesAvailable; } - } else if (partition->storageType == Partition::External) { - // Presume the file system can be mounted, unless we can confirm otherwise. - partition->canMount = true; + } + } +} - // If an external partition is unmounted, query the uuid to get the prospective mount path. - if (!cache && blkid_get_cache(&cache, nullptr) < 0) { - qWarning("Failed to load blkid cache"); - continue; - } +void PartitionManagerPrivate::mount(const Partition &partition) +{ + qCInfo(lcMemoryCardLog) << "Can mount:" << externalMedia.match(partition.deviceName()).hasMatch() << partition.deviceName(); + if (externalMedia.match(partition.deviceName()).hasMatch()) { + m_udisksMonitor->mount(partition.deviceName()); + } +} - // Directly probing the device would be better but requires root permissions. - if (char * const uuid = blkid_get_tag_value( - cache, "UUID", partition->devicePath.toUtf8().constData())) { - partition->mountPath = externalMountPath + QString::fromUtf8(uuid); +void PartitionManagerPrivate::unmount(const Partition &partition) +{ + qCInfo(lcMemoryCardLog) << "Can unmount:" << externalMedia.match(partition.deviceName()).hasMatch() << partition.deviceName(); + if (externalMedia.match(partition.deviceName()).hasMatch()) { + m_udisksMonitor->instance()->unmount(partition.deviceName()); + } else { + qCWarning(lcMemoryCardLog) << "Unmount allowed only for external memory cards," << partition.devicePath() << "is not allowed"; + } +} - ::free(uuid); - } +void PartitionManagerPrivate::format(const Partition &partition, const QString &type, const QString &label) +{ + qCInfo(lcMemoryCardLog) << "Can format:" << externalMedia.match(partition.deviceName()).hasMatch() << partition.deviceName(); - if (char * const type = blkid_get_tag_value( - cache, "TYPE", partition->devicePath.toUtf8().constData())) { - partition->filesystemType = QString::fromUtf8(type); - partition->canMount = !partition->filesystemType.isEmpty() && supportedFs.contains(partition->filesystemType); + if (externalMedia.match(partition.deviceName()).hasMatch()) { + m_udisksMonitor->instance()->format(partition.deviceName(), type, label); + } else { + qCWarning(lcMemoryCardLog) << "Formatting allowed only for external memory cards," << partition.devicePath() << "is not allowed"; + } +} - ::free(type); - } +QStringList PartitionManagerPrivate::supportedFileSystems() const +{ + // Query filesystems supported by this device + // Note this will only find filesystems supported either directly by the + // kernel, or by modules already loaded. + QStringList supportedFs; + QFile filesystems(QStringLiteral("/proc/filesystems")); + if (filesystems.open(QIODevice::ReadOnly)) { + QString line = filesystems.readLine(); + while (line.length() > 0) { + supportedFs << line.trimmed().split('\t').last(); + line = filesystems.readLine(); } } + return supportedFs; } PartitionManager::PartitionManager(QObject *parent) diff --git a/src/partitionmanager_p.h b/src/partitionmanager_p.h index 428b6a7..2b6d2fa 100644 --- a/src/partitionmanager_p.h +++ b/src/partitionmanager_p.h @@ -63,11 +63,23 @@ class PartitionManagerPrivate : public QObject, public QSharedData void refresh(PartitionPrivate *partition); void refresh(const Partitions &partitions, Partitions &changedPartitions); + void mount(const Partition &partition); + void unmount(const Partition &partition); + void format(const Partition &partition, const QString &type, const QString &label); + + QStringList supportedFileSystems() const; + signals: void partitionChanged(const Partition &partition); void partitionAdded(const Partition &partition); void partitionRemoved(const Partition &partition); + void status(const QString &deviceName, Partition::Status); + void errorMessage(const QString &objectPath, const QString &errorName); + void mountError(Partition::Error error); + void unmountError(Partition::Error error); + void formatError(Partition::Error error); + private: static PartitionManagerPrivate *sharedInstance; diff --git a/src/partitionmodel.cpp b/src/partitionmodel.cpp index 69789f6..d6e62cc 100644 --- a/src/partitionmodel.cpp +++ b/src/partitionmodel.cpp @@ -32,6 +32,12 @@ #include "partitionmodel.h" #include "partitionmanager_p.h" +#include "udisks2monitor_p.h" +#include "logging_p.h" + +#include +#include + PartitionModel::PartitionModel(QObject *parent) : QAbstractListModel(parent) , m_manager(PartitionManagerPrivate::instance()) @@ -42,6 +48,18 @@ PartitionModel::PartitionModel(QObject *parent) connect(m_manager.data(), &PartitionManagerPrivate::partitionChanged, this, &PartitionModel::partitionChanged); connect(m_manager.data(), &PartitionManagerPrivate::partitionAdded, this, &PartitionModel::partitionAdded); connect(m_manager.data(), &PartitionManagerPrivate::partitionRemoved, this, &PartitionModel::partitionRemoved); + + connect(m_manager.data(), &PartitionManagerPrivate::errorMessage, this, &PartitionModel::errorMessage); + + connect(m_manager.data(), &PartitionManagerPrivate::mountError, this, [this](Partition::Error error) { + emit mountError(static_cast(error)); + }); + connect(m_manager.data(), &PartitionManagerPrivate::unmountError, this, [this](Partition::Error error) { + emit unmountError(static_cast(error)); + }); + connect(m_manager.data(), &PartitionManagerPrivate::formatError, this, [this](Partition::Error error) { + emit formatError(static_cast(error)); + }); } PartitionModel::~PartitionModel() @@ -64,6 +82,24 @@ void PartitionModel::setStorageTypes(StorageTypes types) } } +QStringList PartitionModel::supportedFormatTypes() const +{ + QStringList types; + QDir dir("/sbin/"); + QStringList entries = dir.entryList(QStringList() << QString("mkfs.*")); + for (const QString &entry : entries) { + QFileInfo info(QString("/sbin/%1").arg(entry)); + if (info.exists() && info.isExecutable()) { + QStringList parts = entry.split('.'); + if (!parts.isEmpty()) { + types << parts.takeLast(); + } + } + } + + return types; +} + void PartitionModel::refresh() { m_manager->refresh(); @@ -76,6 +112,42 @@ void PartitionModel::refresh(int index) } } +void PartitionModel::mount(const QString &deviceName) +{ + qCInfo(lcMemoryCardLog) << Q_FUNC_INFO << deviceName << m_partitions.count(); + + for (const Partition &partition : m_partitions) { + if (deviceName == partition.deviceName()) { + m_manager->mount(partition); + break; + } + } +} + +void PartitionModel::unmount(const QString &deviceName) +{ + qCInfo(lcMemoryCardLog) << Q_FUNC_INFO << deviceName << m_partitions.count(); + + for (const Partition &partition : m_partitions) { + if (deviceName == partition.deviceName()) { + m_manager->unmount(partition); + break; + } + } +} + +void PartitionModel::format(const QString &deviceName, const QString &type, const QString &label) +{ + qCInfo(lcMemoryCardLog) << Q_FUNC_INFO << deviceName << type << label << m_partitions.count(); + + for (const Partition &partition : m_partitions) { + if (deviceName == partition.deviceName()) { + m_manager->format(partition, type, label); + break; + } + } +} + void PartitionModel::update() { const int count = m_partitions.count(); @@ -127,6 +199,7 @@ QHash PartitionModel::roleNames() const { MountFailedRole, "mountFailed" }, { StorageTypeRole, "storageType" }, { FilesystemTypeRole, "filesystemType" }, + { DeviceLabelRole, "deviceLabel" }, { DevicePathRole, "devicePath" }, { DeviceNameRole, "deviceName" }, { MountPathRole, "mountPath" }, @@ -164,6 +237,8 @@ QVariant PartitionModel::data(const QModelIndex &index, int role) const return partition.storageType(); case FilesystemTypeRole: return partition.filesystemType(); + case DeviceLabelRole: + return partition.deviceLabel(); case DevicePathRole: return partition.devicePath(); case DeviceNameRole: @@ -187,6 +262,7 @@ QVariant PartitionModel::data(const QModelIndex &index, int role) const void PartitionModel::partitionChanged(const Partition &partition) { for (int i = 0; i < m_partitions.count(); ++i) { + qCInfo(lcMemoryCardLog) << "partition changed:" << partition.status() << partition.mountPath();; if (m_partitions.at(i) == partition) { QModelIndex index = createIndex(i, 0); emit dataChanged(index, index); diff --git a/src/partitionmodel.h b/src/partitionmodel.h index dceb7f7..8c8eba5 100644 --- a/src/partitionmodel.h +++ b/src/partitionmodel.h @@ -44,6 +44,8 @@ class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel Q_FLAGS(StorageTypes) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(StorageTypes storageTypes READ storageTypes WRITE setStorageTypes NOTIFY storageTypesChanged) + Q_PROPERTY(QStringList supportedFormatTypes READ supportedFormatTypes CONSTANT) + public: enum { ReadOnlyRole, @@ -52,6 +54,7 @@ class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel MountFailedRole, StorageTypeRole, FilesystemTypeRole, + DeviceLabelRole, DevicePathRole, DeviceNameRole, MountPathRole, @@ -61,11 +64,14 @@ class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel PartitionModelRole }; + // For Status role enum Status { Unmounted = Partition::Unmounted, Mounting = Partition::Mounting, Mounted = Partition::Mounted, - Unmounting = Partition::Unmounting + Unmounting = Partition::Unmounting, + Formatting = Partition::Formatting, + Formatted = Partition::Formatted }; enum StorageType { @@ -75,12 +81,31 @@ class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel Mass = Partition::Mass, External = Partition::External, - ExcludeParents = Partition::ExcludeParents, + ExcludeParents = Partition::ExcludeParents, Internal = Partition::Internal, Any = Partition::Any }; + enum Error { + ErrorFailed = Partition::ErrorFailed, + ErrorCancelled = Partition::ErrorCancelled, + ErrorAlreadyCancelled = Partition::ErrorAlreadyCancelled, + ErrorNotAuthorized = Partition::ErrorNotAuthorized, + ErrorNotAuthorizedCanObtain = Partition::ErrorNotAuthorizedCanObtain, + ErrorNotAuthorizedDismissed = Partition::ErrorNotAuthorizedDismissed, + ErrorAlreadyMounted = Partition::ErrorAlreadyMounted, + ErrorNotMounted = Partition::ErrorNotMounted, + ErrorOptionNotPermitted = Partition::ErrorOptionNotPermitted, + ErrorMountedByOtherUser = Partition::ErrorMountedByOtherUser, + ErrorAlreadyUnmounting = Partition::ErrorAlreadyUnmounting, + ErrorNotSupported = Partition::ErrorNotSupported, + ErrorTimedout = Partition::ErrorTimedout, + ErrorWouldWakeup = Partition::ErrorWouldWakeup, + ErrorDeviceBusy = Partition::ErrorDeviceBusy + }; + Q_ENUM(Error) + Q_DECLARE_FLAGS(StorageTypes, StorageType) explicit PartitionModel(QObject *parent = 0); @@ -89,9 +114,15 @@ class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel StorageTypes storageTypes() const; void setStorageTypes(StorageTypes storageTypes); + QStringList supportedFormatTypes() const; + Q_INVOKABLE void refresh(); Q_INVOKABLE void refresh(int index); + Q_INVOKABLE void mount(const QString &deviceName); + Q_INVOKABLE void unmount(const QString &deviceName); + Q_INVOKABLE void format(const QString &deviceName, const QString &type, const QString &label); + QHash roleNames() const; int rowCount(const QModelIndex &parent = QModelIndex()) const; @@ -101,6 +132,11 @@ class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel void countChanged(); void storageTypesChanged(); + void errorMessage(const QString &objectPath, const QString &errorName); + void mountError(Error error); + void unmountError(Error error); + void formatError(Error error); + private: void update(); @@ -108,7 +144,6 @@ class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel void partitionAdded(const Partition &partition); void partitionRemoved(const Partition &partition); - QExplicitlySharedDataPointer m_manager; QVector m_partitions; StorageTypes m_storageTypes; diff --git a/src/src.pro b/src/src.pro index 06da1e7..07e9468 100644 --- a/src/src.pro +++ b/src/src.pro @@ -38,6 +38,7 @@ SOURCES += \ deviceinfo.cpp \ locationsettings.cpp \ timezoneinfo.cpp \ + udisks2block.cpp \ udisks2job.cpp \ udisks2monitor.cpp @@ -69,11 +70,13 @@ HEADERS += \ $$PUBLIC_HEADERS \ qdbusxml2cpp_dbus_types.h \ batterystatus_p.h \ + logging_p.h \ diskusage_p.h \ locationsettings_p.h \ logging_p.h \ partition_p.h \ partitionmanager_p.h \ + udisks2block_p.h \ udisks2defines.h \ udisks2job_p.h \ udisks2monitor_p.h diff --git a/src/udisks2block.cpp b/src/udisks2block.cpp new file mode 100644 index 0000000..a9fe6ba --- /dev/null +++ b/src/udisks2block.cpp @@ -0,0 +1,176 @@ +#include "udisks2block_p.h" +#include "udisks2defines.h" +#include "logging_p.h" + +#include +#include +#include +#include + +UDisks2::Block::Block(const QString &path, const QVariantMap &data, QObject *parent) + : QObject(parent) + , m_path(path) + , m_data(data) + , m_connection(QDBusConnection::systemBus()) +{ + if (!m_connection.connect( + UDISKS2_SERVICE, + m_path, + DBUS_OBJECT_PROPERTIES_INTERFACE, + QStringLiteral("PropertiesChanged"), + this, + SLOT(updateProperties(QDBusMessage)))) { + qCWarning(lcMemoryCardLog) << "Failed to connect to Block properties change interface" << m_path << m_connection.lastError().message(); + } + + QDBusInterface dbusPropertyInterface(UDISKS2_SERVICE, + m_path, + DBUS_OBJECT_PROPERTIES_INTERFACE, + m_connection); + QDBusPendingCall pendingCall = dbusPropertyInterface.asyncCall(DBUS_GET_ALL, UDISKS2_FILESYSTEM_INTERFACE); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, path](QDBusPendingCallWatcher *watcher) { + if (watcher->isValid() && watcher->isFinished()) { + QDBusPendingReply<> reply = *watcher; + QDBusMessage message = reply.reply(); + updateMountPoint(message.arguments().at(0)); + } else { + QDBusError error = watcher->error(); + qCWarning(lcMemoryCardLog) << "Error reading filesystem properties:" << error.name() << error.message(); + } + watcher->deleteLater(); + }); + + if (data.isEmpty()) { + pendingCall = dbusPropertyInterface.asyncCall(DBUS_GET_ALL, UDISKS2_BLOCK_INTERFACE); + watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, path](QDBusPendingCallWatcher *watcher) { + if (watcher->isValid() && watcher->isFinished()) { + QDBusPendingReply<> reply = *watcher; + QDBusMessage message = reply.reply(); + QVariantMap blockProperties = NemoDBus::demarshallArgument(message.arguments().at(0)); + qCInfo(lcMemoryCardLog) << "Block properties:" << blockProperties; + m_data = blockProperties; + emit blockUpdated(); + } else { + QDBusError error = watcher->error(); + qCWarning(lcMemoryCardLog) << "Error reading block properties:" << error.name() << error.message(); + } + watcher->deleteLater(); + }); + } +} + +UDisks2::Block::~Block() +{ +} + +QString UDisks2::Block::path() const +{ + return m_path; +} + +QString UDisks2::Block::device() const +{ + QByteArray d = m_data.value(QStringLiteral("Device")).toByteArray(); + return QString::fromLocal8Bit(d); +} + +QString UDisks2::Block::preferredDevice() const +{ + QByteArray d = m_data.value(QStringLiteral("PreferredDevice")).toByteArray(); + return QString::fromLocal8Bit(d); +} + +QString UDisks2::Block::drive() const +{ + return value(QStringLiteral("Drive")).toString(); +} + +qint64 UDisks2::Block::deviceNumber() const +{ + return value(QStringLiteral("DeviceNumber")).toLongLong(); +} + +QString UDisks2::Block::id() const +{ + return value(QStringLiteral("Id")).toString(); +} + +qint64 UDisks2::Block::size() const +{ + return value(QStringLiteral("Size")).toLongLong(); +} + +bool UDisks2::Block::isReadOnly() const +{ + return value(QStringLiteral("ReadOnly")).toBool(); +} + +QString UDisks2::Block::idType() const +{ + return value(QStringLiteral("IdType")).toString(); +} + +QString UDisks2::Block::idVersion() const +{ + return value(QStringLiteral("IdVersion")).toString(); +} + +QString UDisks2::Block::idLabel() const +{ + return value(QStringLiteral("IdLabel")).toString(); +} + +QString UDisks2::Block::idUUID() const +{ + return value(QStringLiteral("IdUUID")).toString(); +} + +QString UDisks2::Block::mountPath() const +{ + return m_mountPath; +} + +QVariant UDisks2::Block::value(const QString &key) const +{ + return NemoDBus::demarshallDBusArgument(m_data.value(key)); +} + +bool UDisks2::Block::hasData() const +{ + return !m_data.isEmpty(); +} + +void UDisks2::Block::updateProperties(const QDBusMessage &message) +{ + QList arguments = message.arguments(); + QString interface = arguments.value(0).toString(); + if (interface == UDISKS2_BLOCK_INTERFACE) { + QVariantMap changedProperties = NemoDBus::demarshallArgument(arguments.value(1)); + qCInfo(lcMemoryCardLog) << "Changed properties:" << changedProperties; + for (QMap::const_iterator i = changedProperties.begin(); i != changedProperties.end(); ++i) { + m_data.insert(i.key(), i.value()); + } + emit blockUpdated(); + } else if (interface == UDISKS2_FILESYSTEM_INTERFACE) { + updateMountPoint(arguments.value(1)); + } +} + +void UDisks2::Block::updateMountPoint(const QVariant &mountPoints) +{ + QVariantMap mountPointsMap = NemoDBus::demarshallArgument(mountPoints); + QList mountPointList = NemoDBus::demarshallArgument >(mountPointsMap.value(QStringLiteral("MountPoints"))); + m_mountPath.clear(); + + for (const QByteArray &bytes : mountPointList) { + if (bytes.startsWith("/run")) { + m_mountPath = QString::fromLocal8Bit(bytes); + break; + } + } + + qCInfo(lcMemoryCardLog) << "New file system mount points:" << mountPoints << "resolved mount path: " << m_mountPath; + emit mountPathChanged(); +} diff --git a/src/udisks2block_p.h b/src/udisks2block_p.h new file mode 100644 index 0000000..3899eec --- /dev/null +++ b/src/udisks2block_p.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 Jolla Ltd. + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +#ifndef UDISKS2_BLOCK_H +#define UDISKS2_BLOCK_H + +#include +#include +#include + +namespace UDisks2 { + +class Block : public QObject +{ + Q_OBJECT + +public: + Block(const QString &path, const QVariantMap &data, QObject *parent = nullptr); + ~Block(); + + QString path() const; + + QString device() const; + QString preferredDevice() const; + QString drive() const; + + qint64 deviceNumber() const; + QString id() const; + + qint64 size() const; + + bool isReadOnly() const; + + QString idType() const; + QString idVersion() const; + QString idLabel() const; + QString idUUID() const; + + QString mountPath() const; + + QVariant value(const QString &key) const; + + bool hasData() const; + +signals: + void blockUpdated(); + void mountPathChanged(); + +private slots: + void updateProperties(const QDBusMessage &message); + +private: + void updateMountPoint(const QVariant &mountPoints); + + QString m_path; + QVariantMap m_data; + QDBusConnection m_connection; + QString m_mountPath; +}; + +} + +#endif diff --git a/src/udisks2defines.h b/src/udisks2defines.h index 022a076..3e4c524 100644 --- a/src/udisks2defines.h +++ b/src/udisks2defines.h @@ -32,24 +32,39 @@ #ifndef UDISKS2_DEFINES #define UDISKS2_DEFINES -#define DBUS_OBJECT_MANAGER_INTERFACE QLatin1String("org.freedesktop.DBus.ObjectManager") +#define DBUS_OBJECT_MANAGER_INTERFACE QLatin1String("org.freedesktop.DBus.ObjectManager") +#define DBUS_OBJECT_PROPERTIES_INTERFACE QLatin1String("org.freedesktop.DBus.Properties") +#define DBUS_GET_ALL QLatin1String("GetAll") #define UDISKS2_SERVICE QLatin1String("org.freedesktop.UDisks2") -#define UDISKS2_PATH QLatin1String("/org/freedesktop/UDisks2") +#define UDISKS2_PATH QLatin1String("/org/freedesktop/UDisks2") -#define UDISKS2_BLOCK_INTERFACE QLatin1String("org.freedesktop.UDisks2.Block") -#define UDISKS2_PARTITION_INTERFACE QLatin1String("org.freedesktop.UDisks2.Partition") -#define UDISKS2_JOB_INTERFACE QLatin1String("org.freedesktop.UDisks2.Job") +// Interfaces +#define UDISKS2_BLOCK_INTERFACE QLatin1String("org.freedesktop.UDisks2.Block") +#define UDISKS2_FILESYSTEM_INTERFACE QLatin1String("org.freedesktop.UDisks2.Filesystem") +#define UDISKS2_PARTITION_INTERFACE QLatin1String("org.freedesktop.UDisks2.Partition") +#define UDISKS2_JOB_INTERFACE QLatin1String("org.freedesktop.UDisks2.Job") +// Jobs +#define UDISKS2_JOB_OP_FS_UNMOUNT QLatin1String("filesystem-unmount") +#define UDISKS2_JOB_OP_FS_MOUNT QLatin1String("filesystem-mount") +#define UDISKS2_JOB_OP_CLEANUP QLatin1String("cleanup") +#define UDISKS2_JOB_OF_FS_FORMAT QLatin1String("format-mkfs") + +// Job keys #define UDISKS2_JOB_KEY_OPERATION QLatin1String("Operation") -#define UDISKS2_JOB_KEY_OBJECTS QLatin1String("Objects") +#define UDISKS2_JOB_KEY_OBJECTS QLatin1String("Objects") -#define UDISKS2_JOB_OP_FS_UNMOUNT QLatin1String("filesystem-unmount") -#define UDISKS2_JOB_OP_FS_MOUNT QLatin1String("filesystem-mount") -#define UDISKS2_JOB_OP_CLEANUP QLatin1String("cleanup") +// Mount, Unmount, Format +#define UDISKS2_BLOCK_DEVICE_PATH QString("/org/freedesktop/UDisks2/block_devices/%1") +#define UDISKS2_BLOCK_FORMAT QLatin1String("Format") +#define UDISKS2_FILESYSTEM_MOUNT QLatin1String("Mount") +#define UDISKS2_FILESYSTEM_UNMOUNT QLatin1String("Unmount") // Errors -#define UDISKS2_ERROR_DEVICE_BUSY QLatin1String("org.freedesktop.UDisks2.Error.DeviceBusy") -#define UDISKS2_ERROR_TARGET_BUSY QLatin1String("target is busy") +#define UDISKS2_ERROR_DEVICE_BUSY QLatin1String("org.freedesktop.UDisks2.Error.DeviceBusy") +#define UDISKS2_ERROR_TARGET_BUSY QLatin1String("target is busy") +#define UDISKS2_ERROR_ALREADY_MOUNTED "org.freedesktop.UDisks2.Error.AlreadyMounted" +#define UDISKS2_ERROR_ALREADY_UNMOUNTING "org.freedesktop.UDisks2.Error.AlreadyUnmounting" #endif diff --git a/src/udisks2job.cpp b/src/udisks2job.cpp index c55d8f0..9c7a280 100644 --- a/src/udisks2job.cpp +++ b/src/udisks2job.cpp @@ -47,7 +47,7 @@ UDisks2::Job::Job(const QString &path, const QVariantMap &data, QObject *parent) , m_success(false) , m_connection(QDBusConnection::systemBus()) { - if (!m_connection.connect( + if (!m_path.isEmpty() && !m_connection.connect( UDISKS2_SERVICE, m_path, UDISKS2_JOB_INTERFACE, @@ -57,8 +57,6 @@ UDisks2::Job::Job(const QString &path, const QVariantMap &data, QObject *parent) qWarning("Failed to connect to Job's at path %p completed signal: %s: ", qPrintable(m_path), qPrintable(m_connection.lastError().message())); } - // TODO: Move mount / unmount via Udisks2 to PartitionManager - connect(Monitor::instance(), &Monitor::errorMessage, this, [this](const QString &objectPath, const QString &errorName) { QStringList objects = value(UDISKS2_JOB_KEY_OBJECTS).toStringList(); if (objects.contains(objectPath) && errorName == UDISKS2_ERROR_DEVICE_BUSY) { @@ -74,6 +72,14 @@ UDisks2::Job::~Job() { } +void UDisks2::Job::complete(bool success) +{ + m_completed = true; + m_success = success; + m_status = UDisks2::Job::Completed; + emit completed(success); +} + bool UDisks2::Job::isCompleted() const { return m_completed; @@ -116,6 +122,8 @@ UDisks2::Job::Operation UDisks2::Job::operation() const return Mount; } else if (operation == UDISKS2_JOB_OP_FS_UNMOUNT) { return Unmount; + } else if (operation == UDISKS2_JOB_OF_FS_FORMAT) { + return Format; } else { return Unknown; } @@ -123,12 +131,8 @@ UDisks2::Job::Operation UDisks2::Job::operation() const void UDisks2::Job::updateCompleted(bool success, const QString &message) { - m_completed = true; - m_success = success; + complete(success); m_message = message; - m_status = UDisks2::Job::Completed; - - emit completed(success); QDBusConnection systemBus = QDBusConnection::systemBus(); systemBus.disconnect( diff --git a/src/udisks2job_p.h b/src/udisks2job_p.h index 32e0ad8..16befd7 100644 --- a/src/udisks2job_p.h +++ b/src/udisks2job_p.h @@ -55,10 +55,12 @@ class Job : public QObject enum Operation { Mount, Unmount, + Format, Unknown }; Q_ENUM(Operation) + void complete(bool success); bool isCompleted() const; bool success() const; QString message() const; diff --git a/src/udisks2monitor.cpp b/src/udisks2monitor.cpp index 91fe8fe..a7cd9fd 100644 --- a/src/udisks2monitor.cpp +++ b/src/udisks2monitor.cpp @@ -30,18 +30,46 @@ */ #include "udisks2monitor_p.h" +#include "udisks2block_p.h" #include "udisks2job_p.h" #include "udisks2defines.h" +#include "nemo-dbus/dbus.h" #include "partitionmanager_p.h" +#include "logging_p.h" #include #include #include +#include #include -#include #include +struct ErrorEntry { + Partition::Error errorCode; + const char *dbusErrorName; +}; + +// These are "copied" error from udiskserror.c so that we do not link against it. +static const ErrorEntry dbus_error_entries[] = +{ + { Partition::ErrorFailed, "org.freedesktop.UDisks2.Error.Failed" }, + { Partition::ErrorCancelled, "org.freedesktop.UDisks2.Error.Cancelled" }, + { Partition::ErrorAlreadyCancelled, "org.freedesktop.UDisks2.Error.AlreadyCancelled" }, + { Partition::ErrorNotAuthorized, "org.freedesktop.UDisks2.Error.NotAuthorized" }, + { Partition::ErrorNotAuthorizedCanObtain, "org.freedesktop.UDisks2.Error.NotAuthorizedCanObtain" }, + { Partition::ErrorNotAuthorizedDismissed, "org.freedesktop.UDisks2.Error.NotAuthorizedDismissed" }, + { Partition::ErrorAlreadyMounted, UDISKS2_ERROR_ALREADY_MOUNTED }, + { Partition::ErrorNotMounted, "org.freedesktop.UDisks2.Error.NotMounted" }, + { Partition::ErrorOptionNotPermitted, "org.freedesktop.UDisks2.Error.OptionNotPermitted" }, + { Partition::ErrorMountedByOtherUser, "org.freedesktop.UDisks2.Error.MountedByOtherUser" }, + { Partition::ErrorAlreadyUnmounting, UDISKS2_ERROR_ALREADY_UNMOUNTING }, + { Partition::ErrorNotSupported, "org.freedesktop.UDisks2.Error.NotSupported" }, + { Partition::ErrorTimedout, "org.freedesktop.UDisks2.Error.Timedout" }, + { Partition::ErrorWouldWakeup, "org.freedesktop.UDisks2.Error.WouldWakeup" }, + { Partition::ErrorDeviceBusy, "org.freedesktop.UDisks2.Error.DeviceBusy" } +}; + UDisks2::Monitor *UDisks2::Monitor::sharedInstance = nullptr; UDisks2::Monitor *UDisks2::Monitor::instance() @@ -62,9 +90,15 @@ UDisks2::Monitor::Monitor(PartitionManagerPrivate *manager, QObject *parent) QDBusConnection systemBus = QDBusConnection::systemBus(); connect(systemBus.interface(), &QDBusConnectionInterface::callWithCallbackFailed, this, [this](const QDBusError &error, const QDBusMessage &call) { - if (error.name() == UDISKS2_ERROR_DEVICE_BUSY) { - emit errorMessage(call.path(), error.name()); - } + qCInfo(lcMemoryCardLog) << "===================================================="; + qCInfo(lcMemoryCardLog) << "DBus call with callback failed:" << error.message(); + qCInfo(lcMemoryCardLog) << "Name:" << error.name(); + qCInfo(lcMemoryCardLog) << "Error name" << call.errorName(); + qCInfo(lcMemoryCardLog) << "Error message:" << call.errorMessage(); + qCInfo(lcMemoryCardLog) << "Call interface:" << call.interface(); + qCInfo(lcMemoryCardLog) << "Call path:" << call.path(); + qCInfo(lcMemoryCardLog) << "===================================================="; + emit errorMessage(call.path(), error.name()); }); if (!systemBus.connect( @@ -74,7 +108,7 @@ UDisks2::Monitor::Monitor(PartitionManagerPrivate *manager, QObject *parent) QStringLiteral("InterfacesAdded"), this, SLOT(interfacesAdded(QDBusObjectPath, InterfaceAndPropertyMap)))) { - qWarning("Failed to connect to interfaces added signal: %s", qPrintable(systemBus.lastError().message())); + qCWarning(lcMemoryCardLog) << "Failed to connect to interfaces added signal:" << qPrintable(systemBus.lastError().message()); } if (!systemBus.connect( @@ -84,7 +118,7 @@ UDisks2::Monitor::Monitor(PartitionManagerPrivate *manager, QObject *parent) QStringLiteral("InterfacesRemoved"), this, SLOT(interfacesRemoved(QDBusObjectPath, QStringList)))) { - qWarning("Failed to connect to interfaces added signal: %s", qPrintable(systemBus.lastError().message())); + qCWarning(lcMemoryCardLog) << "Failed to connect to interfaces added signal:" << qPrintable(systemBus.lastError().message()); } } @@ -95,94 +129,362 @@ UDisks2::Monitor::~Monitor() m_jobsToWait.clear(); } +void UDisks2::Monitor::mount(const QString &deviceName) +{ + QVariantHash arguments; + arguments.insert(QString("fstype"), QString()); + startMountOperation(UDISKS2_FILESYSTEM_MOUNT, deviceName, arguments); +} + +void UDisks2::Monitor::unmount(const QString &deviceName) +{ + QVariantHash arguments; + arguments.insert(QString(), QString()); + startMountOperation(UDISKS2_FILESYSTEM_UNMOUNT, deviceName, arguments); +} + +void UDisks2::Monitor::format(const QString &deviceName, const QString &type, const QString &label) +{ + if (deviceName.isEmpty()) { + qCCritical(lcMemoryCardLog) << "Cannot format without device name"; + return; + } + + QStringList fsList = m_manager->supportedFileSystems(); + if (!fsList.contains(type)) { + qCWarning(lcMemoryCardLog) << "Can only format" << fsList.join(", ") << "filesystems."; + return; + } + + QVariantHash arguments; + arguments.insert(QStringLiteral("label"), QString(label)); + arguments.insert(QStringLiteral("no-block"), true); + arguments.insert(QStringLiteral("update-partition-type"), true); + + PartitionManagerPrivate::Partitions affectedPartions; + lookupPartitions(affectedPartions, QStringList() << UDISKS2_BLOCK_DEVICE_PATH.arg(deviceName)); + + for (auto partition : affectedPartions) { + if (partition->status == Partition::Mounted) { + m_operationQueue.enqueue(Operation(QString("format"), deviceName, type, arguments)); + unmount(deviceName); + return; + } + } + + doFormat(deviceName, type, arguments); +} + void UDisks2::Monitor::interfacesAdded(const QDBusObjectPath &objectPath, const InterfaceAndPropertyMap &interfaces) { - if (interfaces.contains(UDISKS2_PARTITION_INTERFACE) && externalBlockDevice(objectPath.path())) { + qCInfo(lcMemoryCardLog) << "Interface added:" << objectPath.path() << interfaces; + QString path = objectPath.path(); + if ((interfaces.contains(UDISKS2_PARTITION_INTERFACE) || interfaces.contains(UDISKS2_FILESYSTEM_INTERFACE)) && externalBlockDevice(path)) { m_manager->refresh(); - } else { - const QVariantMap dict = interfaces.value(UDISKS2_JOB_INTERFACE); - const QString operation = dict.value(UDISKS2_JOB_KEY_OPERATION, QString()).toString(); + QVariantMap dict = interfaces.value(UDISKS2_BLOCK_INTERFACE); + addBlockDevice(path, dict); + } else if (path.startsWith(QStringLiteral("/org/freedesktop/UDisks2/jobs"))) { + QVariantMap dict = interfaces.value(UDISKS2_JOB_INTERFACE); + QString operation = dict.value(UDISKS2_JOB_KEY_OPERATION, QString()).toString(); if (operation == UDISKS2_JOB_OP_FS_MOUNT || operation == UDISKS2_JOB_OP_FS_UNMOUNT || - operation == UDISKS2_JOB_OP_CLEANUP) { + operation == UDISKS2_JOB_OP_CLEANUP || + operation == UDISKS2_JOB_OF_FS_FORMAT) { UDisks2::Job *job = new UDisks2::Job(objectPath.path(), dict); - updateBlockDevState(job, true); + updatePartitionStatus(job, true); connect(job, &UDisks2::Job::completed, this, [this](bool success) { UDisks2::Job *job = qobject_cast(sender()); - updateBlockDevState(job, success); + updatePartitionStatus(job, success); }); - m_jobsToWait << job; + m_jobsToWait.insert(path, job); } } + + // object path "/org/freedesktop/UDisks2/block_devices/sda1" + // array [ + // dict entry( + // string "org.freedesktop.UDisks2.Filesystem" + + // Register monitor for this + // signal time=1521817375.168670 sender=:1.83 -> destination=(null destination) serial=199 + // path=/org/freedesktop/UDisks2/block_devices/sda1; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged + // string "org.freedesktop.UDisks2.Block" + } void UDisks2::Monitor::interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces) { Q_UNUSED(interfaces) - - // Refresh model when a job that interests us has finished or - // when block device interface has been removed. - int jobIndex = -1; - int index = 0; QString path = objectPath.path(); - for (UDisks2::Job *j : m_jobsToWait) { - if (j->path() == path) { - j->deleteLater(); - j = nullptr; - jobIndex = index; - break; + if (m_jobsToWait.contains(path)) { + UDisks2::Job *job = m_jobsToWait.take(path); + job->deleteLater(); + } else if (m_blockDevices.contains(path)) { + UDisks2::Block *block = m_blockDevices.take(path); + block->deleteLater(); + if (externalBlockDevice(path)) { + m_manager->refresh(); } - ++index; } +} - if (jobIndex >= 0) { - m_jobsToWait.removeAt(jobIndex); - } else if (externalBlockDevice(path)) { - m_manager->refresh(); +void UDisks2::Monitor::updatePartitionProperties(const UDisks2::Block *blockDevice) +{ + for (auto partition : m_manager->m_partitions) { + if (partition->devicePath == blockDevice->device()) { + QString label = blockDevice->idLabel(); + if (label.isEmpty()) { + label = blockDevice->idUUID(); + } + + qCInfo(lcMemoryCardLog) << "Update block:" << blockDevice->device() << "pref:" << blockDevice->preferredDevice(); + qCInfo(lcMemoryCardLog) << "- drive:" << blockDevice->drive() << "dNumber:" << blockDevice->deviceNumber(); + qCInfo(lcMemoryCardLog) << "- id:" << blockDevice->id() << "size:" << blockDevice->size(); + qCInfo(lcMemoryCardLog) << "- isreadonly:" << blockDevice->isReadOnly() << "idtype:" << blockDevice->idType(); + qCInfo(lcMemoryCardLog) << "- idversion" << blockDevice->idVersion() << "idlabel" << blockDevice->idLabel(); + qCInfo(lcMemoryCardLog) << "- iduuid" << blockDevice->idUUID(); + + partition->devicePath = blockDevice->device(); + partition->mountPath = blockDevice->mountPath(); + partition->deviceLabel = label; + partition->filesystemType = blockDevice->idType(); + partition->readOnly = blockDevice->isReadOnly(); + partition->canMount = blockDevice->value(QStringLiteral("HintAuto")).toBool() + && !partition->filesystemType.isEmpty() + && m_manager->supportedFileSystems().contains(partition->filesystemType); + partition->valid = true; + + m_manager->refresh(partition.data()); + } } } -void UDisks2::Monitor::updateBlockDevState(const UDisks2::Job *job, bool success) +void UDisks2::Monitor::updatePartitionStatus(const UDisks2::Job *job, bool success) { - QStringList objects = job->value(UDISKS2_JOB_KEY_OBJECTS).toStringList(); UDisks2::Job::Operation operation = job->operation(); + PartitionManagerPrivate::Partitions affectedPartions; + lookupPartitions(affectedPartions, job->value(UDISKS2_JOB_KEY_OBJECTS).toStringList()); + if (operation == UDisks2::Job::Mount || operation == UDisks2::Job::Unmount) { - for (const QString &object : objects) { - QString deviceName = object.section(QChar('/'), 5); - for (auto partition : m_manager->m_partitions) { - if (partition->deviceName == deviceName) { - if (success) { - if (job->status() == UDisks2::Job::Added) { - partition->activeState = operation == UDisks2::Job::Mount ? QStringLiteral("activating") : QStringLiteral("deactivating"); - partition->status = operation == UDisks2::Job::Mount ? Partition::Mounting : Partition::Unmounting; - } else { - // Completed busy unmount job shall stay in mounted state. - if (job->deviceBusy() && operation == UDisks2::Job::Unmount) - operation = UDisks2::Job::Mount; - - partition->activeState = operation == UDisks2::Job::Mount ? QStringLiteral("active") : QStringLiteral("inactive"); - partition->status = operation == UDisks2::Job::Mount ? Partition::Mounted : Partition::Unmounted; - } - } else { - partition->activeState = QStringLiteral("failed"); - partition->status = operation == UDisks2::Job::Mount ? Partition::Mounted : Partition::Unmounted; - } + for (auto partition : affectedPartions) { + Partition::Status oldStatus = partition->status; - partition->mountFailed = job->deviceBusy() ? false : !success; + if (success) { + if (job->status() == UDisks2::Job::Added) { + partition->activeState = operation == UDisks2::Job::Mount ? QStringLiteral("activating") : QStringLiteral("deactivating"); + partition->status = operation == UDisks2::Job::Mount ? Partition::Mounting : Partition::Unmounting; + } else { + // Completed busy unmount job shall stay in mounted state. + if (job->deviceBusy() && operation == UDisks2::Job::Unmount) + operation = UDisks2::Job::Mount; - m_manager->refresh(partition.data()); + partition->activeState = operation == UDisks2::Job::Mount ? QStringLiteral("active") : QStringLiteral("inactive"); + partition->status = operation == UDisks2::Job::Mount ? Partition::Mounted : Partition::Unmounted; } + } else { + partition->activeState = QStringLiteral("failed"); + partition->status = operation == UDisks2::Job::Mount ? Partition::Mounted : Partition::Unmounted; + } + + partition->valid = true; + partition->mountFailed = job->deviceBusy() ? false : !success; + if (oldStatus != partition->status) { + m_manager->refresh(partition.data()); + } + } + } else if (operation == UDisks2::Job::Format) { + for (auto partition : affectedPartions) { + Partition::Status oldStatus = partition->status; + if (success) { + if (job->status() == UDisks2::Job::Added) { + partition->activeState = QStringLiteral("inactive"); + partition->status = Partition::Formatting; + } else { + partition->activeState = QStringLiteral("inactive"); + partition->status = Partition::Formatted; + } + } else { + partition->activeState = QStringLiteral("failed"); + partition->status = Partition::Unmounted; + } + partition->valid = true; + if (oldStatus != partition->status) { + m_manager->refresh(partition.data()); } } } } // Used in UDisks2 InterfacesAdded / InterfacesRemoved signals. -bool UDisks2::Monitor::externalBlockDevice(const QString &objectPathStr) const +bool UDisks2::Monitor::externalBlockDevice(const QString &deviceName) const { static const QRegularExpression externalBlockDevice(QStringLiteral("^/org/freedesktop/UDisks2/block_devices/%1$").arg(externalDevice)); - return externalBlockDevice.match(objectPathStr).hasMatch(); + return externalBlockDevice.match(deviceName).hasMatch(); +} + +void UDisks2::Monitor::startMountOperation(const QString &dbusMethod, const QString &deviceName, QVariantHash arguments) +{ + + Q_ASSERT(dbusMethod == UDISKS2_FILESYSTEM_MOUNT || dbusMethod == UDISKS2_FILESYSTEM_UNMOUNT); + + if (deviceName.isEmpty()) { + qCCritical(lcMemoryCardLog) << "Cannot" << dbusMethod.toLower() << "without device name"; + return; + } + + QDBusInterface udisks2Interface(UDISKS2_SERVICE, + UDISKS2_BLOCK_DEVICE_PATH.arg(deviceName), + UDISKS2_FILESYSTEM_INTERFACE, + QDBusConnection::systemBus()); + + QDBusPendingCall pendingCall = udisks2Interface.asyncCall(dbusMethod, arguments); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, [this, deviceName, dbusMethod](QDBusPendingCallWatcher *watcher) { + if (watcher->isValid() && watcher->isFinished()) { + if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) { + emit status(deviceName, Partition::Mounted); + } else { + emit status(deviceName, Partition::Unmounted); + } + } else if (watcher->isError()) { + QDBusError error = watcher->error(); + QByteArray errorData = error.name().toLocal8Bit(); + const char *errorCStr = errorData.constData(); + + qCWarning(lcMemoryCardLog) << dbusMethod << "error:" << errorCStr; + + for (uint i = 0; i < sizeof(dbus_error_entries) / sizeof(ErrorEntry); i++) { + if (strcmp(dbus_error_entries[i].dbusErrorName, errorCStr) == 0) { + if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) { + emit mountError(dbus_error_entries[i].errorCode); + break; + } else { + emit unmountError(dbus_error_entries[i].errorCode); + break; + } + } + } + + if (strcmp(UDISKS2_ERROR_ALREADY_UNMOUNTING, errorCStr) == 0) { + // Do nothing + } else if (strcmp(UDISKS2_ERROR_ALREADY_MOUNTED, errorCStr) == 0) { + emit status(deviceName, Partition::Mounted); + } else if (dbusMethod == UDISKS2_FILESYSTEM_UNMOUNT) { + // All other errors will revert back the previous state. + emit status(deviceName, Partition::Mounted); + } else if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) { + // All other errors will revert back the previous state. + emit status(deviceName, Partition::Unmounted); + } + } + + watcher->deleteLater(); + }); + + if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) { + emit status(deviceName, Partition::Mounting); + } else { + emit status(deviceName, Partition::Unmounting); + } +} + +void UDisks2::Monitor::lookupPartitions(PartitionManagerPrivate::Partitions &affectedPartions, const QStringList &objects) +{ + for (const QString &object : objects) { + QString deviceName = object.section(QChar('/'), 5); + for (auto partition : m_manager->m_partitions) { + if (partition->deviceName == deviceName) { + affectedPartions << partition; + } + } + } +} + +void UDisks2::Monitor::addBlockDevice(const QString &path, const QVariantMap &dict) +{ + if (m_blockDevices.contains(path)) { + return; + } + + UDisks2::Block *block = new UDisks2::Block(path, dict); + m_blockDevices.insert(path, block); + if (block->hasData()) { + updatePartitionProperties(block); + } + // When e.g. partition formatted, update partition info. + connect(block, &UDisks2::Block::blockUpdated, this, [this]() { + UDisks2::Block *block = qobject_cast(sender()); + updatePartitionProperties(block); + }); + + connect(block, &UDisks2::Block::mountPathChanged, this, [this]() { + UDisks2::Block *block = qobject_cast(sender()); + + // Both updatePartitionStatus and updatePartitionProperties + // emits partition refresh => latter one is enough. + + m_manager->blockSignals(true); + QVariantMap data; + data.insert(UDISKS2_JOB_KEY_OPERATION, block->mountPath().isEmpty() ? UDISKS2_JOB_OP_FS_UNMOUNT : UDISKS2_JOB_OP_FS_MOUNT); + data.insert(UDISKS2_JOB_KEY_OBJECTS, QStringList() << block->path()); + qCInfo(lcMemoryCardLog) << "New partition status:" << data; + UDisks2::Job tmpJob(QString(), data); + tmpJob.complete(true); + updatePartitionStatus(&tmpJob, true); + m_manager->blockSignals(false); + + updatePartitionProperties(block); + + if (!m_operationQueue.isEmpty()) { + Operation op = m_operationQueue.head(); + if (op.command == QStringLiteral("format") && block->mountPath().isEmpty()) { + m_operationQueue.dequeue(); + doFormat(op.deviceName, op.type, op.arguments); + } + } + }); +} + +void UDisks2::Monitor::doFormat(const QString &deviceName, const QString &type, const QVariantHash &arguments) +{ + QDBusInterface blockDeviceInterface(UDISKS2_SERVICE, + UDISKS2_BLOCK_DEVICE_PATH.arg(deviceName), + UDISKS2_BLOCK_INTERFACE, + QDBusConnection::systemBus()); + + QDBusPendingCall pendingCall = blockDeviceInterface.asyncCall(UDISKS2_BLOCK_FORMAT, type, arguments); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + this, [this, deviceName](QDBusPendingCallWatcher *watcher) { + if (watcher->isValid() && watcher->isFinished()) { + emit status(deviceName, Partition::Formatted); + } else if (watcher->isError()) { + QDBusError error = watcher->error(); + QByteArray errorData = error.name().toLocal8Bit(); + const char *errorCStr = errorData.constData(); + qCWarning(lcMemoryCardLog) << "Format error:" << errorCStr; + + for (uint i = 0; i < sizeof(dbus_error_entries) / sizeof(ErrorEntry); i++) { + if (strcmp(dbus_error_entries[i].dbusErrorName, errorCStr) == 0) { + emit formatError(dbus_error_entries[i].errorCode); + break; + } + } + } + watcher->deleteLater(); + }); +} + +void UDisks2::Monitor::getBlockDevices() +{ + QVector partitions = m_manager->partitions(Partition::External | Partition::ExcludeParents); + for (const Partition &partition : partitions) { + QString path = UDISKS2_BLOCK_DEVICE_PATH.arg(partition.deviceName()); + QVariantMap data; + addBlockDevice(path, data); + } } diff --git a/src/udisks2monitor_p.h b/src/udisks2monitor_p.h index 33c59ad..135631c 100644 --- a/src/udisks2monitor_p.h +++ b/src/udisks2monitor_p.h @@ -35,6 +35,10 @@ #include #include #include +#include + +#include "partitionmodel.h" +#include "partitionmanager_p.h" class PartitionManagerPrivate; @@ -44,8 +48,24 @@ Q_DECLARE_METATYPE(InterfaceAndPropertyMap) namespace UDisks2 { +class Block; class Job; +struct Operation +{ + Operation(const QString &command, const QString &deviceName, const QString &type, const QVariantHash &arguments) + : command(command) + , deviceName(deviceName) + , type(type) + , arguments(arguments) + {} + + QString command; + QString deviceName; + QString type; + QVariantHash arguments; +}; + class Monitor : public QObject { Q_OBJECT @@ -55,22 +75,44 @@ class Monitor : public QObject static Monitor *instance(); + void mount(const QString &deviceName); + void unmount(const QString &deviceName); + + void format(const QString &deviceName, const QString &type, const QString &label); + + void getBlockDevices(); + signals: + void status(const QString &deviceName, Partition::Status); void errorMessage(const QString &objectPath, const QString &errorName); + void mountError(Partition::Error error); + void unmountError(Partition::Error error); + void formatError(Partition::Error error); private slots: void interfacesAdded(const QDBusObjectPath &objectPath, const InterfaceAndPropertyMap &interfaces); void interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces); private: - void updateBlockDevState(const UDisks2::Job *job, bool success); + void updatePartitionProperties(const UDisks2::Block *blockDevice); + void updatePartitionStatus(const UDisks2::Job *job, bool success); bool externalBlockDevice(const QString &objectPathStr) const; + void startMountOperation(const QString &dbusMethod, const QString &deviceName, QVariantHash arguments); + void lookupPartitions(PartitionManagerPrivate::Partitions &affectedPartions, const QStringList &objects); + + void addBlockDevice(const QString &path, const QVariantMap &dict); + + void doFormat(const QString &deviceName, const QString &type, const QVariantHash &arguments); + private: static Monitor *sharedInstance; QExplicitlySharedDataPointer m_manager; - QList m_jobsToWait; + QMap m_jobsToWait; + QMap m_blockDevices; + + QQueue m_operationQueue; }; }