diff --git a/kded/engine/vault.cpp b/kded/engine/vault.cpp index ce7a9ab..8c1abfe 100644 --- a/kded/engine/vault.cpp +++ b/kded/engine/vault.cpp @@ -1,680 +1,715 @@ /* * 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; + enum DeletionState { + Normal, + DeletionBlocked, + DeletionScheduled + }; + DeletionState deletionState = Normal; + 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 writeConfiguration() + { + if (data) { + + const auto deviceStr = device.data(); + const auto mountPointStr = data->mountPoint.data(); + + Q_ASSERT(!deviceStr.isEmpty() && !mountPointStr.isEmpty()); + + // Saving the data for the current mount + KConfigGroup generalConfig(config, "EncryptedDevices"); + generalConfig.writeEntry(deviceStr, true); + + KConfigGroup vaultConfig(config, deviceStr); + vaultConfig.writeEntry(CFG_LAST_STATUS, (int)data->status); + vaultConfig.writeEntry(CFG_MOUNT_POINT, mountPointStr); + vaultConfig.writeEntry(CFG_NAME, data->name); + vaultConfig.writeEntry(CFG_BACKEND, data->backend->name()); + + vaultConfig.writeEntry(CFG_ACTIVITIES, data->activities); + vaultConfig.writeEntry(CFG_OFFLINEONLY, data->isOfflineOnly); + + } else { + + 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); + + } + + config->sync(); + } + + void updateStatus() { if (data) { // Checking the status, and whether we should update it const auto oldStatus = data->status; if (oldStatus == VaultInfo::Dismantling) { // This means that the vault should be forgotten KConfigGroup generalConfig(config, "EncryptedDevices"); generalConfig.deleteEntry(device.data()); KConfigGroup vaultConfig(config, device.data()); vaultConfig.deleteGroup(); emit q->statusChanged(data->status = VaultInfo::Dismantled); } else { const auto newStatus = isOpened() ? VaultInfo::Opened : isInitialized() ? VaultInfo::Closed : VaultInfo::NotInitialized; if (oldStatus == newStatus) return; data->status = newStatus; emit q->statusChanged(data->status); - if (newStatus == VaultInfo::Closed || newStatus == VaultInfo::Opened) { + if (newStatus == VaultInfo::Closed + || newStatus == VaultInfo::Opened) { emit q->isOpenedChanged(newStatus == VaultInfo::Opened); } - if (oldStatus == VaultInfo::NotInitialized || newStatus == VaultInfo::NotInitialized) { + 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); - - 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()); - - vaultConfig.writeEntry(CFG_ACTIVITIES, data->activities); - vaultConfig.writeEntry(CFG_OFFLINEONLY, data->isOfflineOnly); + writeConfiguration(); 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); + writeConfiguration(); 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; + deletionState = DeletionBlocked; + return future | onSuccess([this] { updateStatus(); + + if (deletionState == DeletionScheduled) { + q->deleteLater(); + } + deletionState = Normal; }); } 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(); - + d->writeConfiguration(); emit infoChanged(); }); } Vault::~Vault() { if (d->isOpened()) { close(); } } +void Vault::scheduleDeletion() +{ + if (d->deletionState == Private::Normal) { + deleteLater(); + } else { + d->deletionState = Private::DeletionScheduled; + } +} + + + 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([] (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::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 dismantle it.")) : // otherwise 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::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 01f0035..afcfce6 100644 --- a/kded/engine/vault.h +++ b/kded/engine/vault.h @@ -1,139 +1,142 @@ /* * 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<> dismantle(const Payload &payload); VaultInfo info() const; + // TODO: Replace this with shared_ptr and enable_shared_from_this? + void scheduleDeletion(); + 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/ui/vaultcreationwizard.cpp b/kded/ui/vaultcreationwizard.cpp index 1b1977d..d5810a8 100644 --- a/kded/ui/vaultcreationwizard.cpp +++ b/kded/ui/vaultcreationwizard.cpp @@ -1,323 +1,323 @@ /* * 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 "vaultcreationwizard.h" #include "ui_vaultcreationwizard.h" #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 "directorypairchooserwidget.h" #include "noticewidget.h" #include "passwordchooserwidget.h" #include "offlineonlywidget.h" class VaultCreationWizard::Private { public: VaultCreationWizard *const q; Ui::VaultCreationWizard ui; QPushButton *buttonPrevious; QPushButton *buttonNext; QPushButton *buttonCreate; QStackedLayout *layout; inline void buttonNextSetEnabled(bool enabled) { buttonNext->setEnabled(enabled); buttonCreate->setEnabled(enabled); } QVector currentStepModules; steps currentSteps; BackendChooserWidget *firstStepModule = nullptr; DialogDsl::DialogModule *currentModule = nullptr; Logic logic { { "encfs" / i18n("EncFS"), { step { notice("encfs-message", i18n("Security notice:\n\ According to a security audit by Taylor Hornby (Defuse Security),\n\ the current implementation of Encfs is vulnerable or potentially vulnerable\n\ to multiple types of attacks.\n\ For example, an attacker with read/write access\n\ to encrypted data might lower the decryption complexity\n\ for subsequently encrypted data without this being noticed by a legitimate user,\n\ or might use timing analysis to deduce information.\n\

\n\ This means that you should not synchronize\n\ the encrypted data to a cloud storage service,\n\ or use it in other circumstances where the attacker\n\ can frequently access the encrypted data.\n\

\n\ See defuse.ca/audits/encfs.htm for more information.")) }, step { passwordChooser() }, step { directoryPairChooser( DirectoryPairChooserWidget::AutoFillPaths | DirectoryPairChooserWidget::ShowMountPointPicker | DirectoryPairChooserWidget::RequireNewMountPoint ) }, step { activitiesChooser(), offlineOnlyChooser() } } }, { "cryfs" / i18n("CryFS"), { step { notice("cryfs-message", i18n("Security notice:\n\ CryFS encrypts your files, so you can safely store them anywhere.\n\ It works well together with cloud services like Dropbox, iCloud, OneDrive and others.\n\

\n\ Unlike some other file-system overlay solutions,\n\ it does not expose the directory structure,\n\ the number of files nor the file sizes\n\ through the encrypted data format.\n\

\n\ One important thing to note is that,\n\ while CryFS is considered safe,\n\ there is no independent security audit\n\ which confirms this.")) }, step { passwordChooser() }, step { directoryPairChooser( DirectoryPairChooserWidget::AutoFillPaths | DirectoryPairChooserWidget::ShowDevicePicker | DirectoryPairChooserWidget::ShowMountPointPicker | DirectoryPairChooserWidget::RequireNewDevice | DirectoryPairChooserWidget::RequireNewMountPoint ) }, step { cryfsCypherChooser(), activitiesChooser(), offlineOnlyChooser() } } } }; // to suggest the highest priority to the user as a starting value QMap priorities = { { "encfs", 1 }, { "cryfs", 2 } }; template QPushButton *addDialogButton(const QString &icon, const QString &title, ClickHandler clickHandler) { auto button = new QPushButton(QIcon::fromTheme(icon), title); ui.buttons->addButton(button, QDialogButtonBox::ActionRole); QObject::connect(button, &QPushButton::clicked, q, clickHandler); return button; } Private(VaultCreationWizard *parent) : q(parent) { ui.setupUi(parent); ui.message->hide(); layout = new QStackedLayout(); layout->setContentsMargins(0, 0, 0, 0); ui.container->setLayout(layout); // The dialog buttons do not have previous/next by default // so we need to create them buttonPrevious = addDialogButton("go-previous", i18n("Previous"), [this] { previousStep(); }); buttonNext = addDialogButton("go-next", i18n("Next"), [this] { nextStep(); }); buttonCreate = addDialogButton("dialog-ok-apply", i18n("Create"), [this] { createVault(); }); // The 'Create' button should be hidden by default buttonCreate->hide(); buttonPrevious->setEnabled(false); buttonNextSetEnabled(false); // Loading the fist page of the wizard firstStepModule = new BackendChooserWidget(); setCurrentModule(firstStepModule); layout->addWidget(firstStepModule); // Loading the backends to the combo box for (const auto& key: logic.keys()) { firstStepModule->addItem(key, key.translation(), priorities.value(key)); } firstStepModule->checkBackendAvailable(); } void setCurrentModule(DialogDsl::DialogModule *module) { // If there is a current module already, disconnect it if (currentModule) { currentModule->aboutToBeHidden(); currentModule->disconnect(); } // The current module needs to be changed currentModule = module; currentModule->aboutToBeShown(); QObject::connect( currentModule, &DialogModule::isValidChanged, q, [&] (bool valid) { buttonNextSetEnabled(valid); }); // Lets update the button states // 1. next/create button is enabled only if the current // module is in the valid state buttonNextSetEnabled(currentModule->isValid()); // 2. previous button is enabled only if we are not on // the first page buttonPrevious->setEnabled(currentStepModules.size() > 0); // 3. If we have loaded the last page, we want to show the // 'Create' button instead of 'Next' if (!currentSteps.isEmpty() && currentStepModules.size() == currentSteps.size()) { buttonNext->hide(); buttonCreate->show(); } else { buttonNext->show(); buttonCreate->hide(); } // Calling to initialize the module -- we are passing all the // previously collected data to it auto collectedPayload = firstStepModule->fields(); for (const auto* module: currentStepModules) { collectedPayload.unite(module->fields()); } currentModule->init(collectedPayload); } void previousStep() { if (currentStepModules.isEmpty()) return; // We want to kill the current module, and move to the previous one currentStepModules.takeLast(); currentModule->deleteLater();; if (currentStepModules.size()) { setCurrentModule(currentStepModules.last()); } else { setCurrentModule(firstStepModule); } if (!currentModule->shouldBeShown()) { previousStep(); } } void nextStep() { // If the step modules are empty, this means that we // have just started - the user chose the backend // and we need to load the vault creation steps if (currentStepModules.isEmpty()) { const auto &fields = firstStepModule->fields(); currentSteps = logic[fields[KEY_BACKEND].toByteArray()]; } // Loading the modulws that we need to show now auto subModules = currentSteps[currentStepModules.size()]; // If there is only one module on the current page, // lets not complicate things by creating the compound module DialogModule *stepWidget = (subModules.size() == 1) ? subModules.first()() : new CompoundDialogModule(subModules); // Adding the widget to the list and the layout currentStepModules << stepWidget; layout->addWidget(stepWidget); layout->setCurrentWidget(stepWidget); // Set the newly added module to be the current setCurrentModule(stepWidget); if (!currentModule->shouldBeShown()) { nextStep(); } } void createVault() { auto collectedPayload = firstStepModule->fields(); for (const auto* module: currentStepModules) { collectedPayload.unite(module->fields()); } const auto name = collectedPayload[KEY_NAME].toString(); const PlasmaVault::Device device(collectedPayload[KEY_DEVICE].toString()); const PlasmaVault::MountPoint mountPoint(collectedPayload[KEY_MOUNT_POINT].toString()); auto vault = new PlasmaVault::Vault(device, q); auto future = vault->create(name, mountPoint, collectedPayload); auto result = AsynQt::await(future); if (result) { emit q->createdVault(vault); q->QDialog::accept(); } else { ui.message->setText(result.error().message()); ui.message->setMessageType(KMessageWidget::Error); ui.message->show(); - delete vault; + vault->scheduleDeletion(); } } }; VaultCreationWizard::VaultCreationWizard(QWidget *parent) : QDialog(parent) , d(new Private(this)) { setWindowTitle(i18nc("@title:window", "Create a New Vault")); } VaultCreationWizard::~VaultCreationWizard() { }