File indexing completed on 2024-04-21 16:20:45

0001 /*
0002  *   SPDX-FileCopyrightText: 2017, 2018, 2019 Ivan Cukic <ivan.cukic (at) kde.org>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "vaultsmodel.h"
0008 #include "vaultsmodel_p.h"
0009 #include <QFileInfo>
0010 
0011 using namespace PlasmaVault;
0012 
0013 VaultsModel::Private::Private(VaultsModel *parent)
0014     : service("org.kde.kded5", "/modules/plasmavault", QDBusConnection::sessionBus())
0015     , serviceWatcher("org.kde.kded5", QDBusConnection::sessionBus())
0016     , q(parent)
0017 {
0018     connect(&service, &org::kde::plasmavault::vaultAdded, this, &Private::onVaultAdded);
0019     connect(&service, &org::kde::plasmavault::vaultChanged, this, &Private::onVaultChanged);
0020     connect(&service, &org::kde::plasmavault::vaultRemoved, this, &Private::onVaultRemoved);
0021 
0022     connect(&serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this](const QString &service, const QString &oldOwner, const QString &newOwner) {
0023         Q_UNUSED(oldOwner);
0024 
0025         if (service != "org.kde.kded5") {
0026             return;
0027         }
0028 
0029         // If kded is not running, just clear all vault info,
0030         // otherwise load the data
0031         if (newOwner.isEmpty()) {
0032             clearData();
0033         } else {
0034             loadData();
0035         }
0036     });
0037 
0038     // Try to load the data. This should start kded if it is not running
0039     // for some reason
0040     loadData();
0041 }
0042 
0043 void VaultsModel::Private::loadData()
0044 {
0045     // Before loading the new data, lets forget everything
0046     clearData();
0047 
0048     // Asynchronously load the devices
0049     QDBusPendingReply<VaultInfoList> reply = service.availableDevices();
0050     auto watcher = new QDBusPendingCallWatcher(reply);
0051     connect(watcher, &QDBusPendingCallWatcher::finished, q, [this, reply, watcher] {
0052         watcher->deleteLater();
0053         if (reply.isError()) {
0054             return;
0055         }
0056         const VaultInfoList &vaultList = reply.value();
0057         const int oldSize = vaultKeys.size();
0058         q->beginResetModel();
0059 
0060         vaults.clear();
0061         vaultKeys.clear();
0062         busyVaults.clear();
0063         errorVaults.clear();
0064 
0065         for (const auto &vault : vaultList) {
0066             vaults[vault.device] = vault;
0067             vaultKeys << vault.device;
0068 
0069             if (vault.isBusy()) {
0070                 busyVaults << vault.device;
0071             }
0072 
0073             if (!vault.message.isEmpty()) {
0074                 errorVaults << vault.device;
0075             }
0076         }
0077 
0078         q->endResetModel();
0079 
0080         if (vaultKeys.size() != oldSize) {
0081             Q_EMIT q->rowCountChanged(vaultKeys.size());
0082         }
0083 
0084         Q_EMIT q->isBusyChanged(busyVaults.count() != 0);
0085         Q_EMIT q->hasErrorChanged(errorVaults.count() != 0);
0086     });
0087 }
0088 
0089 void VaultsModel::Private::clearData()
0090 {
0091     q->beginResetModel();
0092     vaultKeys.clear();
0093     vaults.clear();
0094     q->endResetModel();
0095 }
0096 
0097 void VaultsModel::Private::onVaultAdded(const PlasmaVault::VaultInfo &vaultInfo)
0098 {
0099     const auto device = vaultInfo.device;
0100 
0101     if (vaults.contains(device))
0102         return;
0103 
0104     q->beginInsertRows(QModelIndex(), vaultKeys.size(), vaultKeys.size());
0105     vaults[device] = vaultInfo;
0106     vaultKeys << device;
0107     q->endInsertRows();
0108     Q_EMIT q->rowCountChanged(vaultKeys.size());
0109 }
0110 
0111 void VaultsModel::Private::onVaultRemoved(const QString &device)
0112 {
0113     if (!vaults.contains(device))
0114         return;
0115 
0116     const auto row = vaultKeys.indexOf(device);
0117 
0118     q->beginRemoveRows(QModelIndex(), row, row);
0119     vaultKeys.removeAt(row);
0120     vaults.remove(device);
0121     q->endRemoveRows();
0122     Q_EMIT q->rowCountChanged(vaultKeys.size());
0123 }
0124 
0125 void VaultsModel::Private::onVaultChanged(const PlasmaVault::VaultInfo &vaultInfo)
0126 {
0127     const auto device = vaultInfo.device;
0128 
0129     if (!vaultKeys.contains(device))
0130         return;
0131 
0132     const auto row = vaultKeys.indexOf(device);
0133 
0134     // Lets see whether this warrants updates to the busy flag
0135     if (vaultInfo.isBusy() && !busyVaults.contains(device)) {
0136         busyVaults << device;
0137         if (busyVaults.count() == 1) {
0138             Q_EMIT q->isBusyChanged(true);
0139         }
0140     }
0141 
0142     if (!vaultInfo.isBusy() && busyVaults.contains(device)) {
0143         busyVaults.remove(device);
0144         if (busyVaults.count() == 0) {
0145             Q_EMIT q->isBusyChanged(false);
0146         }
0147     }
0148 
0149     // Lets see whether this warrants updates to the error flag
0150     if (!vaultInfo.message.isEmpty() && !errorVaults.contains(device)) {
0151         errorVaults << device;
0152         if (errorVaults.count() == 1) {
0153             Q_EMIT q->hasErrorChanged(true);
0154         }
0155     }
0156 
0157     if (vaultInfo.message.isEmpty() && errorVaults.contains(device)) {
0158         errorVaults.remove(device);
0159         if (errorVaults.count() == 0) {
0160             Q_EMIT q->hasErrorChanged(false);
0161         }
0162     }
0163 
0164     vaults[device] = vaultInfo;
0165     q->dataChanged(q->index(row), q->index(row));
0166 }
0167 
0168 VaultsModel::VaultsModel(QObject *parent)
0169     : QAbstractListModel(parent)
0170     , d(new Private(this))
0171 {
0172 }
0173 
0174 VaultsModel::~VaultsModel()
0175 {
0176 }
0177 
0178 int VaultsModel::rowCount(const QModelIndex &parent) const
0179 {
0180     Q_UNUSED(parent);
0181     return d->vaultKeys.size();
0182 }
0183 
0184 QVariant VaultsModel::data(const QModelIndex &index, int role) const
0185 {
0186     if (!index.isValid()) {
0187         return {};
0188     }
0189 
0190     const int row = index.row();
0191 
0192     if (row >= d->vaultKeys.count()) {
0193         return {};
0194     }
0195 
0196     const auto device = d->vaultKeys[row];
0197     const auto &vault = d->vaults[device];
0198 
0199     switch (role) {
0200     case VaultDevice:
0201         return vault.device;
0202 
0203     case VaultMountPoint:
0204         return vault.mountPoint;
0205 
0206     case VaultName:
0207         return vault.name.isEmpty() ? vault.device : vault.name;
0208 
0209     case VaultIcon: {
0210         switch (vault.status) {
0211         case VaultInfo::Error:
0212             return "document-close";
0213 
0214         case VaultInfo::DeviceMissing:
0215             return "document-close";
0216 
0217         case VaultInfo::NotInitialized:
0218             return "folder-gray";
0219 
0220         case VaultInfo::Closed:
0221             return "folder-encrypted";
0222 
0223         case VaultInfo::Opened:
0224             return "folder-decrypted";
0225 
0226         default:
0227             return "";
0228         }
0229     }
0230 
0231     case VaultIsBusy:
0232         return vault.isBusy();
0233 
0234     case VaultIsOpened:
0235         return vault.isOpened();
0236 
0237     case VaultActivities:
0238         return vault.activities;
0239 
0240     case VaultIsOfflineOnly:
0241         return vault.isOfflineOnly;
0242 
0243     case VaultMessage:
0244         return vault.message;
0245 
0246     case VaultIsEnabled:
0247         return !(vault.status == VaultInfo::Error) //
0248             && !(vault.status == VaultInfo::DeviceMissing);
0249     }
0250 
0251     return {};
0252 }
0253 
0254 QHash<int, QByteArray> VaultsModel::roleNames() const
0255 {
0256     return {
0257         {VaultName, "name"},
0258         {VaultIcon, "icon"},
0259         {VaultDevice, "device"},
0260         {VaultMountPoint, "mountPoint"},
0261         {VaultIsBusy, "isBusy"},
0262         {VaultIsOpened, "isOpened"},
0263         {VaultActivities, "activities"},
0264         {VaultIsOfflineOnly, "isOfflineOnly"},
0265         {VaultStatus, "status"},
0266         {VaultMessage, "message"},
0267         {VaultIsEnabled, "isEnabled"},
0268     };
0269 }
0270 
0271 void VaultsModel::refresh()
0272 {
0273     d->loadData();
0274 }
0275 
0276 void VaultsModel::reloadDevices()
0277 {
0278     d->service.updateStatus();
0279 }
0280 
0281 void VaultsModel::open(const QString &device)
0282 {
0283     if (!d->vaults.contains(device))
0284         return;
0285 
0286     d->service.openVault(device);
0287 }
0288 
0289 void VaultsModel::close(const QString &device)
0290 {
0291     if (!d->vaults.contains(device))
0292         return;
0293 
0294     d->service.closeVault(device);
0295 }
0296 
0297 void VaultsModel::toggle(const QString &device)
0298 {
0299     if (!d->vaults.contains(device))
0300         return;
0301 
0302     const auto &vault = d->vaults[device];
0303     if (vault.status == VaultInfo::Opened) {
0304         close(device);
0305     } else if (vault.status == VaultInfo::Closed) {
0306         open(device);
0307     }
0308 }
0309 
0310 void VaultsModel::forceClose(const QString &device)
0311 {
0312     if (!d->vaults.contains(device))
0313         return;
0314 
0315     d->service.forceCloseVault(device);
0316 }
0317 
0318 void VaultsModel::configure(const QString &device)
0319 {
0320     if (!d->vaults.contains(device))
0321         return;
0322 
0323     d->service.configureVault(device);
0324 }
0325 
0326 void VaultsModel::openInFileManager(const QString &device)
0327 {
0328     if (!d->vaults.contains(device))
0329         return;
0330 
0331     d->service.openVaultInFileManager(device);
0332 }
0333 
0334 void VaultsModel::requestNewVault()
0335 {
0336     d->service.requestNewVault();
0337 }
0338 
0339 bool VaultsModel::isBusy() const
0340 {
0341     return !d->busyVaults.isEmpty();
0342 }
0343 
0344 bool VaultsModel::hasError() const
0345 {
0346     return !d->errorVaults.isEmpty();
0347 }
0348 
0349 SortedVaultsModelProxy::SortedVaultsModelProxy(QObject *parent)
0350     : QSortFilterProxyModel(parent)
0351     , m_source(new VaultsModel(this))
0352     , m_kamd(new KActivities::Consumer(this))
0353 {
0354     setSourceModel(m_source);
0355 
0356     connect(m_kamd, &KActivities::Consumer::currentActivityChanged, this, &SortedVaultsModelProxy::invalidate);
0357     connect(m_kamd, &KActivities::Consumer::serviceStatusChanged, this, &SortedVaultsModelProxy::invalidate);
0358 }
0359 
0360 bool SortedVaultsModelProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const
0361 {
0362     const auto leftData = sourceModel()->data(left, VaultsModel::VaultName);
0363     const auto rightData = sourceModel()->data(right, VaultsModel::VaultName);
0364 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0365     return leftData < rightData;
0366 #else
0367     return QPartialOrdering::Less == QVariant::compare(leftData, rightData);
0368 #endif
0369 }
0370 
0371 bool SortedVaultsModelProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
0372 {
0373     Q_UNUSED(sourceParent);
0374 
0375     const auto activities = m_source->index(sourceRow).data(VaultsModel::VaultActivities).toStringList();
0376 
0377     const auto isOpened = m_source->index(sourceRow).data(VaultsModel::VaultIsOpened).toBool();
0378 
0379     return activities.size() == 0 || isOpened || activities.contains(m_kamd->currentActivity());
0380 }
0381 
0382 QObject *SortedVaultsModelProxy::actionsModel() const
0383 {
0384     return sourceModel();
0385 }
0386 
0387 void SortedVaultsModelProxy::reloadDevices()
0388 {
0389     static_cast<VaultsModel *>(sourceModel())->reloadDevices();
0390 }