From 4270bf6cd9c5a646b13dc0a5d7c250b8aa2a97e5 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Thu, 23 Jun 2016 00:20:38 +0000 Subject: [PATCH] [settings] Add C++ API for querying partition information. Contributes to JB#35430 --- rpm/nemo-qml-plugin-systemsettings.spec | 1 + src/aboutsettings.cpp | 182 ++--------- src/aboutsettings.h | 9 +- src/alarmtonemodel.h | 4 +- src/datetimesettings.h | 4 +- src/developermodesettings.h | 4 +- src/devicelockiface.h | 4 +- src/diskusage.h | 4 +- src/displaysettings.h | 4 +- src/languagemodel.h | 7 +- src/partition.cpp | 224 +++++++++++++ src/partition.h | 105 ++++++ src/partition_p.h | 162 ++++++++++ src/partitionmanager.cpp | 410 ++++++++++++++++++++++++ src/partitionmanager.h | 62 ++++ src/partitionmanager_p.h | 77 +++++ src/partitionmodel.cpp | 211 ++++++++++++ src/partitionmodel.h | 117 +++++++ src/plugin/plugin.cpp | 2 + src/profilecontrol.h | 4 +- src/src.pro | 28 +- src/systemsettingsglobal.h | 13 + 22 files changed, 1465 insertions(+), 173 deletions(-) create mode 100644 src/partition.cpp create mode 100644 src/partition.h create mode 100644 src/partition_p.h create mode 100644 src/partitionmanager.cpp create mode 100644 src/partitionmanager.h create mode 100644 src/partitionmanager_p.h create mode 100644 src/partitionmodel.cpp create mode 100644 src/partitionmodel.h create mode 100644 src/systemsettingsglobal.h diff --git a/rpm/nemo-qml-plugin-systemsettings.spec b/rpm/nemo-qml-plugin-systemsettings.spec index 66a24af..07797d8 100644 --- a/rpm/nemo-qml-plugin-systemsettings.spec +++ b/rpm/nemo-qml-plugin-systemsettings.spec @@ -17,6 +17,7 @@ BuildRequires: pkgconfig(mce) BuildRequires: pkgconfig(mlite5) BuildRequires: pkgconfig(usb-moded-qt5) BuildRequires: pkgconfig(libshadowutils) +BuildRequires: pkgconfig(blkid) %description %{summary}. diff --git a/src/aboutsettings.cpp b/src/aboutsettings.cpp index 68681a8..1e1a192 100644 --- a/src/aboutsettings.cpp +++ b/src/aboutsettings.cpp @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -44,9 +43,6 @@ #include #include -#include - - namespace { @@ -119,72 +115,10 @@ void parseReleaseFile(const QString &filename, QMap *result) } } -struct StorageInfo { - StorageInfo() - : partitionSize(0) - , availableDiskSpace(0) - , totalDiskSpace(0) - , external(false) - , mounted(false) - { } - - QString mountPath; - QString devicePath; - QString filesystem; - quint64 partitionSize; - qlonglong availableDiskSpace; - qlonglong totalDiskSpace; - bool external; - bool mounted; -}; - - -QMap parseExternalPartitions() -{ - QMap devices; - - QFile partitions(QStringLiteral("/proc/partitions")); - if (!partitions.open(QIODevice::ReadOnly)) - return devices; - - // Read file headers - partitions.readLine(); - partitions.readLine(); - - while (!partitions.atEnd()) { - QByteArray line = partitions.readLine().trimmed(); - - int nameIndex = line.lastIndexOf(' '); - if (nameIndex <= 0) - continue; - - int sizeIndex = line.lastIndexOf(' ', nameIndex - 1); - if (sizeIndex == -1) - continue; - - QByteArray size = line.mid(sizeIndex+1, nameIndex - sizeIndex - 1); - QByteArray name = line.mid(nameIndex+1); - - if (name.startsWith("mmcblk1")) { - // If adding a partition remove the whole device. - devices.remove(QStringLiteral("/dev/mmcblk1")); - - StorageInfo info; - info.devicePath = QStringLiteral("/dev/") + QString::fromLatin1(name); - info.partitionSize = size.toULongLong() * 1024; - info.external = true; - - devices.insert(info.devicePath, info); - } - } - - return devices; -} - } AboutSettings::AboutSettings(QObject *parent) -: QObject(parent), m_sysinfo(new QStorageInfo(this)), m_netinfo(new QNetworkInfo(this)), +: QObject(parent), m_netinfo(new QNetworkInfo(this)), m_devinfo(new QDeviceInfo(this)) { QSettings settings(QStringLiteral("/mnt/vendor_data/vendor-data.ini"), QSettings::IniFormat); @@ -200,12 +134,12 @@ AboutSettings::~AboutSettings() qlonglong AboutSettings::totalDiskSpace() const { - return m_sysinfo->totalDiskSpace("/"); + return m_partitionManager.root().bytesTotal(); } qlonglong AboutSettings::availableDiskSpace() const { - return m_sysinfo->availableDiskSpace("/"); + return m_partitionManager.root().bytesAvailable(); } QVariant AboutSettings::diskUsageModel() const @@ -282,98 +216,38 @@ QString AboutSettings::vendorVersion() const void AboutSettings::refreshStorageModels() { - // Optional mountpoints that we want to report disk usage for - QStringList candidates; - candidates << QStringLiteral("/home"); - - QMap devices = parseExternalPartitions(); - - FILE *fsd = setmntent(_PATH_MOUNTED, "r"); - struct mntent entry; - char buffer[3*PATH_MAX]; - while ((getmntent_r(fsd, &entry, buffer, sizeof(buffer))) != NULL) { - StorageInfo info; - info.mountPath = QString::fromLatin1(entry.mnt_dir); - info.devicePath = QString::fromLatin1(entry.mnt_fsname); - info.filesystem = QString::fromLatin1(entry.mnt_type); - - bool addInfo = false; - if (info.mountPath == QLatin1String("/") && info.devicePath.startsWith(QLatin1Char('/'))) { - // Always report rootfs, replacing other mounts from same device - addInfo = true; - } else if (!devices.contains(info.devicePath) || devices.value(info.devicePath).external) { - // Optional candidates and external storage - if (candidates.contains(info.mountPath)) { - addInfo = true; - } else if (info.devicePath.startsWith(QLatin1String("/dev/mmcblk1"))) { - info.external = true; - addInfo = true; - } - } - - if (addInfo) { - info.mounted = !info.external || !info.mountPath.isEmpty(); - info.availableDiskSpace = info.mounted ? m_sysinfo->availableDiskSpace(info.mountPath) : 0; - info.totalDiskSpace = info.mounted ? m_sysinfo->totalDiskSpace(info.mountPath) : info.partitionSize; - - bool ignoreDuplicateEntry = false; - if (info.external) { - for (QMap::Iterator it = devices.begin(); it != devices.end(); it++) { - const StorageInfo &currInfo = it.value(); - if (info.external && info.totalDiskSpace == currInfo.totalDiskSpace) { - const QString &currMountPath = currInfo.mountPath; - - // it appears the same device has been mounted under multiple paths, so ignore - // the one with the longer device path, assuming it's the extraneous entry - if (currMountPath.indexOf(info.mountPath) >= 0) { - // remove the duplicate and keep this entry - devices.erase(it); - break; - } else if (info.mountPath.indexOf(currMountPath) >= 0) { - // ignore this entry - ignoreDuplicateEntry = true; - break; - } - } - } - } - if (!ignoreDuplicateEntry) { - devices.insert(info.devicePath, info); - } - } - } - endmntent(fsd); - m_internalStorage.clear(); m_externalStorage.clear(); - int internalPartitionCount = 0; - foreach (const StorageInfo &info, devices) { - if (!info.external) { - internalPartitionCount++; - } - } + m_partitionManager.refresh(); - foreach (const StorageInfo &info, devices) { + for (auto partition : m_partitionManager.partitions()) { QVariantMap row; - row[QStringLiteral("mounted")] = info.mounted; - row[QStringLiteral("path")] = info.mountPath; - row[QStringLiteral("available")] = info.availableDiskSpace; - row[QStringLiteral("total")] = info.totalDiskSpace; - row[QStringLiteral("filesystem")] = info.filesystem; - row[QStringLiteral("devicePath")] = info.devicePath; - - if (!info.external) { - row[QStringLiteral("storageType")] = internalPartitionCount == 1 ? QStringLiteral("mass") - : info.mountPath == QLatin1String("/") ? QStringLiteral("system") - : QStringLiteral("user"); - - - m_internalStorage << QVariant(row); - } else { - row[QStringLiteral("storageType")] = QStringLiteral("card"); + row[QStringLiteral("mounted")] = partition.status() == Partition::Mounted; + row[QStringLiteral("path")] = partition.mountPath(); + row[QStringLiteral("available")] = partition.bytesAvailable(); + row[QStringLiteral("total")] = partition.bytesTotal(); + row[QStringLiteral("filesystem")] = partition.filesystemType(); + row[QStringLiteral("devicePath")] = partition.devicePath(); + row[QStringLiteral("storageType")] = [&partition]() { + switch (partition.storageType()) { + case Partition::System: + return QStringLiteral("system"); + case Partition::User: + return QStringLiteral("user"); + case Partition::Mass: + return QStringLiteral("mass"); + case Partition::External: + return QStringLiteral("card"); + default: + return QString(); + } + }(); + if (partition.storageType() == Partition::External) { m_externalStorage << QVariant(row); + } else { + m_internalStorage << QVariant(row); } } diff --git a/src/aboutsettings.h b/src/aboutsettings.h index 530f277..8e327e2 100644 --- a/src/aboutsettings.h +++ b/src/aboutsettings.h @@ -35,10 +35,12 @@ #include #include -class QStorageInfo; +#include +#include + class QNetworkInfo; class QDeviceInfo; -class AboutSettings: public QObject +class SYSTEMSETTINGS_EXPORT AboutSettings: public QObject { Q_OBJECT @@ -90,12 +92,13 @@ class AboutSettings: public QObject void storageChanged(); private: - QStorageInfo *m_sysinfo; QNetworkInfo *m_netinfo; QDeviceInfo *m_devinfo; QVariantList m_internalStorage; QVariantList m_externalStorage; + PartitionManager m_partitionManager; + mutable QMap m_osRelease; mutable QMap m_hardwareRelease; diff --git a/src/alarmtonemodel.h b/src/alarmtonemodel.h index 8ad94d8..4487320 100644 --- a/src/alarmtonemodel.h +++ b/src/alarmtonemodel.h @@ -36,7 +36,9 @@ #include #include -class AlarmToneModel +#include + +class SYSTEMSETTINGS_EXPORT AlarmToneModel : public QAbstractListModel { Q_OBJECT diff --git a/src/datetimesettings.h b/src/datetimesettings.h index 11efb9f..6ebecd7 100644 --- a/src/datetimesettings.h +++ b/src/datetimesettings.h @@ -38,7 +38,9 @@ #include #include -class DateTimeSettings: public QObject +#include + +class SYSTEMSETTINGS_EXPORT DateTimeSettings: public QObject { Q_OBJECT diff --git a/src/developermodesettings.h b/src/developermodesettings.h index 2f76195..e58285a 100644 --- a/src/developermodesettings.h +++ b/src/developermodesettings.h @@ -40,9 +40,11 @@ #include #include +#include + class DeveloperModeSettingsWorker; -class DeveloperModeSettings : public QObject +class SYSTEMSETTINGS_EXPORT DeveloperModeSettings : public QObject { Q_OBJECT Q_ENUMS(Status) diff --git a/src/devicelockiface.h b/src/devicelockiface.h index 7b027a0..4617ed9 100644 --- a/src/devicelockiface.h +++ b/src/devicelockiface.h @@ -36,7 +36,9 @@ #include #include -class DeviceLockInterface : public QObject +#include + +class SYSTEMSETTINGS_EXPORT DeviceLockInterface : public QObject { Q_OBJECT Q_ENUMS(ResetMode) diff --git a/src/diskusage.h b/src/diskusage.h index 61aca4f..de102d8 100644 --- a/src/diskusage.h +++ b/src/diskusage.h @@ -38,9 +38,11 @@ #include #include +#include + class DiskUsagePrivate; -class DiskUsage : public QObject +class SYSTEMSETTINGS_EXPORT DiskUsage : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(DiskUsage) diff --git a/src/displaysettings.h b/src/displaysettings.h index 76e90bc..12af5f2 100644 --- a/src/displaysettings.h +++ b/src/displaysettings.h @@ -40,7 +40,9 @@ class ComNokiaMceSignalInterface; class QDBusVariant; class MGConfItem; -class DisplaySettings: public QObject +#include + +class SYSTEMSETTINGS_EXPORT DisplaySettings: public QObject { Q_OBJECT diff --git a/src/languagemodel.h b/src/languagemodel.h index c3512d6..d7f91b4 100644 --- a/src/languagemodel.h +++ b/src/languagemodel.h @@ -35,7 +35,10 @@ #include #include -class Language { + +#include + +class SYSTEMSETTINGS_EXPORT Language { public: Language(QString name, QString localeCode, QString region, QString regionLabel); QString name() const; @@ -50,7 +53,7 @@ class Language { QString m_regionLabel; }; -class LanguageModel: public QAbstractListModel +class SYSTEMSETTINGS_EXPORT LanguageModel: public QAbstractListModel { Q_OBJECT Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged) diff --git a/src/partition.cpp b/src/partition.cpp new file mode 100644 index 0000000..d8653d6 --- /dev/null +++ b/src/partition.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2016 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." + */ + +#include "partition_p.h" +#include "partitionmanager_p.h" + +static const auto systemdService = QStringLiteral("org.freedesktop.systemd1"); +static const auto systemdPath = QStringLiteral("/org/freedesktop/systemd1"); +static const auto propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); +static const auto unitInterface = QStringLiteral("org.freedesktop.systemd1.Unit"); +static const auto managerInterface = QStringLiteral("org.freedesktop.systemd1.Manager"); +static const auto activeStateProperty = QStringLiteral("ActiveState"); + +void PartitionPrivate::getUnit() +{ + if (storageType != Partition::External) { + return; + } + + const QString serviceName = QStringLiteral("mount-sd@") + deviceName + QStringLiteral(".service"); + + Q_ASSERT(!pendingCall); + Q_ASSERT(unitPath.isEmpty()); + + pendingCall = DBus::call( + this, + systemdPath, + managerInterface, + QStringLiteral("GetUnit"), + serviceName, + [this](const QDBusObjectPath &path) { + pendingCall = nullptr; + + setUnitPath(path.path()); + }, [this](const QDBusError &error) { + pendingCall = nullptr; + + qWarning("Failed to query the unit path of %s: %s", qPrintable(devicePath), qPrintable(error.message())); + }); +} + +void PartitionPrivate::setUnitPath(const QString &path) +{ + unitPath = path; + + QDBusConnection systemBus = QDBusConnection::systemBus(); + + if (!systemBus.connect( + systemdService, + unitPath, + propertiesInterface, + QStringLiteral("PropertiesChanged"), + this, + SLOT(propertiesChanged(QString,QVariantMap,QStringList)))) { + qWarning("Failed to connect to unit properties changed signal: %s", qPrintable(systemBus.lastError().message())); + } + + updateState(); +} + +void PartitionPrivate::updateState() +{ + if (unitPath.isEmpty()) { + return; + } + + delete pendingCall; + + pendingCall = DBus::call( + this, + unitPath, + propertiesInterface, + QStringLiteral("Get"), + unitInterface, + activeStateProperty, + [this](const QVariant &state) { + pendingCall = nullptr; + + activeState = state.toString(); + mountFailed = activeState == QStringLiteral("failed"); + + if (manager) { + manager->refresh(this); + } + }, [this](const QDBusError &error) { + pendingCall = nullptr; + + qWarning("Failed to query the active state of %s: %s", qPrintable(devicePath), qPrintable(error.message())); + }); +} + +void PartitionPrivate::propertiesChanged( + const QString &, const QVariantMap &, const QStringList &invalidated) +{ + if (invalidated.contains(activeStateProperty)) { + updateState(); + } +} + +Partition::Partition() +{ +} + +Partition::Partition(const Partition &partition) + : d(partition.d) +{ +} + +Partition::Partition(const QExplicitlySharedDataPointer &d) + : d(d) +{ +} + +Partition &Partition::operator =(const Partition &partition) +{ + d = partition.d; + return *this; +} + +Partition::~Partition() +{ +} + +bool Partition::operator ==(const Partition &partition) const +{ + return d == partition.d; +} + +bool Partition::operator !=(const Partition &partition) const +{ + return d != partition.d; +} + +bool Partition::isReadOnly() const +{ + return !d || d->readOnly; +} + +Partition::Status Partition::status() const +{ + return d ? d->status : Partition::Unmounted; +} + +bool Partition::canMount() const +{ + return d && d->canMount; +} + +bool Partition::mountFailed() const +{ + return d && d->mountFailed; +} + +Partition::StorageType Partition::storageType() const +{ + return d ? d->storageType : Invalid; +} + +QString Partition::devicePath() const +{ + return d ? d->devicePath : QString(); +} + +QString Partition::mountPath() const +{ + return d ? d->mountPath : QString(); +} + +QString Partition::filesystemType() const +{ + return d ? d->filesystemType : QString(); +} + +qint64 Partition::bytesAvailable() const +{ + return d ? d->bytesAvailable : 0; +} + +qint64 Partition::bytesTotal() const +{ + return d ? d->bytesTotal : 0; +} + +qint64 Partition::bytesFree() const +{ + return d ? d->bytesFree : 0; +} + +void Partition::refresh() +{ + if (const auto manager = d ? d->manager : nullptr) { + manager->refresh(d.data()); + + emit manager->partitionChanged(*this); + } +} diff --git a/src/partition.h b/src/partition.h new file mode 100644 index 0000000..0f0b81c --- /dev/null +++ b/src/partition.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2016 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 PARTITION_H +#define PARTITION_H + +#include + +#include + +class PartitionPrivate; + +class SYSTEMSETTINGS_EXPORT Partition +{ +public: + enum StorageType + { + Invalid = 0x00, + System = 0x01, + User = 0x02, + Mass = 0x04, + External = 0x08, + + ExcludeParents = 0x1000, + + Internal = System | User | Mass, + Any = System | User | Mass | External + }; + + enum Status { + Unmounted, + Mounting, + Mounted, + Unmounting + }; + + Q_DECLARE_FLAGS(StorageTypes, StorageType) + + Partition(); + Partition(const Partition &partition); + Partition &operator =(const Partition &partition); + ~Partition(); + + bool operator ==(const Partition &partition) const; + bool operator !=(const Partition &partition) const; + + bool isReadOnly() const; + + Status status() const; + + bool canMount() const; + bool mountFailed() const; + + StorageType storageType() const; + + QString devicePath() const; + QString mountPath() const; + + QString filesystemType() const; + + qint64 bytesAvailable() const; + qint64 bytesTotal() const; + qint64 bytesFree() const; + + void refresh(); + +private: + friend class PartitionManagerPrivate; + + explicit Partition(const QExplicitlySharedDataPointer &d); + + QExplicitlySharedDataPointer d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Partition::StorageTypes) + +#endif diff --git a/src/partition_p.h b/src/partition_p.h new file mode 100644 index 0000000..24c4203 --- /dev/null +++ b/src/partition_p.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016 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 PARTITION_P_H +#define PARTITION_P_H + +#include "partition.h" + +#include +#include +#include + +class PartitionManagerPrivate; + +class PartitionPrivate : public QObject, public QSharedData +{ + Q_OBJECT +public: + PartitionPrivate(PartitionManagerPrivate *manager) + : manager(manager) + , pendingCall(nullptr) + , bytesAvailable(0) + , bytesTotal(0) + , bytesFree(0) + , storageType(Partition::Invalid) + , status(Partition::Unmounted) + , readOnly(true) + , canMount(false) + , mountFailed(false) + , deviceRoot(false) + { + } + +public slots: + void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated); + +public: + void getUnit(); + void setUnitPath(const QString &path); + void updateState(); + bool isParent(const QExplicitlySharedDataPointer &child) const { + return (deviceRoot && child->deviceName.startsWith(deviceName + QLatin1Char('p'))); + } + + PartitionManagerPrivate *manager; + QDBusPendingCallWatcher *pendingCall; + + QString unitPath; + QString deviceName; + QString devicePath; + QString mountPath; + QString filesystemType; + QString activeState; + qint64 bytesAvailable; + qint64 bytesTotal; + qint64 bytesFree; + Partition::StorageType storageType; + Partition::Status status; + bool readOnly; + bool canMount; + bool mountFailed; + bool deviceRoot; +}; + +namespace DBus { + +inline QVariantList packArguments() +{ + return QVariantList(); +} + +template inline QVariantList packArguments(const Argument &argument) +{ + return QVariantList() << QVariant::fromValue(argument); +} + +// This should be varidic to support an arbitrary number of arguments, but I seem to be missing +// something obvious and hitting type deduction failures. +template inline QVariantList packArguments(const Argument0 argument0, const Argument1 &argument1) +{ + return QVariantList() << QVariant::fromValue(argument0) << QVariant::fromValue(argument1); +} + +template +QDBusPendingCallWatcher *call( + QObject *parent, + const QString &path, + const QString &interface, + const QString &method, + const Arguments&... arguments, + const Finished &onFinished, + const Error &onError) +{ + QDBusMessage message = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.systemd1"), path, interface, method); + message.setArguments(packArguments(arguments...)); + + QDBusConnection systemBus = QDBusConnection::systemBus(); + + QDBusPendingCall call = systemBus.asyncCall(message); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, parent); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [onFinished, onError](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + + QDBusPendingReply reply = *watcher; + + if (reply.isError()) { + onError(reply.error()); + } else { + onFinished(reply.value()); + } + }); + + return watcher; +} + +template +void invoke( + const QString &path, + const QString &interface, + const QString &method, + const Arguments&... arguments) +{ + QDBusMessage message = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.systemd1"), path, interface, method); + message.setArguments(packArguments(arguments...)); + + QDBusConnection::systemBus().asyncCall(message); +} + +} + +#endif diff --git a/src/partitionmanager.cpp b/src/partitionmanager.cpp new file mode 100644 index 0000000..d0f8874 --- /dev/null +++ b/src/partitionmanager.cpp @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2016 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." + */ + +#include "partitionmanager_p.h" + +#include +#include + +#include +#include +#include +#include + +static const auto systemdService = QStringLiteral("org.freedesktop.systemd1"); +static const auto systemdPath = QStringLiteral("/org/freedesktop/systemd1"); +static const auto managerInterface = QStringLiteral("org.freedesktop.systemd1.Manager"); +static const auto sdcardMountPath = QStringLiteral("/media/sdcard/"); + +PartitionManagerPrivate *PartitionManagerPrivate::sharedInstance = nullptr; + +PartitionManagerPrivate::PartitionManagerPrivate() +{ + Q_ASSERT(!sharedInstance); + + sharedInstance = this; + + QExplicitlySharedDataPointer root(new PartitionPrivate(this)); + root->storageType = Partition::System; + root->mountPath = QStringLiteral("/"); + + m_partitions.append(root); + + QExplicitlySharedDataPointer home(new PartitionPrivate(this)); + home->storageType = Partition::User; + home->mountPath = QStringLiteral("/home"); + + m_partitions.append(home); + + refresh(); + + // Remove any prospective internal partitions that aren't mounted. + int internalPartitionCount = 0; + for (Partitions::iterator it = m_partitions.begin(); it != m_partitions.end();) { + auto partition = *it; + + if (partition->storageType & Partition::Internal) { + if (partition->status == Partition::Mounted) { + internalPartitionCount += 1; + } else { + it = m_partitions.erase(it); + continue; + } + } + + ++it; + } + + if (internalPartitionCount == 1) { + root->storageType = Partition::Mass; + } + + QDBusConnection systemBus = QDBusConnection::systemBus(); + + if (!systemBus.connect( + systemdService, + systemdPath, + managerInterface, + QStringLiteral("UnitNew"), + this, + SLOT(newUnit(QString,QDBusObjectPath)))) { + qWarning("Failed to connect to new unit signal: %s", qPrintable(systemBus.lastError().message())); + } + + if (!systemBus.connect( + systemdService, + systemdPath, + managerInterface, + QStringLiteral("UnitRemoved"), + this, + SLOT(removedUnit(QString,QDBusObjectPath)))) { + qWarning("Failed to connect to removed unit signal: %s", qPrintable(systemBus.lastError().message())); + } + + if (root->status == Partition::Mounted) { + m_root = Partition(QExplicitlySharedDataPointer(root)); + } +} + +PartitionManagerPrivate::~PartitionManagerPrivate() +{ + sharedInstance = nullptr; + + for (auto partition : m_partitions) { + partition->manager = nullptr; + } +} + +PartitionManagerPrivate *PartitionManagerPrivate::instance() +{ + return sharedInstance ? sharedInstance : new PartitionManagerPrivate; +} + +Partition PartitionManagerPrivate::root() const +{ + return m_root; +} + +QVector PartitionManagerPrivate::partitions(const Partition::StorageTypes types) const +{ + QVector partitions; + + for (const auto partition : m_partitions) { + if (partition->storageType & types) { + if ((types & Partition::ExcludeParents) + && !partitions.isEmpty() + && partitions.last().d->isParent(partition)) { + partitions.last() = Partition(partition); + } else { + partitions.append(Partition(partition)); + } + } + } + + return partitions; +} + +void PartitionManagerPrivate::refresh() +{ + int index; + for (index = 0; index < m_partitions.count(); ++index) { + if (m_partitions.at(index)->storageType == Partition::External) { + break; + } + } + + Partitions addedPartitions; + Partitions changedPartitions; + + QFile partitionFile(QStringLiteral("/proc/partitions")); + if (partitionFile.open(QIODevice::ReadOnly)) { + // Read headers. + partitionFile.readLine(); + partitionFile.readLine(); + + static const QRegularExpression whitespace(QStringLiteral("\\s+")); + static const QRegularExpression externalMedia(QStringLiteral("^mmcblk(?!0)\\d+(?:p\\d+$)?")); + static const QRegularExpression deviceRoot(QStringLiteral("^mmcblk\\d+$")); + + while (!partitionFile.atEnd()) { + QStringList line = QString::fromUtf8(partitionFile.readLine()).split(whitespace, QString::SkipEmptyParts); + + if (line.count() != 4) { + continue; + } + + const QString deviceName = line.at(3); + + if (!externalMedia.match(deviceName).hasMatch()) { + continue; + } + + const auto partition = [&]() { + for (int i = index; i < m_partitions.count(); ++i) { + const auto partition = m_partitions.at(i); + if (partition->deviceName == deviceName) { + if (index != i) { + m_partitions.removeAt(i); + m_partitions.insert(index, partition); + } + + changedPartitions.append(partition); + + return partition; + } + } + QExplicitlySharedDataPointer partition(new PartitionPrivate(this)); + partition->storageType = Partition::External; + partition->deviceName = deviceName; + partition->devicePath = QStringLiteral("/dev/") + deviceName; + partition->deviceRoot = deviceRoot.match(deviceName).hasMatch(); + + m_partitions.insert(index, partition); + addedPartitions.append(partition); + + return partition; + }(); + + partition->bytesTotal = line.at(2).toInt() * 1024; + + ++index; + } + } + + const auto removedPartitions = m_partitions.mid(index); + m_partitions.resize(index); + + refresh(m_partitions); + + for (const auto partition : removedPartitions) { + emit partitionRemoved(Partition(partition)); + } + + for (const auto partition : addedPartitions) { + if (partition->storageType == Partition::External) { + partition->getUnit(); + } + emit partitionAdded(Partition(partition)); + } + + for (const auto partition : changedPartitions) { + emit partitionChanged(Partition(partition)); + } +} + +void PartitionManagerPrivate::refresh(PartitionPrivate *partition) +{ + refresh(Partitions() << QExplicitlySharedDataPointer(partition)); + + emit partitionChanged(Partition(QExplicitlySharedDataPointer(partition))); +} + +void PartitionManagerPrivate::refresh(const 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(); + } + + FILE *mtab = setmntent("/etc/mtab", "r"); + mntent mountEntry; + char buffer[3 * PATH_MAX]; + + while (getmntent_r(mtab, &mountEntry, buffer, sizeof(buffer))) { + if (!mountEntry.mnt_fsname || !mountEntry.mnt_dir) { + continue; + } + + 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(sdcardMountPath))) { + continue; + } + + if (((partition->storageType & Partition::Internal) + && partition->mountPath == mountPath + && devicePath.startsWith(QLatin1Char('/'))) + || (partition->storageType == Partition::External + && partition->devicePath == devicePath)) { + partition->mountPath = mountPath; + partition->devicePath = devicePath; + partition->filesystemType = QString::fromUtf8(mountEntry.mnt_type); + partition->status = partition->activeState == QStringLiteral("deactivating") + ? Partition::Unmounting + : Partition::Mounted; + partition->canMount = true; + } + } + } + + endmntent(mtab); + + blkid_cache cache = nullptr; + + for (auto partition : partitions) { + if (partition->status == Partition::Mounted) { + struct statvfs64 stat; + + if (::statvfs64(partition->mountPath.toUtf8().constData(), &stat) == 0) { + partition->bytesTotal = stat.f_blocks * stat.f_frsize; + partition->bytesFree = stat.f_bfree * stat.f_frsize; + partition->bytesAvailable = stat.f_bavail * stat.f_frsize; + partition->readOnly = (stat.f_flag & ST_RDONLY) != 0; + } + } 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; + } + + // 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 = sdcardMountPath + QString::fromUtf8(uuid); + + ::free(uuid); + } + + if (char * const type = blkid_get_tag_value( + cache, "TYPE", partition->devicePath.toUtf8().constData())) { + partition->filesystemType = QString::fromUtf8(type); + partition->canMount = !partition->filesystemType.isEmpty(); + + ::free(type); + } + } + } +} + +static const QString deviceNameFromSystemdService(const QString &serviceName) +{ + // strip mount-sd@ (9) from the front, and .service (8) from the back to get the device name. + return serviceName.mid(9, serviceName.length() - 17); +} + +void PartitionManagerPrivate::newUnit(const QString &serviceName, const QDBusObjectPath &) +{ + if (!serviceName.startsWith(QStringLiteral("mount-sd@"))) { + return; + } + + const QString deviceName = deviceNameFromSystemdService(serviceName); + + for (auto partition : m_partitions) { + if (partition->deviceName == deviceName) { + return; + } + } + + refresh(); +} + +void PartitionManagerPrivate::removedUnit(const QString &serviceName, const QDBusObjectPath &) +{ + if (!serviceName.startsWith(QStringLiteral("mount-sd@"))) { + return; + } + + const QString deviceName = deviceNameFromSystemdService(serviceName); + + for (Partitions::iterator it = m_partitions.begin(); it != m_partitions.end(); ++it) { + const auto partition = *it; + + if (partition->deviceName == deviceName) { + refresh(); + + return; + } + } +} + +PartitionManager::PartitionManager(QObject *parent) + : QObject(parent) + , d(PartitionManagerPrivate::instance()) +{ + connect(d.data(), &PartitionManagerPrivate::partitionChanged, this, &PartitionManager::partitionChanged); + connect(d.data(), &PartitionManagerPrivate::partitionAdded, this, &PartitionManager::partitionAdded); + connect(d.data(), &PartitionManagerPrivate::partitionRemoved, this, &PartitionManager::partitionRemoved); +} + +PartitionManager::~PartitionManager() +{ +} + +Partition PartitionManager::root() const +{ + return d->root(); +} + +QVector PartitionManager::partitions(Partition::StorageTypes types) const +{ + return d->partitions(types); +} + +void PartitionManager::refresh() +{ + d->refresh(); +} diff --git a/src/partitionmanager.h b/src/partitionmanager.h new file mode 100644 index 0000000..18d3669 --- /dev/null +++ b/src/partitionmanager.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 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 PARTITIONMANAGER_H +#define PARTITIONMANAGER_H + +#include + +#include + +class PartitionManagerPrivate; + +class SYSTEMSETTINGS_EXPORT PartitionManager : public QObject +{ + Q_OBJECT +public: + explicit PartitionManager(QObject *parent = 0); + ~PartitionManager(); + + Partition root() const; + QVector partitions(Partition::StorageTypes types = Partition::Any | Partition::ExcludeParents) const; + + void refresh(); + +signals: + void partitionChanged(const Partition &partition); + void partitionAdded(const Partition &partition); + void partitionRemoved(const Partition &partition); + +private: + QExplicitlySharedDataPointer d; +}; + +#endif diff --git a/src/partitionmanager_p.h b/src/partitionmanager_p.h new file mode 100644 index 0000000..61da9fc --- /dev/null +++ b/src/partitionmanager_p.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 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 PARTITIONMANAGER_P_H +#define PARTITIONMANAGER_P_H + +#include +#include + +#include + +class PartitionManagerPrivate : public QObject, public QSharedData +{ + Q_OBJECT +public: + typedef QVector> Partitions; + + PartitionManagerPrivate(); + ~PartitionManagerPrivate(); + + static PartitionManagerPrivate *instance(); + + Partition root() const; + QVector partitions(Partition::StorageTypes types) const; + + void refresh(); + void refresh(PartitionPrivate *partition); + void refresh(const Partitions &partitions); + +signals: + void partitionChanged(const Partition &partition); + void partitionAdded(const Partition &partition); + void partitionRemoved(const Partition &partition); + +private slots: + void newUnit(const QString &serviceName, const QDBusObjectPath &path); + void removedUnit(const QString &serviceName, const QDBusObjectPath &path); + +private: + static PartitionManagerPrivate *sharedInstance; + + Partitions m_partitions; + Partition m_root; + +}; + + +#endif + diff --git a/src/partitionmodel.cpp b/src/partitionmodel.cpp new file mode 100644 index 0000000..e1761eb --- /dev/null +++ b/src/partitionmodel.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2016 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." + */ + +#include "partitionmodel.h" +#include "partitionmanager_p.h" + +PartitionModel::PartitionModel(QObject *parent) + : QAbstractListModel(parent) + , m_manager(PartitionManagerPrivate::instance()) + , m_storageTypes(Any | ExcludeParents) +{ + m_partitions = m_manager->partitions(Partition::Any | Partition::ExcludeParents); + + 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); +} + +PartitionModel::~PartitionModel() +{ +} + +PartitionModel::StorageTypes PartitionModel::storageTypes() const +{ + return m_storageTypes; +} + +void PartitionModel::setStorageTypes(StorageTypes types) +{ + if (m_storageTypes != types) { + m_storageTypes = types; + + update(); + + emit storageTypesChanged(); + } +} + +void PartitionModel::refresh() +{ + m_manager->refresh(); +} + +void PartitionModel::refresh(int index) +{ + if (index >= 0 && index < m_partitions.count()) { + m_partitions[index].refresh(); + } +} + +void PartitionModel::update() +{ + const int count = m_partitions.count(); + + const auto partitions = m_manager->partitions(Partition::StorageTypes(int(m_storageTypes))); + + int index = 0; + + for (const auto partition : partitions) { + const int existingIndex = [this, index, partition]() { + for (int i = index; i < m_partitions.count(); ++i) { + if (m_partitions.at(i) == partition) { + return i; + } + } + return -1; + }(); + + if (existingIndex == -1) { + beginInsertRows(QModelIndex(), index, index); + m_partitions.insert(index, partition); + endInsertRows(); + } else if (existingIndex > index) { + beginMoveRows(QModelIndex(), existingIndex, existingIndex, QModelIndex(), index); + const auto partition = m_partitions.takeAt(existingIndex); + m_partitions.insert(index, partition); + endMoveRows(); + } + ++index; + } + + if (index < m_partitions.count()) { + beginRemoveRows(QModelIndex(), index, m_partitions.count() - 1); + m_partitions.resize(index); + endRemoveRows(); + } + + if (count != m_partitions.count()) { + emit countChanged(); + } +} + +QHash PartitionModel::roleNames() const +{ + static const QHash roleNames = { + { ReadOnlyRole, "readOnly" }, + { StatusRole, "status" }, + { CanMountRole, "canMount" }, + { MountFailedRole, "mountFailed" }, + { StorageTypeRole, "storageType" }, + { FilesystemTypeRole, "filesystemType" }, + { DevicePathRole, "devicePath" }, + { MountPathRole, "mountPath" }, + { BytesAvailableRole, "bytesAvailable" }, + { BytesTotalRole, "bytesTotal" }, + { BytesFreeRole, "bytesFree" } + }; + + return roleNames; +} + +int PartitionModel::rowCount(const QModelIndex &parent) const +{ + return !parent.isValid() ? m_partitions.count() : 0; +} + +QVariant PartitionModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= m_partitions.count() || index.column() != 0) { + return QVariant(); + } else { + const Partition &partition = m_partitions.at(index.row()); + + switch (role) { + case ReadOnlyRole: + return partition.isReadOnly(); + case StatusRole: + return partition.status(); + case CanMountRole: + return partition.canMount(); + case MountFailedRole: + return partition.mountFailed(); + case StorageTypeRole: + return partition.storageType(); + case FilesystemTypeRole: + return partition.filesystemType(); + case DevicePathRole: + return partition.devicePath(); + case MountPathRole: + return partition.mountPath(); + case BytesAvailableRole: + return partition.bytesAvailable(); + case BytesTotalRole: + return partition.bytesTotal(); + case BytesFreeRole: + return partition.bytesFree(); + default: + return QVariant(); + } + } +} + +void PartitionModel::partitionChanged(const Partition &partition) +{ + for (int i = 0; i < m_partitions.count(); ++i) { + if (m_partitions.at(i) == partition) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0)); + return; + } + } +} + +void PartitionModel::partitionAdded(const Partition &partition) +{ + if (partition.storageType() & m_storageTypes) { + update(); + } +} + +void PartitionModel::partitionRemoved(const Partition &partition) +{ + for (int i = 0; i < m_partitions.count(); ++i) { + if (m_partitions.at(i) == partition) { + beginRemoveRows(QModelIndex(), i, i); + m_partitions.removeAt(i); + endRemoveRows(); + + emit countChanged(); + + return; + } + } +} diff --git a/src/partitionmodel.h b/src/partitionmodel.h new file mode 100644 index 0000000..a2d9b2b --- /dev/null +++ b/src/partitionmodel.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 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 PARTITIONMODEL_H +#define PARTITIONMODEL_H + +#include + +#include + +class SYSTEMSETTINGS_EXPORT PartitionModel : public QAbstractListModel +{ + Q_OBJECT + Q_ENUMS(Status) + Q_ENUMS(StorageType) + Q_FLAGS(StorageTypes) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(StorageTypes storageTypes READ storageTypes WRITE setStorageTypes NOTIFY storageTypesChanged) +public: + enum { + ReadOnlyRole, + StatusRole, + CanMountRole, + MountFailedRole, + StorageTypeRole, + FilesystemTypeRole, + DevicePathRole, + MountPathRole, + BytesAvailableRole, + BytesTotalRole, + BytesFreeRole, + }; + + enum Status { + Unmounted = Partition::Unmounted, + Mounting = Partition::Mounting, + Mounted = Partition::Mounted, + Unmounting = Partition::Unmounting + }; + + enum StorageType { + Invalid = Partition::Invalid, + System = Partition::System, + User = Partition::User, + Mass = Partition::Mass, + External = Partition::External, + + ExcludeParents = Partition::ExcludeParents, + + Internal = Partition::Internal, + Any = Partition::Any + }; + + Q_DECLARE_FLAGS(StorageTypes, StorageType) + + explicit PartitionModel(QObject *parent = 0); + ~PartitionModel(); + + StorageTypes storageTypes() const; + void setStorageTypes(StorageTypes storageTypes); + + Q_INVOKABLE void refresh(); + Q_INVOKABLE void refresh(int index); + + QHash roleNames() const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + +signals: + void countChanged(); + void storageTypesChanged(); + +private: + void update(); + + void partitionChanged(const Partition &partition); + void partitionAdded(const Partition &partition); + void partitionRemoved(const Partition &partition); + + + QExplicitlySharedDataPointer m_manager; + QVector m_partitions; + StorageTypes m_storageTypes; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(PartitionModel::StorageTypes) + +#endif diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp index 04d0820..5abba82 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/plugin.cpp @@ -45,6 +45,7 @@ #include "devicelockiface.h" #include "developermodesettings.h" #include "diskusage.h" +#include "partitionmodel.h" class SystemSettingsPlugin : public QQmlExtensionPlugin { @@ -68,6 +69,7 @@ class SystemSettingsPlugin : public QQmlExtensionPlugin qmlRegisterType(uri, 1, 0, "DisplaySettings"); qmlRegisterType(uri, 1, 0, "USBSettings"); qmlRegisterType(uri, 1, 0, "AboutSettings"); + qmlRegisterType(uri, 1, 0, "PartitionModel"); qmlRegisterType(uri, 1, 0, "DeviceLockInterface"); qmlRegisterType(uri, 1, 0, "DeveloperModeSettings"); qRegisterMetaType("DeveloperModeSettings::Status"); diff --git a/src/profilecontrol.h b/src/profilecontrol.h index ed6e13e..1dff5c2 100644 --- a/src/profilecontrol.h +++ b/src/profilecontrol.h @@ -36,7 +36,9 @@ #include #include -class ProfileControl: public QObject +#include + +class SYSTEMSETTINGS_EXPORT ProfileControl: public QObject { Q_OBJECT Q_ENUMS(VibraMode) diff --git a/src/src.pro b/src/src.pro index b7210cd..025c6fe 100644 --- a/src/src.pro +++ b/src/src.pro @@ -1,13 +1,12 @@ TEMPLATE = lib TARGET = systemsettings -# TODO: hide_symbols CONFIG += qt create_pc create_prl no_install_prl c++11 QT += qml dbus systeminfo QT -= gui -CONFIG += link_pkgconfig -PKGCONFIG += profile mlite5 timed-qt5 libshadowutils +CONFIG += c++11 hide_symbols link_pkgconfig +PKGCONFIG += profile mlite5 timed-qt5 libshadowutils blkid system(qdbusxml2cpp -p mceiface.h:mceiface.cpp mce.xml) @@ -22,9 +21,12 @@ SOURCES += \ devicelockiface.cpp \ developermodesettings.cpp \ diskusage.cpp \ - diskusage_impl.cpp + diskusage_impl.cpp \ + partition.cpp \ + partitionmanager.cpp \ + partitionmodel.cpp -HEADERS += \ +PUBLIC_HEADERS = \ languagemodel.h \ datetimesettings.h \ profilecontrol.h \ @@ -35,10 +37,22 @@ HEADERS += \ devicelockiface.h \ developermodesettings.h \ diskusage.h \ - diskusage_p.h + partition.h \ + partitionmanager.h \ + partitionmodel.h \ + systemsettingsglobal.h + +HEADERS += \ + $$PUBLIC_HEADERS \ + diskusage_p.h \ + partition_p.h \ + partitionmanager_p.h + +DEFINES += \ + SYSTEMSETTINGS_BUILD_LIBRARY develheaders.path = /usr/include/systemsettings -develheaders.files = $$HEADERS +develheaders.files = $$PUBLIC_HEADERS target.path = $$[QT_INSTALL_LIBS] pkgconfig.files = $$PWD/pkgconfig/systemsettings.pc diff --git a/src/systemsettingsglobal.h b/src/systemsettingsglobal.h new file mode 100644 index 0000000..6576441 --- /dev/null +++ b/src/systemsettingsglobal.h @@ -0,0 +1,13 @@ + +#ifndef SYSTEMSETTINGSGLOBAL_H +#define SYSTEMSETTINGSGLOBAL_H + +#include + +#if defined(SYSTEMSETTINGS_BUILD_LIBRARY) +#define SYSTEMSETTINGS_EXPORT Q_DECL_EXPORT +#else +#define SYSTEMSETTINGS_EXPORT Q_DECL_IMPORT +#endif + +#endif