diff --git a/asynqt/private/wrappers/kjob_p.h b/asynqt/private/wrappers/kjob_p.h new file mode 100644 index 0000000..711c249 --- /dev/null +++ b/asynqt/private/wrappers/kjob_p.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + * If not, see . + */ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the AsynQt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +namespace AsynQt { +namespace detail { + +template +class KJobFutureInterface : public QObject, + public QFutureInterface<_Result> { +public: + KJobFutureInterface(KJob* job) + : job(job) + { + job->setAutoDelete(false); + } + + ~KJobFutureInterface() + { + } + + void callFinished(); + + QFuture<_Result> start() + { + QObject::connect(job, &KJob::result, + this, [this] () { callFinished(); }, + Qt::QueuedConnection); + + this->reportStarted(); + + job->start(); + + return this->future(); + } + +private: + KJob* job; +}; + +template +void KJobFutureInterface<_Result>::callFinished() +{ + this->reportResult(job); + this->reportFinished(); + + deleteLater(); +} + +template <> +void KJobFutureInterface::callFinished() +{ + this->reportFinished(); + + job->deleteLater(); + deleteLater(); +} + +} // namespace detail +} // namespace AsynQt + diff --git a/asynqt/wrappers/kjob.h b/asynqt/wrappers/kjob.h new file mode 100644 index 0000000..7a81f40 --- /dev/null +++ b/asynqt/wrappers/kjob.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 Ivan Cukic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + * If not, see . + */ + +#ifndef ASYNQT_CONS_KJOBFUTURE_H +#define ASYNQT_CONS_KJOBFUTURE_H + +#include + +#include +#include +#include + +#include + +#include "../private/wrappers/kjob_p.h" + +namespace AsynQt { + +/** + * Creates a future from the specified dbus reply + */ +template +QFuture<_Result> makeFuture(KJob* job) +{ + using namespace detail; + + return (new KJobFutureInterface<_Result>(job))->start(); +} + +} // namespace AsynQt + +#endif // ASYNQT_CONS_DBUSFUTURE_H + diff --git a/common/vaultinfo.h b/common/vaultinfo.h index 49fe7b1..18abed8 100644 --- a/common/vaultinfo.h +++ b/common/vaultinfo.h @@ -1,160 +1,161 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_COMMON_VAULT_INFO_H #define PLASMAVAULT_COMMON_VAULT_INFO_H #include #include #include namespace PlasmaVault { /** * Class used to serialize the vault data for DBus communication */ class VaultInfo { public: enum Status { NotInitialized = 0, Opened = 1, Closed = 2, Creating = 3, Opening = 4, Closing = 5, - Destroying = 6, + Dismantling = 6, + Dismantled = 7, Error = 255 }; // Q_ENUM(Status); // VaultInfo is not a QObject or anything like that QString name; QString device; QString mountPoint; Status status; QString message; QStringList activities; bool isOfflineOnly; inline bool isInitialized() const { return status == Closed || status == Opened || status == Closing || status == Opening; } inline bool isOpened() const { return status == Opened; } inline bool isBusy() const { return status == Creating || status == Opening || status == Closing - || status == Destroying; + || status == Dismantling; } }; typedef QList VaultInfoList; inline QDebug &operator<< (QDebug &debug, const VaultInfo &vaultInfo) { debug << vaultInfo.name << vaultInfo.device << vaultInfo.mountPoint << (quint16)vaultInfo.status << vaultInfo.message << vaultInfo.activities << vaultInfo.isOfflineOnly ; return debug; } inline QDBusArgument &operator<< (QDBusArgument &argument, const VaultInfo &vaultInfo) { argument.beginStructure(); argument << vaultInfo.name << vaultInfo.device << vaultInfo.mountPoint << (quint16)vaultInfo.status << vaultInfo.message << vaultInfo.activities << vaultInfo.isOfflineOnly ; argument.endStructure(); return argument; } inline const QDBusArgument &operator>> (const QDBusArgument &argument, VaultInfo &vaultInfo) { quint16 status; argument.beginStructure(); argument >> vaultInfo.name >> vaultInfo.device >> vaultInfo.mountPoint >> status >> vaultInfo.message >> vaultInfo.activities >> vaultInfo.isOfflineOnly ; vaultInfo.status = (VaultInfo::Status)status; argument.endStructure(); return argument; } } // namespace PlasmaVault Q_DECLARE_METATYPE(PlasmaVault::VaultInfo) Q_DECLARE_METATYPE(PlasmaVault::VaultInfoList) #endif // include guard diff --git a/kded/CMakeLists.txt b/kded/CMakeLists.txt index 2822303..666100e 100644 --- a/kded/CMakeLists.txt +++ b/kded/CMakeLists.txt @@ -1,92 +1,94 @@ include_directories ( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/engine ) set ( kded_plasmavault_SRCS service.cpp engine/vault.cpp engine/backend_p.cpp engine/fusebackend_p.cpp engine/types.cpp engine/commandresult.cpp engine/backends/encfs/encfsbackend.cpp engine/backends/cryfs/cryfsbackend.cpp ui/dialogdsl.cpp ui/activitieslinkingwidget.cpp ui/backendchooserwidget.cpp ui/cryfscypherchooserwidget.cpp ui/directorypairchooserwidget.cpp ui/directorychooserwidget.cpp ui/namechooserwidget.cpp ui/noticewidget.cpp ui/passwordchooserwidget.cpp ui/offlineonlywidget.cpp + ui/vaultdeletionwidget.cpp ui/vaultcreationwizard.cpp ui/vaultconfigurationwizard.cpp ui/mountdialog.cpp ../common/vaultinfo.cpp ) ki18n_wrap_ui ( kded_plasmavault_SRCS ui/activitieslinkingwidget.ui ui/backendchooserwidget.ui ui/cryfscypherchooserwidget.ui ui/directorypairchooserwidget.ui ui/directorychooserwidget.ui ui/namechooserwidget.ui ui/noticewidget.ui ui/passwordchooserwidget.ui ui/offlineonlywidget.ui + ui/vaultdeletionwidget.ui ui/vaultcreationwizard.ui ui/vaultconfigurationwizard.ui ui/mountdialog.ui ) add_library ( kded_plasmavault MODULE ${kded_plasmavault_SRCS} ) set_target_properties ( kded_plasmavault PROPERTIES OUTPUT_NAME plasmavault ) kcoreaddons_desktop_to_json ( kded_plasmavault plasmavault.desktop ) target_link_libraries ( kded_plasmavault Qt5::Core Qt5::DBus Qt5::Widgets KF5::Activities KF5::ConfigCore KF5::ConfigWidgets KF5::CoreAddons KF5::DBusAddons KF5::I18n KF5::KIOCore KF5::KIOWidgets KF5::SysGuard KF5::WidgetsAddons KF5::ProcessCore KF5::NetworkManagerQt ) install ( TARGETS kded_plasmavault DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kded) diff --git a/kded/engine/backend_p.h b/kded/engine/backend_p.h index b98c9ac..7813332 100644 --- a/kded/engine/backend_p.h +++ b/kded/engine/backend_p.h @@ -1,81 +1,81 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_KDED_ENGINE_BACKEND_P_H #define PLASMAVAULT_KDED_ENGINE_BACKEND_P_H #include #include #include #include #include "vault.h" #include "commandresult.h" namespace PlasmaVault { class Backend { public: typedef std::shared_ptr Ptr; Backend(); virtual ~Backend(); virtual bool isInitialized(const Device &device) const = 0; virtual bool isOpened(const MountPoint &mountPoint) const = 0; virtual FutureResult<> initialize(const QString &name, const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) = 0; virtual FutureResult<> open(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) = 0; virtual FutureResult<> close(const Device &device, const MountPoint &mountPoint) = 0; - virtual FutureResult<> destroy(const Device &device, - const MountPoint &mountPoint, - const Vault::Payload &payload) = 0; + virtual FutureResult<> dismantle(const Device &device, + const MountPoint &mountPoint, + const Vault::Payload &payload) = 0; virtual FutureResult<> validateBackend() = 0; virtual QString name() const = 0; static QStringList availableBackends(); static Ptr instance(const QString &backend); QString formatMessageLine( const QString &command, const QPair &result) const; protected: static bool isDirectoryEmpty(const QString &path); }; } // namespace PlasmaVault #endif // include guard diff --git a/kded/engine/commandresult.h b/kded/engine/commandresult.h index 89fba31..297872b 100644 --- a/kded/engine/commandresult.h +++ b/kded/engine/commandresult.h @@ -1,71 +1,72 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_KDED_ENGINE_COMMAND_RESULT_H #define PLASMAVAULT_KDED_ENGINE_COMMAND_RESULT_H #include #include "asynqt/basic/all.h" #include "asynqt/utils/expected.h" namespace PlasmaVault { class Error { public: enum Code { MountPointError, DeviceError, BackendError, - CommandError + CommandError, + DeletionError }; Error(Code code, const QString &message = QString()); Code code() const; QString message() const; private: Code m_code; QString m_message; }; template using Result = AsynQt::Expected; template using FutureResult = QFuture>; inline FutureResult<> errorResult(Error::Code error, const QString &message) { qWarning() << message; return makeReadyFuture(Result<>::error(error, message)); } } // namespace PlasmaVault #endif // include guard diff --git a/kded/engine/fusebackend_p.cpp b/kded/engine/fusebackend_p.cpp index 81ce494..8f41724 100644 --- a/kded/engine/fusebackend_p.cpp +++ b/kded/engine/fusebackend_p.cpp @@ -1,248 +1,260 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "fusebackend_p.h" +#include #include #include #include #include #include +#include #include #include #include +#include #include #include #include "singleton_p.h" using namespace AsynQt; namespace PlasmaVault { Result<> FuseBackend::hasProcessFinishedSuccessfully(QProcess *process) { const auto out = process->readAllStandardOutput(); const auto err = process->readAllStandardError(); return // If all went well, just return success (process->exitStatus() == QProcess::NormalExit && process->exitCode() == 0) ? Result<>::success() : // If we tried to mount into a non-empty location, report err.contains("'nonempty'") ? Result<>::error(Error::CommandError, i18n("The mount point directory is not empty, refusing to open the vault")) : // If we have a message for the user, report it // !out.isEmpty() ? // Result<>::error(Error::CommandError, // out) : // otherwise just report that we failed Result<>::error(Error::CommandError, i18n("Unable to perform the operation")); } FuseBackend::FuseBackend() { } FuseBackend::~FuseBackend() { } QProcess *FuseBackend::process(const QString &executable, const QStringList &arguments, const QHash &environment) const { auto result = new QProcess(); result->setProgram(executable); result->setArguments(arguments); if (environment.count() > 0) { auto env = result->processEnvironment(); for (const auto& key: environment.keys()) { env.insert(key, environment[key]); } result->setProcessEnvironment(env); } return result; } QProcess *FuseBackend::fusermount(const QStringList &arguments) const { return process("fusermount", arguments, {}); } FutureResult<> FuseBackend::initialize(const QString &name, const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) { Q_UNUSED(name); return isInitialized(device) ? errorResult(Error::BackendError, i18n("This directory already contains encrypted data")) : !isDirectoryEmpty(device) || !isDirectoryEmpty(mountPoint) ? errorResult(Error::BackendError, i18n("You need to select empty directories for the encrypted storage and for the mount point")) : // otherwise mount(device, mountPoint, payload); } FutureResult<> FuseBackend::open(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) { return isOpened(mountPoint) ? errorResult(Error::BackendError, i18n("Device is already open")) : // otherwise mount(device, mountPoint, payload); } FutureResult<> FuseBackend::close(const Device &device, const MountPoint &mountPoint) { Q_UNUSED(device); return !isOpened(mountPoint) ? errorResult(Error::BackendError, i18n("Device is not open")) : // otherwise makeFuture(fusermount({ "-u", mountPoint }), hasProcessFinishedSuccessfully); } -FutureResult<> FuseBackend::destroy(const Device &device, - const MountPoint &mountPoint, - const Vault::Payload &payload) +FutureResult<> FuseBackend::dismantle(const Device &device, + const MountPoint &mountPoint, + const Vault::Payload &payload) { // TODO: // mount // unmount // remove the directories - // return Fuse::destroy(device, mountPoint, password); + // return Fuse::dismantle(device, mountPoint, password); - Q_UNUSED(device) - Q_UNUSED(mountPoint) Q_UNUSED(payload) - return {}; + + // Removing the data and the mount point + return transform(makeFuture( + KIO::del( { QUrl::fromLocalFile(device.data()) + , QUrl::fromLocalFile(mountPoint.data()) + } )), + [] (KJob *job) { + job->deleteLater(); + return job->error() == 0 ? Result<>::success() + : Result<>::error(Error::DeletionError, job->errorString()); + } + ); } QFuture> FuseBackend::checkVersion( QProcess *process, const std::tuple &requiredVersion) const { using namespace AsynQt::operators; return makeFuture(process, [=] (QProcess *process) { if (process->exitStatus() != QProcess::NormalExit) { return qMakePair( false, i18n("Failed to execute")); } QRegularExpression versionMatcher("([0-9]+)[.]([0-9]+)[.]([0-9]+)"); const auto out = process->readAllStandardOutput(); const auto err = process->readAllStandardError(); const auto all = out + err; const auto matches = versionMatcher.match(all); if (!matches.isValid()) { return qMakePair( false, i18n("Unable to detect the version")); } const auto matchedVersion = std::make_tuple(matches.captured(1).toInt(), matches.captured(2).toInt(), matches.captured(3).toInt()); if (matchedVersion < requiredVersion) { // Bad version, we need to notify the world return qMakePair( false, i18n("Wrong version installed. The required version is %1.%2.%3", std::get<0>(requiredVersion), std::get<1>(requiredVersion), std::get<2>(requiredVersion) )); } return qMakePair( true, i18n("Correct version found")); }); } bool FuseBackend::isOpened(const MountPoint &mountPoint) const { // warning: KMountPoint depends on /etc/mtab according to the documentation. KMountPoint::Ptr ptr = KMountPoint::currentMountPoints().findByPath(mountPoint); // we can not rely on ptr->realDeviceName() since it is empty, // KMountPoint can not get the source return ptr && ptr->mountPoint() == mountPoint; } } // namespace PlasmaVault diff --git a/kded/engine/fusebackend_p.h b/kded/engine/fusebackend_p.h index 4bb6cfe..aa2aaf9 100644 --- a/kded/engine/fusebackend_p.h +++ b/kded/engine/fusebackend_p.h @@ -1,72 +1,72 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_KDED_ENGINE_BACKENDS_FUSE_BACKEND_H #define PLASMAVAULT_KDED_ENGINE_BACKENDS_FUSE_BACKEND_H #include "backend_p.h" #include namespace PlasmaVault { class FuseBackend: public Backend { public: FuseBackend(); ~FuseBackend(); bool isOpened(const MountPoint &mountPoint) const override; FutureResult<> initialize(const QString &name, const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) override; FutureResult<> open(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) override; FutureResult<> close(const Device &device, const MountPoint &mountPoint) override; - FutureResult<> destroy(const Device &device, - const MountPoint &mountPoint, - const Vault::Payload &payload) override; + FutureResult<> dismantle(const Device &device, + const MountPoint &mountPoint, + const Vault::Payload &payload) override; protected: virtual FutureResult<> mount(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) = 0; QProcess *process(const QString &command, const QStringList &args, const QHash &environment) const; QFuture> checkVersion( QProcess *process, const std::tuple &requiredVersion) const; QProcess *fusermount(const QStringList &arguments = QStringList()) const; static Result<> hasProcessFinishedSuccessfully(QProcess *process); }; } // namespace PlasmaVault #endif // include guard diff --git a/kded/engine/vault.cpp b/kded/engine/vault.cpp index 9065d79..72f1e10 100644 --- a/kded/engine/vault.cpp +++ b/kded/engine/vault.cpp @@ -1,665 +1,680 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "vault.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "backend_p.h" #include "asynqt/basic/all.h" #include "asynqt/wrappers/process.h" #include "asynqt/operations/listen.h" #include "asynqt/operations/cast.h" #include #define CFG_NAME "name" #define CFG_LAST_STATUS "lastStatus" #define CFG_LAST_ERROR "lastError" #define CFG_MOUNT_POINT "mountPoint" #define CFG_BACKEND "backend" #define CFG_ACTIVITIES "activities" #define CFG_OFFLINEONLY "offlineOnly" namespace PlasmaVault { class Vault::Private { public: Vault * const q; KSharedConfigPtr config; Device device; QTimer savingDelay; struct Data { QString name; MountPoint mountPoint; VaultInfo::Status status; QString message; QStringList activities; bool isOfflineOnly; QString backendName; Backend::Ptr backend; }; using ExpectedData = AsynQt::Expected; ExpectedData data; void updateMessage(const QString &message) { if (!data) return; data->message = message; emit q->messageChanged(message); } void updateStatus() { if (data) { // Checking the status, and whether we should update it const auto oldStatus = data->status; - const auto newStatus = - isOpened() ? VaultInfo::Opened : - isInitialized() ? VaultInfo::Closed : - VaultInfo::NotInitialized; - if (oldStatus == newStatus) return; + if (oldStatus == VaultInfo::Dismantling) { + // This means that the vault should be forgotten + KConfigGroup generalConfig(config, "EncryptedDevices"); + generalConfig.deleteEntry(device.data()); - data->status = newStatus; + KConfigGroup vaultConfig(config, device.data()); + vaultConfig.deleteGroup(); - emit q->statusChanged(data->status); + emit q->statusChanged(data->status = VaultInfo::Dismantled); - if (newStatus == VaultInfo::Closed || newStatus == VaultInfo::Opened) { - emit q->isOpenedChanged(newStatus == VaultInfo::Opened); - } + } else { + const auto newStatus = + isOpened() ? VaultInfo::Opened : + isInitialized() ? VaultInfo::Closed : + VaultInfo::NotInitialized; - if (oldStatus == VaultInfo::NotInitialized || newStatus == VaultInfo::NotInitialized) { - emit q->isInitializedChanged(newStatus); - } + if (oldStatus == newStatus) return; - if (oldStatus == VaultInfo::Creating - || oldStatus == VaultInfo::Opening - || oldStatus == VaultInfo::Closing - || oldStatus == VaultInfo::Destroying) { - emit q->isBusyChanged(false); - } + data->status = newStatus; - // Saving the data for the current mount - KConfigGroup generalConfig(config, "EncryptedDevices"); - generalConfig.writeEntry(device.data(), true); + emit q->statusChanged(data->status); - KConfigGroup vaultConfig(config, device.data()); - vaultConfig.writeEntry(CFG_LAST_STATUS, (int)data->status); - vaultConfig.writeEntry(CFG_MOUNT_POINT, data->mountPoint.data()); - vaultConfig.writeEntry(CFG_NAME, data->name); - vaultConfig.writeEntry(CFG_BACKEND, data->backend->name()); + if (newStatus == VaultInfo::Closed || newStatus == VaultInfo::Opened) { + emit q->isOpenedChanged(newStatus == VaultInfo::Opened); + } + + if (oldStatus == VaultInfo::NotInitialized || newStatus == VaultInfo::NotInitialized) { + emit q->isInitializedChanged(newStatus); + } + + if (oldStatus == VaultInfo::Creating + || oldStatus == VaultInfo::Opening + || oldStatus == VaultInfo::Closing + || oldStatus == VaultInfo::Dismantling) { + emit q->isBusyChanged(false); + } + + // Saving the data for the current mount + KConfigGroup generalConfig(config, "EncryptedDevices"); + generalConfig.writeEntry(device.data(), true); - vaultConfig.writeEntry(CFG_ACTIVITIES, data->activities); - vaultConfig.writeEntry(CFG_OFFLINEONLY, data->isOfflineOnly); + KConfigGroup vaultConfig(config, device.data()); + vaultConfig.writeEntry(CFG_LAST_STATUS, (int)data->status); + vaultConfig.writeEntry(CFG_MOUNT_POINT, data->mountPoint.data()); + vaultConfig.writeEntry(CFG_NAME, data->name); + vaultConfig.writeEntry(CFG_BACKEND, data->backend->name()); - org::kde::KDirNotify::emitFilesAdded( - QUrl::fromLocalFile(data->mountPoint.data())); + vaultConfig.writeEntry(CFG_ACTIVITIES, data->activities); + vaultConfig.writeEntry(CFG_OFFLINEONLY, data->isOfflineOnly); + + org::kde::KDirNotify::emitFilesAdded( + QUrl::fromLocalFile(data->mountPoint.data())); + } } else { emit q->isOpenedChanged(false); emit q->isInitializedChanged(false); emit q->isBusyChanged(false); KConfigGroup generalConfig(config, "EncryptedDevices"); generalConfig.writeEntry(device.data(), false); KConfigGroup vaultConfig(config, device.data()); vaultConfig.writeEntry(CFG_LAST_STATUS, (int)VaultInfo::Error); vaultConfig.writeEntry(CFG_LAST_ERROR, data.error().message() + " (code: " + QString::number(data.error().code()) + ")"); // vaultConfig.deleteEntry(CFG_MOUNT_POINT); // vaultConfig.deleteEntry(CFG_NAME); // vaultConfig.deleteEntry(CFG_BACKEND); // vaultConfig.deleteEntry(CFG_ACTIVITIES); // vaultConfig.deleteEntry(CFG_OFFLINEONLY); emit q->statusChanged(VaultInfo::Error); } config->sync(); } ExpectedData errorData(Error::Code error, const QString &message) const { qWarning() << "error: " << message; return ExpectedData::error(error, message); } ExpectedData loadVault(const Device &device, const QString &name = QString(), const MountPoint &mountPoint = MountPoint(), const Payload &payload = Payload()) const { if (!config->hasGroup(device.data())) { return errorData(Error::DeviceError, i18n("Unknown device")); } Data vaultData; const QString backendName = payload[KEY_BACKEND].toString(); const QStringList activities = payload[KEY_ACTIVITIES].toStringList(); const bool isOfflineOnly = payload[KEY_OFFLINEONLY].toBool(); // status should never be in this state, if we got an error, // d->data should not be valid vaultData.status = VaultInfo::Error; // Reading the mount data from the config const KConfigGroup vaultConfig(config, device.data()); vaultData.name = vaultConfig.readEntry(CFG_NAME, name); vaultData.mountPoint = MountPoint(vaultConfig.readEntry(CFG_MOUNT_POINT, mountPoint.data())); vaultData.backendName = vaultConfig.readEntry(CFG_BACKEND, backendName); vaultData.activities = vaultConfig.readEntry(CFG_ACTIVITIES, activities); vaultData.isOfflineOnly = vaultConfig.readEntry(CFG_OFFLINEONLY, isOfflineOnly); const QDir mountPointDir(vaultData.mountPoint); return // If the backend is not known, we need to fail !Backend::availableBackends().contains(vaultData.backendName) ? errorData(Error::BackendError, i18n("Configured backend does not exist: %1", vaultData.backendName)) : // If the mount point is empty, we can not do anything vaultData.mountPoint.isEmpty() ? errorData(Error::MountPointError, i18n("Mount point is not specified")) : // Lets try to create the mount point !mountPointDir.exists() && !QDir().mkpath(vaultData.mountPoint) ? errorData(Error::MountPointError, i18n("Cannot create the mount point")) : // Instantiate the backend if possible !(vaultData.backend = Backend::instance(vaultData.backendName)) ? errorData(Error::BackendError, i18n("Configured backend cannot be instantiated: %1", vaultData.backendName)) : // otherwise ExpectedData::success(vaultData); } Private(Vault *parent, const Device &device) : q(parent) , config(KSharedConfig::openConfig(PLASMAVAULT_CONFIG_FILE)) , device(device) , data(loadVault(device)) { updateStatus(); } template T followFuture(VaultInfo::Status whileNotFinished, const T &future) { using namespace AsynQt::operators; emit q->isBusyChanged(true); data->status = whileNotFinished; return future | onSuccess([this] { updateStatus(); }); } bool isInitialized() const { return data && data->backend->isInitialized(device); } bool isOpened() const { return data && data->backend->isOpened(data->mountPoint); } }; Vault::Vault(const Device &device, QObject *parent) : QObject(parent) , d(new Private(this, device)) { d->savingDelay.setInterval(300); d->savingDelay.setSingleShot(true); connect(&d->savingDelay, &QTimer::timeout, this, [&] { qDebug() << "Saving vault info:" << d->data->name << d->data->mountPoint << d->data->activities << d->data->isOfflineOnly ; KConfigGroup vaultConfig(d->config, d->device.data()); vaultConfig.writeEntry(CFG_MOUNT_POINT, d->data->mountPoint.data()); vaultConfig.writeEntry(CFG_NAME, d->data->name); vaultConfig.writeEntry(CFG_ACTIVITIES, d->data->activities); vaultConfig.writeEntry(CFG_OFFLINEONLY, d->data->isOfflineOnly); d->config->sync(); emit infoChanged(); }); } Vault::~Vault() { - close(); + if (d->isOpened()) { + close(); + } } void Vault::saveConfiguration() { d->savingDelay.start(); } FutureResult<> Vault::create(const QString &name, const MountPoint &mountPoint, const Payload &payload) { using namespace AsynQt::operators; return // If the backend is already known, and the device is initialized, // we do not want to do it again d->data && d->data->backend->isInitialized(d->device) ? errorResult(Error::DeviceError, i18n("This device is already registered. Cannot recreate it.")) : // Mount not open, check the error messages !(d->data = d->loadVault(d->device, name, mountPoint, payload)) ? errorResult(Error::BackendError, i18n("Unknown error, unable to create the backend.")) : // otherwise d->followFuture(VaultInfo::Creating, d->data->backend->initialize(name, d->device, mountPoint, payload)) | onSuccess([mountPoint] { // If we have successfully created the vault, // lets try to set its icon QFile dotDir(mountPoint + "/.directory"); if (dotDir.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&dotDir); out << "[Desktop Entry]\nIcon=folder-decrypted\n"; } }); } FutureResult<> Vault::open(const Payload &payload) { return // We can not mount something that has not been registered // with us before !d->data ? errorResult(Error::BackendError, i18n("Cannot open an unknown vault.")) : // otherwise d->followFuture(VaultInfo::Opening, d->data->backend->open(d->device, d->data->mountPoint, payload)); } FutureResult<> Vault::close() { using namespace AsynQt::operators; return // We can not mount something that has not been registered // with us before !d->data ? errorResult(Error::BackendError, i18n("The vault is unknown, cannot close it.")) : // otherwise d->followFuture(VaultInfo::Closing, d->data->backend->close(d->device, d->data->mountPoint)) | onSuccess([this] (const Result<> &result) { if (!isOpened() || result) { d->updateMessage(QString()); } else { // We want to check whether there is an application // that is accessing the vault AsynQt::Process::getOutput("lsof", { "-t", mountPoint() }) | cast() | onError([this] { d->updateMessage(i18n("Unable to close the vault, an application is using it")); }) | onSuccess([this] (const QString &result) { // based on ksolidnotify.cpp QStringList blockApps; const auto &pidList = result.split(QRegExp(QStringLiteral("\\s+")), QString::SkipEmptyParts); if (pidList.size() == 0) { d->updateMessage(i18n("Unable to close the vault, an application is using it")); close(); } else { KSysGuard::Processes procs; for (const QString &pidStr: pidList) { int pid = pidStr.toInt(); if (!pid) { continue; } procs.updateOrAddProcess(pid); KSysGuard::Process *proc = procs.getProcess(pid); if (!blockApps.contains(proc->name())) { blockApps << proc->name(); } } blockApps.removeDuplicates(); d->updateMessage(i18n("Unable to close the vault, it is used by %1", blockApps.join(", "))); } }); } }); } // FutureResult<> Vault::configure() // { // return close(); // } FutureResult<> Vault::forceClose() { using namespace AsynQt::operators; AsynQt::await( AsynQt::Process::getOutput("lsof", { "-t", mountPoint() }) | cast() | onError([this] { d->updateMessage(i18n("Failed to fetch the list of applications using this vault")); }) | onSuccess([this] (const QString &result) { // based on ksolidnotify.cpp QStringList blockApps; const auto &pidList = result.split(QRegExp(QStringLiteral("\\s+")), QString::SkipEmptyParts); KSysGuard::Processes procs; for (const QString &pidStr: pidList) { int pid = pidStr.toInt(); if (!pid) { continue; } procs.sendSignal(pid, SIGKILL); } })); return close(); } -FutureResult<> Vault::destroy(const Payload &payload) +FutureResult<> Vault::dismantle(const Payload &payload) { return // We can not mount something that has not been registered // with us before !d->data ? errorResult(Error::BackendError, - i18n("The vault is unknown, cannot destroy it.")) : + i18n("The vault is unknown, cannot dismantle it.")) : // otherwise - d->followFuture(VaultInfo::Destroying, - d->data->backend->destroy(d->device, d->data->mountPoint, payload)); + d->followFuture(VaultInfo::Dismantling, + d->data->backend->dismantle(d->device, d->data->mountPoint, payload)); } VaultInfo::Status Vault::status() const { return d->data->status; } bool Vault::isValid() const { return d->data; } Device Vault::device() const { return d->device; } QList Vault::availableDevices() { const auto config = KSharedConfig::openConfig(PLASMAVAULT_CONFIG_FILE); const KConfigGroup general(config, "EncryptedDevices"); QList results; for (const auto& item: general.keyList()) { results << Device(item); } return results; } QStringList Vault::statusMessage() { for (const auto& backendName: Backend::availableBackends()) { auto backend = Backend::instance(backendName); backend->validateBackend(); } return {}; } QString Vault::message() const { if (!d->data) { return d->data.error().message(); } else { return d->data->message; } } bool Vault::isInitialized() const { return d->isInitialized(); } bool Vault::isOpened() const { return d->isOpened(); } MountPoint Vault::mountPoint() const { return d->data->mountPoint; } void Vault::setMountPoint(const MountPoint &mountPoint) { if (d->data->mountPoint != mountPoint.data()) { QDir().rmpath(d->data->mountPoint.data()); QDir().mkpath(mountPoint.data()); d->data->mountPoint = mountPoint; saveConfiguration(); } } QStringList Vault::activities() const { return d->data->activities; } void Vault::setActivities(const QStringList &activities) { d->data->activities = activities; emit activitiesChanged(activities); saveConfiguration(); } bool Vault::isOfflineOnly() const { return d->data->isOfflineOnly; } void Vault::setIsOfflineOnly(bool isOfflineOnly) { d->data->isOfflineOnly = isOfflineOnly; emit isOfflineOnlyChanged(isOfflineOnly); saveConfiguration(); } QString Vault::name() const { return d->data->name; } void Vault::setName(const QString &name) { d->data->name = name; emit nameChanged(name); saveConfiguration(); } QString Vault::backend() const { return d->data->backendName; } bool Vault::isBusy() const { if (!d->data) { return false; } switch (status()) { case VaultInfo::Creating: case VaultInfo::Opening: case VaultInfo::Closing: - case VaultInfo::Destroying: + case VaultInfo::Dismantling: return true; default: return false; } } VaultInfo Vault::info() const { VaultInfo vaultInfo; vaultInfo.device = device(); vaultInfo.name = name(); vaultInfo.status = status(); vaultInfo.message = message(); vaultInfo.activities = activities(); vaultInfo.isOfflineOnly = isOfflineOnly(); return vaultInfo; } } // namespace PlasmaVault diff --git a/kded/engine/vault.h b/kded/engine/vault.h index d5f8c55..01f0035 100644 --- a/kded/engine/vault.h +++ b/kded/engine/vault.h @@ -1,139 +1,139 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_KDED_ENGINE_VAULT_H #define PLASMAVAULT_KDED_ENGINE_VAULT_H #include #include #include #include #include #include "types.h" #include "commandresult.h" class QDBusArgument; namespace PlasmaVault { // Main keys #define KEY_NAME "vault-name" #define KEY_BACKEND "vault-backend" #define KEY_PASSWORD "vault-password" #define KEY_DEVICE "vault-device" #define KEY_MOUNT_POINT "vault-mount-point" // Additional options #define KEY_ACTIVITIES "vault-activities" #define KEY_OFFLINEONLY "vault-offline-only" class Vault: public QObject { Q_OBJECT Q_PROPERTY(PlasmaVault::Device device READ device) Q_PROPERTY(QString mountPoint READ mountPoint NOTIFY mountPointChanged) Q_PROPERTY(VaultInfo::Status status READ status NOTIFY statusChanged) Q_PROPERTY(bool isInitialized READ isInitialized NOTIFY isInitializedChanged) Q_PROPERTY(bool isOpened READ isOpened NOTIFY isOpenedChanged) Q_PROPERTY(bool isBusy READ isBusy NOTIFY isBusyChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString message READ message NOTIFY messageChanged) Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged) Q_PROPERTY(bool isOfflineOnly READ isOfflineOnly NOTIFY isOfflineOnlyChanged) public: Vault(const Device &device, QObject *parent = nullptr); ~Vault(); typedef QHash Payload; bool isValid() const; FutureResult<> create(const QString &name, const MountPoint &mountPoint, const Payload &payload); FutureResult<> open(const Payload &payload); FutureResult<> close(); // FutureResult<> configure(); FutureResult<> forceClose(); - FutureResult<> destroy(const Payload &payload); + FutureResult<> dismantle(const Payload &payload); VaultInfo info() const; public Q_SLOTS: QString message() const; VaultInfo::Status status() const; bool isInitialized() const; bool isOpened() const; bool isBusy() const; Device device() const; QString backend() const; MountPoint mountPoint() const; void setMountPoint(const MountPoint &mountPoint); QString name() const; void setName(const QString &name); QStringList activities() const; void setActivities(const QStringList &activities); bool isOfflineOnly() const; void setIsOfflineOnly(bool isOfflineOnly); void saveConfiguration(); Q_SIGNALS: void mountPointChanged(const QString &mountPoint); void statusChanged(VaultInfo::Status status); void isInitializedChanged(bool isInitialized); void isOpenedChanged(bool isOpened); void isBusyChanged(bool isBusy); void activitiesChanged(const QStringList &activities); void messageChanged(const QString &message); void nameChanged(const QString &name); void isOfflineOnlyChanged(bool isOfflineOnly); void infoChanged(); public: static QList availableDevices(); static QStringList statusMessage(); private: class Private; QScopedPointer d; }; } // namespace PlasmaVault #endif // include guard diff --git a/kded/service.cpp b/kded/service.cpp index 1f96d70..bebdb71 100644 --- a/kded/service.cpp +++ b/kded/service.cpp @@ -1,383 +1,427 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "service.h" #include #include #include #include #include #include #include "engine/vault.h" #include "engine/commandresult.h" #include "ui/vaultcreationwizard.h" #include "ui/vaultconfigurationwizard.h" #include "ui/mountdialog.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(PlasmaVaultServiceFactory, "plasmavault.json", registerPlugin();) using namespace PlasmaVault; using AsynQt::Expected; class PlasmaVaultService::Private { public: QHash knownVaults; QSet openVaults; KActivities::Consumer kamd; struct NetworkingState { bool wasNetworkingEnabled; QVector devicesInhibittingNetworking; }; // Ideally, this would be std::optional... lovely C++17 Expected savedNetworkingState = Expected::error(0); void saveNetworkingState() { // Ignore the request if we already have a saved state if (savedNetworkingState) { return; } savedNetworkingState = Expected::success( NetworkingState { NetworkManager::isNetworkingEnabled() || true, {} }); } void restoreNetworkingState() { // Ignore the request if we do not have a state saved // or if there are more devices inhibitting networking if (!savedNetworkingState || !savedNetworkingState->devicesInhibittingNetworking.isEmpty()) { return; } NetworkManager::setNetworkingEnabled(savedNetworkingState->wasNetworkingEnabled); } Vault* vaultFor(const QString &device_) const { const Device device(device_); if (!knownVaults.contains(device)) { return nullptr; } return knownVaults[device]; } }; PlasmaVaultService::PlasmaVaultService(QObject * parent, const QVariantList&) : KDEDModule(parent) , d(new Private()) { connect(this, &KDEDModule::moduleRegistered, this, &PlasmaVaultService::slotRegistered); connect(&d->kamd, &KActivities::Consumer::currentActivityChanged, this, &PlasmaVaultService::onCurrentActivityChanged); init(); } PlasmaVaultService::~PlasmaVaultService() { } void PlasmaVaultService::init() { for (const Device &device: Vault::availableDevices()) { registerVault(new Vault(device, this)); } } PlasmaVault::VaultInfoList PlasmaVaultService::availableDevices() const { PlasmaVault::VaultInfoList result; for (const auto &vault: d->knownVaults.values()) { const auto vaultData = vault->info(); result << vaultData; } return result; } void PlasmaVaultService::requestNewVault() { const auto dialog = new VaultCreationWizard(); connect(dialog, &VaultCreationWizard::createdVault, this, &PlasmaVaultService::registerVault); dialog->show(); } void PlasmaVaultService::slotRegistered(const QDBusObjectPath &path) { if (path.path() == QLatin1String("/modules/plasmavault")) { emit registered(); } } void PlasmaVaultService::registerVault(Vault *vault) { if (!vault->isValid()) { qWarning() << "Warning: Trying to register an invalid vault: " << vault->device(); return; } if (d->knownVaults.contains(vault->device())) { qWarning() << "Warning: This one is already registered: " << vault->device(); return; } vault->setParent(this); d->knownVaults[vault->device()] = vault; connect(vault, &Vault::statusChanged, this, &PlasmaVaultService::onVaultStatusChanged); connect(vault, &Vault::messageChanged, this, &PlasmaVaultService::onVaultMessageChanged); connect(vault, &Vault::infoChanged, this, &PlasmaVaultService::onVaultInfoChanged); emit vaultAdded(vault->info()); if (vault->status() == VaultInfo::Opened) { d->openVaults << vault->device(); } } +void PlasmaVaultService::forgetVault(Vault* vault) +{ + // Can not be open + // d->openVaults.remove(vault.device()); + // and therefore can not inhibit networking + // ... d->savedNetworkingState ... + + emit vaultRemoved(vault->device().data()); + + d->knownVaults.remove(vault->device()); + vault->deleteLater(); +} + + + void PlasmaVaultService::onVaultStatusChanged(VaultInfo::Status status) { const auto vault = qobject_cast(sender()); - if (status == VaultInfo::Opened) { + if (status == VaultInfo::Dismantled) { + forgetVault(vault); + + } else if (status == VaultInfo::Opened) { d->openVaults << vault->device(); if (d->openVaults.size() == 1) { emit hasOpenVaultsChanged(true); } + } else { d->openVaults.remove(vault->device()); if (d->openVaults.isEmpty()) { emit hasOpenVaultsChanged(false); } + } if (vault->isOfflineOnly()) { d->saveNetworkingState(); auto& devicesInhibittingNetworking = d->savedNetworkingState->devicesInhibittingNetworking; // We need to check whether this vault // should be added or removed from the // inhibitors list const bool alreadyInhibiting = devicesInhibittingNetworking.contains(vault->device()); if (status == VaultInfo::Opened && !alreadyInhibiting) { devicesInhibittingNetworking << vault->device(); } if (status != VaultInfo::Opened && alreadyInhibiting) { devicesInhibittingNetworking.removeAll(vault->device()); } // qDebug() << "Vaults inhibitting networking: " << devicesInhibittingNetworking; // Now, let's handle the networking part if (!devicesInhibittingNetworking.isEmpty()) { NetworkManager::setNetworkingEnabled(false); } d->restoreNetworkingState(); } emit vaultChanged(vault->info()); } void PlasmaVaultService::onVaultInfoChanged() { const auto vault = qobject_cast(sender()); emit vaultChanged(vault->info()); } void PlasmaVaultService::onVaultMessageChanged(const QString &message) { Q_UNUSED(message); const auto vault = qobject_cast(sender()); emit vaultChanged(vault->info()); } void showPasswordMountDialog(Vault *vault, const std::function &function) { auto dialog = new MountDialog(vault, function); dialog->open(); } //^ void PlasmaVaultService::openVault(const QString &device) { if (auto vault = d->vaultFor(device)) { if (vault->isOpened()) return; showPasswordMountDialog(vault, [this, vault] { emit vaultChanged(vault->info()); }); } } void PlasmaVaultService::closeVault(const QString &device) { if (auto vault = d->vaultFor(device)) { if (!vault->isOpened()) return; vault->close(); } } void PlasmaVaultService::configureVault(const QString &device) { if (auto vault = d->vaultFor(device)) { const auto dialog = new VaultConfigurationWizard(vault); // connect(dialog, &VaultConfigurationWizard::configurationChanged, // this, &PlasmaVaultService::registerVault); dialog->show(); } } void PlasmaVaultService::forceCloseVault(const QString &device) { if (auto vault = d->vaultFor(device)) { if (!vault->isOpened()) return; vault->forceClose(); } } void PlasmaVaultService::openVaultInFileManager(const QString &device) { if (auto vault = d->vaultFor(device)) { if (vault->isOpened()) { new KRun(QUrl::fromLocalFile((QString)vault->mountPoint()), 0); } else { showPasswordMountDialog(vault, [this, vault] { emit vaultChanged(vault->info()); new KRun(QUrl::fromLocalFile((QString)vault->mountPoint()), 0); }); } } } void PlasmaVaultService::onCurrentActivityChanged( const QString ¤tActivity) { for (auto* vault: d->knownVaults.values()) { const auto vaultActivities = vault->activities(); if (!vaultActivities.isEmpty() && !vaultActivities.contains(currentActivity)) { vault->close(); } } } bool PlasmaVaultService::hasOpenVaults() const { return !d->openVaults.isEmpty(); } void PlasmaVaultService::closeAllVaults() { for (const auto& device: d->openVaults) { closeVault(device.data()); } } void PlasmaVaultService::forceCloseAllVaults() { for (const auto& device: d->openVaults) { forceCloseVault(device.data()); } } + +void PlasmaVaultService::deleteVault(const QString &device, const QString &name) +{ + if (!d->knownVaults.contains(Device(device))) { + qWarning() << "The specified vault does not exist: " << device; + return; + } + + auto vault = d->knownVaults[Device(device)]; + + if (vault->status() == VaultInfo::Opened) { + qWarning() << "Can not delete an open vault: " << device; + return; + } + + if (vault->name() != name) { + qWarning() << "Name is not correct: " << device; + return; + } + + vault->dismantle({}); +} + + #include "service.moc" diff --git a/kded/service.h b/kded/service.h index f77c1c4..8686f06 100644 --- a/kded/service.h +++ b/kded/service.h @@ -1,82 +1,85 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_KDED_SERVICE_H #define PLASMAVAULT_KDED_SERVICE_H #include #include #include #include class Q_DECL_EXPORT PlasmaVaultService : public KDEDModule { Q_CLASSINFO("D-Bus Interface", "org.kde.plasmavault") Q_OBJECT public: PlasmaVaultService(QObject *parent, const QVariantList&); ~PlasmaVaultService(); public Q_SLOTS: Q_SCRIPTABLE void init(); Q_SCRIPTABLE void requestNewVault(); Q_SCRIPTABLE void openVault(const QString &device); Q_SCRIPTABLE void closeVault(const QString &device); Q_SCRIPTABLE void forceCloseVault(const QString &device); Q_SCRIPTABLE void configureVault(const QString &device); Q_SCRIPTABLE void openVaultInFileManager(const QString &device); Q_SCRIPTABLE PlasmaVault::VaultInfoList availableDevices() const; Q_SCRIPTABLE bool hasOpenVaults() const; Q_SCRIPTABLE void closeAllVaults(); Q_SCRIPTABLE void forceCloseAllVaults(); + Q_SCRIPTABLE void deleteVault(const QString &device, const QString &name); + Q_SIGNALS: void registered(); Q_SCRIPTABLE void vaultAdded(const PlasmaVault::VaultInfo &vaultData); Q_SCRIPTABLE void vaultRemoved(const QString &device); Q_SCRIPTABLE void vaultChanged(const PlasmaVault::VaultInfo &vaultData); Q_SCRIPTABLE void hasOpenVaultsChanged(bool hasOpenVaults); private Q_SLOTS: void slotRegistered(const QDBusObjectPath &path); void registerVault(PlasmaVault::Vault *vault); + void forgetVault(PlasmaVault::Vault *vault); void onVaultStatusChanged(PlasmaVault::VaultInfo::Status status); void onVaultMessageChanged(const QString &message); void onVaultInfoChanged(); void onCurrentActivityChanged(const QString ¤tActivity); private: class Private; QScopedPointer d; }; #endif // include guard diff --git a/kded/ui/dialogdsl.cpp b/kded/ui/dialogdsl.cpp index aa88f61..124b84d 100644 --- a/kded/ui/dialogdsl.cpp +++ b/kded/ui/dialogdsl.cpp @@ -1,144 +1,158 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dialogdsl.h" #include namespace DialogDsl { Key::Key(const QByteArray &key, const QString &translation) : QByteArray(key) , m_translation(translation) { } QString Key::translation() const { return m_translation; } DialogModule::DialogModule(bool valid) : m_isValid(valid) { } void DialogModule::setIsValid(bool valid) { if (valid == m_isValid) return; m_isValid = valid; emit isValidChanged(valid); } bool DialogModule::shouldBeShown() const { return true; } void DialogModule::aboutToBeShown() { } void DialogModule::aboutToBeHidden() { } bool DialogModule::isValid() const { return m_isValid; } void DialogModule::init(const PlasmaVault::Vault::Payload &payload) { } CompoundDialogModule::CompoundDialogModule(const step &children) : DialogDsl::DialogModule(false) { auto layout = new QVBoxLayout(this); setLayout(layout); - bool valid = true; - for (const auto& childFactory: children) { auto child = childFactory(); child->setParent(this); child->setSizePolicy( QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); m_children << child; - valid &= child->isValid(); + if (!child->isValid()) { + m_invalidChildren << child; + } + + connect(child, &DialogModule::isValidChanged, + this, [this, child] (bool valid) { + if (valid) { + m_invalidChildren.remove(child); + } else { + m_invalidChildren << child; + } + + setIsValid(m_invalidChildren.isEmpty()); + }); + + connect(child, &DialogModule::requestCancellation, + this, &DialogModule::requestCancellation); layout->addWidget(child); } - setIsValid(valid); + setIsValid(m_invalidChildren.isEmpty()); auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addSpacerItem(spacer); } PlasmaVault::Vault::Payload CompoundDialogModule::fields() const { PlasmaVault::Vault::Payload result; for (const auto& child: m_children) { result.unite(child->fields()); } return result; } void CompoundDialogModule::init(const PlasmaVault::Vault::Payload &payload) { for (const auto& child: m_children) { child->init(payload); } } } // namespace DialogDsl diff --git a/kded/ui/dialogdsl.h b/kded/ui/dialogdsl.h index 3583b7b..60220af 100644 --- a/kded/ui/dialogdsl.h +++ b/kded/ui/dialogdsl.h @@ -1,130 +1,132 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLASMAVAULT_KDED_UI_DIALOG_DSL_H #define PLASMAVAULT_KDED_UI_DIALOG_DSL_H #include #include #include #include #include #include namespace DialogDsl { // We want to have a normal ID for the QMap or QHash, // but we also want it to have a translated name class Key: public QByteArray { public: Key(const QByteArray &id, const QString &translation = QString()); QString translation() const; private: QString m_translation; }; namespace operators { // A nicer way to define a Key and its translation inline Key operator/ (const char *id, const QString &name) { return Key(id, name); } } // A dialog module can return a set of configured key-value pairs class DialogModule: public QWidget { Q_OBJECT Q_PROPERTY(bool isValid READ isValid WRITE setIsValid NOTIFY isValidChanged) public: DialogModule(bool isValid); virtual PlasmaVault::Vault::Payload fields() const = 0; virtual void init(const PlasmaVault::Vault::Payload &payload); virtual void aboutToBeShown(); virtual bool shouldBeShown() const; virtual void aboutToBeHidden(); bool isValid() const; void setIsValid(bool valid); Q_SIGNALS: void isValidChanged(bool valid); + void requestCancellation(); private: bool m_isValid; }; typedef std::function ModuleFactory; class step: public QVector { public: step() = default; step(const std::initializer_list& modules) : QVector(modules) { } friend step operator/(const QString &title, step &&from) { step result(std::move(from)); result.m_title = title; return result; } QString title() const { return m_title; } private: QString m_title; }; typedef QVector steps; typedef QMap Logic; // If we want to have a single page containing multiple modules class CompoundDialogModule: public DialogModule { public: CompoundDialogModule(const step &children); PlasmaVault::Vault::Payload fields() const override; void init(const PlasmaVault::Vault::Payload &payload) override; private: QVector m_children; + QSet m_invalidChildren; }; } // namespace DialogDsl #endif // include guard diff --git a/kded/ui/directorychooserwidget.cpp b/kded/ui/directorychooserwidget.cpp index e965639..d5cca06 100644 --- a/kded/ui/directorychooserwidget.cpp +++ b/kded/ui/directorychooserwidget.cpp @@ -1,102 +1,104 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "directorychooserwidget.h" #include "ui_directorychooserwidget.h" #include "vault.h" class DirectoryChooserWidget::Private { public: Ui::DirectoryChooserWidget ui; DirectoryChooserWidget::Flags flags; bool mountPointValid = false; DirectoryChooserWidget *const q; Private(DirectoryChooserWidget *parent) : q(parent) { } void setMountPointValid(bool valid) { if (mountPointValid == valid) return; mountPointValid = valid; q->setIsValid(valid); } bool isDirectoryValid(const QUrl &url) const { if (url.isEmpty()) return false; QDir directory(url.toString()); // TODO: Support alternative flags once they are needed if (!directory.exists() || directory.entryList().isEmpty()) return true; return false; } }; DirectoryChooserWidget::DirectoryChooserWidget( DirectoryChooserWidget::Flags flags) : DialogDsl::DialogModule(false), d(new Private(this)) { d->ui.setupUi(this); d->flags = flags; connect(d->ui.editMountPoint, &KUrlRequester::textEdited, this, [&] () { d->setMountPointValid(d->isDirectoryValid(d->ui.editMountPoint->url())); }); } DirectoryChooserWidget::~DirectoryChooserWidget() { } PlasmaVault::Vault::Payload DirectoryChooserWidget::fields() const { return { { KEY_MOUNT_POINT, d->ui.editMountPoint->url().toLocalFile() } }; } void DirectoryChooserWidget::init( const PlasmaVault::Vault::Payload &payload) { const auto mountPoint = payload[KEY_MOUNT_POINT].toString(); d->ui.editMountPoint->setText(mountPoint); + + d->setMountPointValid(d->isDirectoryValid(d->ui.editMountPoint->url())); } diff --git a/kded/ui/namechooserwidget.cpp b/kded/ui/namechooserwidget.cpp index 6aa5c6b..e7a0f73 100644 --- a/kded/ui/namechooserwidget.cpp +++ b/kded/ui/namechooserwidget.cpp @@ -1,71 +1,77 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "namechooserwidget.h" #include "ui_namechooserwidget.h" #include "vault.h" class NameChooserWidget::Private { public: Ui::NameChooserWidget ui; NameChooserWidget *const q; Private(NameChooserWidget *parent) : q(parent) { } }; NameChooserWidget::NameChooserWidget() : DialogDsl::DialogModule(false), d(new Private(this)) { d->ui.setupUi(this); + + connect(d->ui.editVaultName, &QLineEdit::textChanged, + this, [this] (const QString &text) { + setIsValid(!d->ui.editVaultName->text().isEmpty()); + }); } NameChooserWidget::~NameChooserWidget() { } PlasmaVault::Vault::Payload NameChooserWidget::fields() const { return { { KEY_NAME, d->ui.editVaultName->text() } }; } void NameChooserWidget::init( const PlasmaVault::Vault::Payload &payload) { const auto name = payload[KEY_NAME].toString(); d->ui.editVaultName->setText(name); + setIsValid(!d->ui.editVaultName->text().isEmpty()); } diff --git a/kded/ui/vaultconfigurationwizard.cpp b/kded/ui/vaultconfigurationwizard.cpp index 3dd34d8..1849b57 100644 --- a/kded/ui/vaultconfigurationwizard.cpp +++ b/kded/ui/vaultconfigurationwizard.cpp @@ -1,189 +1,203 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "vaultconfigurationwizard.h" #include "ui_vaultconfigurationwizard.h" #include #include #include #include #include #include "dialogdsl.h" #include "vault.h" using namespace DialogDsl; using namespace DialogDsl::operators; #include "backendchooserwidget.h" #include "activitieslinkingwidget.h" #include "cryfscypherchooserwidget.h" #include "directorychooserwidget.h" #include "noticewidget.h" #include "namechooserwidget.h" #include "passwordchooserwidget.h" #include "offlineonlywidget.h" +#include "vaultdeletionwidget.h" using PlasmaVault::Vault; class VaultConfigurationWizard::Private { public: VaultConfigurationWizard *const q; Vault *vault; Ui::VaultConfigurationWizard ui; QStackedLayout *layout; steps currentSteps; QVector currentModuleDialogs; + QSet invalidModules; steps defaultSteps { i18n("General") / step { nameChooser(), directoryChooser(DirectoryChooserWidget::RequireEmptyMountPoint) }, i18n("Advanced") / step { activitiesChooser(), offlineOnlyChooser() - } + }, - /* - i18n("Dismantle") / step { - notice( - "dismantle-message", - i18n("Note that Plasma Vault will not delete any of the files,\n\ - the dismantling process only removes the vault from Plasma.\n\ - You will need to remove the files manually."), - NoticeWidget::ShowAlways) + i18n("Delete") / step { + vaultDeletion() } - */ }; Logic logic { { "encfs" / i18n("EncFS"), defaultSteps }, { "cryfs" / i18n("CryFS"), defaultSteps } }; Private(Vault *vault, VaultConfigurationWizard *parent) : q(parent) , vault(vault) { ui.setupUi(parent); ui.message->hide(); layout = new QStackedLayout(); layout->setContentsMargins(0, 0, 0, 0); ui.container->setLayout(layout); auto tabs = new QTabWidget(); layout->addWidget(tabs); // Loading the backends auto modules = logic[Key(vault->backend().toLatin1())]; Vault::Payload payload { + { KEY_DEVICE, QVariant(vault->device()) }, { KEY_NAME, QVariant(vault->name()) }, { KEY_MOUNT_POINT, QVariant(vault->mountPoint()) }, { KEY_ACTIVITIES, QVariant(vault->activities()) }, { KEY_OFFLINEONLY, QVariant(vault->isOfflineOnly()) } }; for (const auto& module: modules) { DialogModule *stepWidget = new CompoundDialogModule(module); stepWidget->init(payload); tabs->addTab(stepWidget, module.title()); currentModuleDialogs << stepWidget; + + QObject::connect( + stepWidget, &DialogModule::isValidChanged, + q, [this,stepWidget] (bool valid) { + if (valid) { + invalidModules.remove(stepWidget); + } else { + invalidModules << stepWidget; + } + + ui.buttons->button(QDialogButtonBox::Ok)->setEnabled(invalidModules.isEmpty()); + }); + + QObject::connect( + stepWidget, &DialogModule::requestCancellation, + q, [this] { + q->reject(); + }); } } void setVaultOpened(bool vaultOpened) { bool configurationEnabled = !vaultOpened; ui.buttons->button(QDialogButtonBox::Ok)->setEnabled(configurationEnabled); ui.frameUnlockVault->setVisible(!configurationEnabled); ui.container->setEnabled(configurationEnabled); } void saveConfiguration() { Vault::Payload collectedPayload; qDebug() << "Getting the data"; for (const auto* module: currentModuleDialogs) { qDebug() << "Data: " << module->fields(); collectedPayload.unite(module->fields()); } const auto name = collectedPayload[KEY_NAME].toString(); const PlasmaVault::MountPoint mountPoint(collectedPayload[KEY_MOUNT_POINT].toString()); const auto activities = collectedPayload[KEY_ACTIVITIES].toStringList(); const auto isOfflineOnly = collectedPayload[KEY_OFFLINEONLY].toBool(); if (name.isEmpty() || mountPoint.isEmpty()) return; vault->setName(name); vault->setMountPoint(mountPoint); vault->setActivities(activities); vault->setIsOfflineOnly(isOfflineOnly); } }; VaultConfigurationWizard::VaultConfigurationWizard(Vault *vault, QWidget *parent) : QDialog(parent) , d(new Private(vault, this)) { setWindowTitle(i18n("Configure")); d->setVaultOpened(vault->isOpened()); connect(d->ui.buttonCloseVault, &QPushButton::clicked, this, [=] () { vault->close(); }); connect(vault, &Vault::isOpenedChanged, this, [=] (bool isOpened) { d->setVaultOpened(isOpened); }); connect(d->ui.buttons, &QDialogButtonBox::accepted, this, [=] { d->saveConfiguration(); }); } VaultConfigurationWizard::~VaultConfigurationWizard() { } diff --git a/kded/ui/vaultdeletionwidget.cpp b/kded/ui/vaultdeletionwidget.cpp new file mode 100644 index 0000000..32d92a7 --- /dev/null +++ b/kded/ui/vaultdeletionwidget.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2017 by Ivan Cukic + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "vaultdeletionwidget.h" + +#include "ui_vaultdeletionwidget.h" + +#include +#include +#include + +#include +#include + +class VaultDeletionWidget::Private { +public: + Ui::VaultDeletionWidget ui; + QString vaultName; + QString vaultDevice; + KSharedConfig::Ptr config; +}; + + + +VaultDeletionWidget::VaultDeletionWidget() + : DialogDsl::DialogModule(true) + , d(new Private()) +{ + d->ui.setupUi(this); + + auto messageWidget = new KMessageWidget(d->ui.labelWarning->text(), this); + messageWidget->setMessageType(KMessageWidget::Warning); + messageWidget->setCloseButtonVisible(false); + messageWidget->setIcon(QIcon::fromTheme("dialog-warning")); + static_cast(layout())->insertWidget(0, messageWidget); + + d->ui.labelWarning->hide(); + + connect(d->ui.textVaultNameConfirmation, &QLineEdit::textEdited, + this, [this] (const QString &newText) { + d->ui.buttonDeleteVault->setEnabled(d->vaultName == newText); + }); + + connect(d->ui.buttonDeleteVault, &QPushButton::clicked, + this, [this] { + d->ui.buttonDeleteVault->setEnabled(false); + emit requestCancellation(); + + QDBusInterface( + QStringLiteral("org.kde.kded5"), + QStringLiteral("/modules/plasmavault"), + QStringLiteral("org.kde.plasmavault")) + .asyncCall("deleteVault", d->vaultDevice, d->vaultName); + + }); + +} + + + +VaultDeletionWidget::~VaultDeletionWidget() +{ +} + + + +PlasmaVault::Vault::Payload VaultDeletionWidget::fields() const +{ + return {}; +} + +void VaultDeletionWidget::init(const PlasmaVault::Vault::Payload &payload) +{ + d->vaultName = payload[KEY_NAME].toString(); + d->vaultDevice = payload[KEY_DEVICE].toString(); + d->ui.buttonDeleteVault->setEnabled(false); +} + diff --git a/kded/ui/namechooserwidget.cpp b/kded/ui/vaultdeletionwidget.h similarity index 57% copy from kded/ui/namechooserwidget.cpp copy to kded/ui/vaultdeletionwidget.h index 6aa5c6b..a202f1a 100644 --- a/kded/ui/namechooserwidget.cpp +++ b/kded/ui/vaultdeletionwidget.h @@ -1,71 +1,51 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "namechooserwidget.h" -#include "ui_namechooserwidget.h" - -#include "vault.h" - -class NameChooserWidget::Private { -public: - Ui::NameChooserWidget ui; - - NameChooserWidget *const q; - Private(NameChooserWidget *parent) - : q(parent) - { - } -}; +#ifndef PLASMAVAULT_KDED_UI_DELETION_WIDGET_H +#define PLASMAVAULT_KDED_UI_DELETION_WIDGET_H +#include "dialogdsl.h" +class VaultDeletionWidget: public DialogDsl::DialogModule { + Q_OBJECT -NameChooserWidget::NameChooserWidget() - : DialogDsl::DialogModule(false), d(new Private(this)) -{ - d->ui.setupUi(this); -} +public: + VaultDeletionWidget(); + ~VaultDeletionWidget(); + PlasmaVault::Vault::Payload fields() const override; -NameChooserWidget::~NameChooserWidget() -{ -} - + void init(const PlasmaVault::Vault::Payload &payload) override; +private: + class Private; + QScopedPointer d; +}; -PlasmaVault::Vault::Payload NameChooserWidget::fields() const +inline DialogDsl::ModuleFactory vaultDeletion() { - return { - { KEY_NAME, d->ui.editVaultName->text() } + return [=] { + return new VaultDeletionWidget(); }; } - - -void NameChooserWidget::init( - const PlasmaVault::Vault::Payload &payload) -{ - const auto name = payload[KEY_NAME].toString(); - - d->ui.editVaultName->setText(name); -} - - +#endif // include guard diff --git a/kded/ui/vaultdeletionwidget.ui b/kded/ui/vaultdeletionwidget.ui new file mode 100644 index 0000000..c611538 --- /dev/null +++ b/kded/ui/vaultdeletionwidget.ui @@ -0,0 +1,65 @@ + + + VaultDeletionWidget + + + + 0 + 0 + 652 + 150 + + + + + + + This action <b>cannot</b> be undone. This will permanently delete the selected vault! + + + + + + + Please type in the name of the vault to confirm: + + + + + + + + + + + + false + + + Delete this vault + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/plasma/vaultsmodel.cpp b/plasma/vaultsmodel.cpp index aae6e68..2f5c65b 100644 --- a/plasma/vaultsmodel.cpp +++ b/plasma/vaultsmodel.cpp @@ -1,440 +1,440 @@ /* * Copyright 2017 by Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "vaultsmodel.h" #include "vaultsmodel_p.h" #include #include #include using namespace PlasmaVault; using namespace AsynQt; using namespace AsynQt::operators; VaultsModel::Private::Private(VaultsModel *parent) : service( "org.kde.kded5" , "/modules/plasmavault" , QDBusConnection::sessionBus() ) , serviceWatcher( "org.kde.kded5" , QDBusConnection::sessionBus() ) , q(parent) { connect(&service, &org::kde::plasmavault::vaultAdded, this, &Private::onVaultAdded); connect(&service, &org::kde::plasmavault::vaultChanged, this, &Private::onVaultChanged); connect(&service, &org::kde::plasmavault::vaultRemoved, this, &Private::onVaultRemoved); connect(&serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this] (const QString &service, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); if (service != "org.kde.kded5") { return; } // If kded is not running, just clear all vault info, // otherwise load the data if (newOwner.isEmpty()) { clearData(); } else { loadData(); } }); // Try to load the data. This should start kded if it is not running // for some reason loadData(); } void VaultsModel::Private::loadData() { // Before loading the new data, lets forget everything clearData(); // Asynchronously load the devices DBus::asyncCall(&service, "availableDevices") | onSuccess([this] (const VaultInfoList &vaultList) { q->beginResetModel(); vaults.clear(); vaultKeys.clear(); busyVaults.clear(); errorVaults.clear(); for (const auto& vault: vaultList) { vaults[vault.device] = vault; vaultKeys << vault.device; if (vault.isBusy()) { busyVaults << vault.device; } if (!vault.message.isEmpty()) { errorVaults << vault.device; } } q->endResetModel(); emit q->isBusyChanged(busyVaults.count() != 0); emit q->hasErrorChanged(errorVaults.count() != 0); }); } void VaultsModel::Private::clearData() { q->beginResetModel(); vaultKeys.clear(); vaults.clear(); q->endResetModel(); } void VaultsModel::Private::onVaultAdded(const PlasmaVault::VaultInfo &vaultInfo) { const auto device = vaultInfo.device; if (vaults.contains(device)) return; q->beginInsertRows(QModelIndex(), vaultKeys.size(), vaultKeys.size()); vaults[device] = vaultInfo; vaultKeys << device; q->endInsertRows(); } void VaultsModel::Private::onVaultRemoved(const QString &device) { - if (vaults.contains(device)) return; + if (!vaults.contains(device)) return; const auto row = vaultKeys.indexOf(device); q->beginRemoveRows(QModelIndex(), row, row); vaultKeys.removeAt(row); vaults.remove(device); q->endRemoveRows(); } void VaultsModel::Private::onVaultChanged( const PlasmaVault::VaultInfo &vaultInfo) { const auto device = vaultInfo.device; if (!vaultKeys.contains(device)) return; const auto row = vaultKeys.indexOf(device); // Lets see whether this warrants updates to the busy flag if (vaultInfo.isBusy() && !busyVaults.contains(device)) { busyVaults << device; if (busyVaults.count() == 1) { emit q->isBusyChanged(true); } } if (!vaultInfo.isBusy() && busyVaults.contains(device)) { busyVaults.remove(device); if (busyVaults.count() == 0) { emit q->isBusyChanged(false); } } // Lets see whether this warrants updates to the error flag if (!vaultInfo.message.isEmpty() && !errorVaults.contains(device)) { errorVaults << device; if (errorVaults.count() == 1) { emit q->hasErrorChanged(true); } } if (vaultInfo.message.isEmpty() && errorVaults.contains(device)) { errorVaults.remove(device); if (errorVaults.count() == 0) { emit q->hasErrorChanged(false); } } vaults[device] = vaultInfo; q->dataChanged(q->index(row), q->index(row)); } VaultsModel::VaultsModel(QObject *parent) : QAbstractListModel(parent) , d(new Private(this)) { } VaultsModel::~VaultsModel() { } int VaultsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return d->vaultKeys.size(); } QVariant VaultsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return {}; } const int row = index.row(); if (row >= d->vaultKeys.count()) { return {}; } const auto device = d->vaultKeys[row]; const auto &vault = d->vaults[device]; switch (role) { case VaultDevice: return vault.device; case VaultMountPoint: return vault.mountPoint; case VaultName: return vault.name.isEmpty() ? vault.device : vault.name; case VaultIcon: { QFileInfo fileInfo(vault.device); if (!fileInfo.exists()) { return "document-close"; } else switch (vault.status) { case VaultInfo::Error: return "document-close"; case VaultInfo::NotInitialized: return "folder-gray"; case VaultInfo::Closed: return "folder-encrypted"; case VaultInfo::Opened: return "folder-decrypted"; default: return ""; } } case VaultIsBusy: return vault.isBusy(); case VaultIsOpened: return vault.isOpened(); case VaultActivities: return vault.activities; case VaultIsOfflineOnly: return vault.isOfflineOnly; case VaultMessage: return vault.message; } return {}; } QHash VaultsModel::roleNames() const { return { { VaultName, "name" }, { VaultIcon, "icon" }, { VaultDevice, "device" }, { VaultMountPoint, "mountPoint" }, { VaultIsBusy, "isBusy" }, { VaultIsOpened, "isOpened" }, { VaultActivities, "activities" }, { VaultIsOfflineOnly, "isOfflineOnly" }, { VaultStatus, "status" }, { VaultMessage, "message" } }; } void VaultsModel::refresh() { d->loadData(); } void VaultsModel::open(const QString &device) { if (!d->vaults.contains(device)) return; DBus::asyncCall<>(&d->service, "openVault", device); } void VaultsModel::close(const QString &device) { if (!d->vaults.contains(device)) return; DBus::asyncCall<>(&d->service, "closeVault", device); } void VaultsModel::toggle(const QString &device) { if (!d->vaults.contains(device)) return; const auto &vault = d->vaults[device]; if (vault.status == VaultInfo::Opened) { close(device); } else if (vault.status == VaultInfo::Closed) { open(device); } } void VaultsModel::forceClose(const QString &device) { if (!d->vaults.contains(device)) return; DBus::asyncCall<>(&d->service, "forceCloseVault", device); } void VaultsModel::configure(const QString &device) { if (!d->vaults.contains(device)) return; DBus::asyncCall<>(&d->service, "configureVault", device); } void VaultsModel::openInFileManager(const QString &device) { if (!d->vaults.contains(device)) return; DBus::asyncCall<>(&d->service, "openVaultInFileManager", device); } void VaultsModel::requestNewVault() { DBus::asyncCall<>(&d->service, "requestNewVault"); } bool VaultsModel::isBusy() const { return !d->busyVaults.isEmpty(); } bool VaultsModel::hasError() const { return !d->errorVaults.isEmpty(); } SortedVaultsModelProxy::SortedVaultsModelProxy(QObject *parent) : QSortFilterProxyModel(parent) , m_source(new VaultsModel(this)) , m_kamd(new KActivities::Consumer(this)) { setSourceModel(m_source); connect(m_kamd, &KActivities::Consumer::currentActivityChanged, this, &SortedVaultsModelProxy::invalidate); connect(m_kamd, &KActivities::Consumer::serviceStatusChanged, this, &SortedVaultsModelProxy::invalidate); } bool SortedVaultsModelProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { const auto leftData = sourceModel()->data(left, VaultsModel::VaultName); const auto rightData = sourceModel()->data(right, VaultsModel::VaultName); return leftData < rightData; } bool SortedVaultsModelProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { Q_UNUSED(sourceParent); const auto activities = m_source->index(sourceRow).data(VaultsModel::VaultActivities).toStringList(); const auto isOpened = m_source->index(sourceRow).data(VaultsModel::VaultIsOpened).toBool(); return activities.size() == 0 || isOpened || activities.contains(m_kamd->currentActivity()); } QObject *SortedVaultsModelProxy::source() const { return sourceModel(); }