From de7843b484ac93dd6d7950ac4a6621f2fbc0f524 Mon Sep 17 00:00:00 2001 From: Skycoder42 Date: Thu, 1 Mar 2018 19:52:56 +0100 Subject: [PATCH] added first basic datasync viewmodel (no gui yet) --- examples/mvvmcore/SampleCore/samplecore_de.ts | 244 ++++++++++ src/mvvmcore/coreapp.cpp | 36 +- src/mvvmcore/message.cpp | 36 +- src/mvvmcore/message.h | 26 +- src/mvvmcore/settingsviewmodel.cpp | 10 +- src/mvvmdatasynccore/accountmodel.cpp | 131 ++++++ src/mvvmdatasynccore/accountmodel.h | 51 +++ src/mvvmdatasynccore/accountmodel_p.h | 20 + .../application-x-datasync-account-data.xml | 11 + src/mvvmdatasynccore/datasyncviewmodel.cpp | 422 ++++++++++++++++++ src/mvvmdatasynccore/datasyncviewmodel.h | 86 ++++ src/mvvmdatasynccore/datasyncviewmodel_p.h | 32 ++ src/mvvmdatasynccore/exportsetupviewmodel.cpp | 82 ++++ src/mvvmdatasynccore/exportsetupviewmodel_p.h | 53 +++ src/mvvmdatasynccore/mvvmdatasynccore.pro | 43 ++ src/mvvmdatasynccore/qpmx.json | 14 + .../qtmvvmdatasynccore_global.h | 12 + .../translations/qtmvvmdatasynccore_de.ts | 4 + .../qtmvvmdatasynccore_template.ts | 4 + src/mvvmquick/quickpresenter.cpp | 4 +- src/mvvmwidgets/inputwidgetfactory.cpp | 2 +- src/mvvmwidgets/widgetspresenter.cpp | 6 +- src/src.pro | 4 + sync.profile | 3 +- 24 files changed, 1286 insertions(+), 50 deletions(-) create mode 100644 examples/mvvmcore/SampleCore/samplecore_de.ts create mode 100644 src/mvvmdatasynccore/accountmodel.cpp create mode 100644 src/mvvmdatasynccore/accountmodel.h create mode 100644 src/mvvmdatasynccore/accountmodel_p.h create mode 100644 src/mvvmdatasynccore/application-x-datasync-account-data.xml create mode 100644 src/mvvmdatasynccore/datasyncviewmodel.cpp create mode 100644 src/mvvmdatasynccore/datasyncviewmodel.h create mode 100644 src/mvvmdatasynccore/datasyncviewmodel_p.h create mode 100644 src/mvvmdatasynccore/exportsetupviewmodel.cpp create mode 100644 src/mvvmdatasynccore/exportsetupviewmodel_p.h create mode 100644 src/mvvmdatasynccore/mvvmdatasynccore.pro create mode 100644 src/mvvmdatasynccore/qpmx.json create mode 100644 src/mvvmdatasynccore/qtmvvmdatasynccore_global.h create mode 100644 src/mvvmdatasynccore/translations/qtmvvmdatasynccore_de.ts create mode 100644 src/mvvmdatasynccore/translations/qtmvvmdatasynccore_template.ts diff --git a/examples/mvvmcore/SampleCore/samplecore_de.ts b/examples/mvvmcore/SampleCore/samplecore_de.ts new file mode 100644 index 0000000..0c7643d --- /dev/null +++ b/examples/mvvmcore/SampleCore/samplecore_de.ts @@ -0,0 +1,244 @@ + + + + + DrawerViewModel + + + Main Sample + + + + + Tab Sample + + + + + Settings + + + + + SampleViewModel + + + Random input + + + + + Enter a number: + + + + + Open Files: + + + + + Clear Eventlist + + + + + Do you really want to clear the eventlist? + + + + + QtMvvm sample application + + + + + BSD 3 Clause + + + + + TabItemViewModel + + + No Title + + + + + TabViewModel + + + New Tab + + + + + Enter a tab title: + + + + + qtmvvm_settings_xml + + + property + + + + + Text 1 + + + + + Value C + + + + + Value A + + + + + Another Section + + + + + Value B + + + + + Enter a nice name + + + + + Value B+C + + + + + Variant A + + + + + This is a tooltip + + + + + Variant C + + + + + Value A+B + + + + + Sub-Group + + + + + &Check me + + + + + Enter a &value + + + + + The value must be between 0 and 1 + + + + + Open &system settings + + + + + Another main category + + + + + Value A+C + + + + + Variant B + + + + + Choose a &font + + + + + This is another section + + + + + bool + + + + + Select a &mode + + + + + I am a checkbox! + + + + + Value A+B+C + + + + + Enter a &number + + + + + You can use this to trigger whatever kind of action you need + + + + + Text 2 + + + + + Enter a &website + + + + + Enter a &name + + + + + https://example.org/test + + + + diff --git a/src/mvvmcore/coreapp.cpp b/src/mvvmcore/coreapp.cpp index 3097a21..6c89a74 100644 --- a/src/mvvmcore/coreapp.cpp +++ b/src/mvvmcore/coreapp.cpp @@ -104,20 +104,20 @@ bool CoreApp::autoParse(QCommandLineParser &parser, const QStringList &arguments if(parser.parse(arguments)) { if(parser.isSet(QStringLiteral("help"))) { MessageConfig helpConfig {MessageConfig::TypeMessageBox, MessageConfig::SubTypeQuestion}; - helpConfig.setTitle(tr("Help")); - helpConfig.setText(parser.helpText()); - helpConfig.setButtons(MessageConfig::Ok); + helpConfig.setTitle(tr("Help")) + .setText(parser.helpText()) + .setButtons(MessageConfig::Ok); auto res = showDialog(helpConfig); connect(res, &MessageResult::dialogDone, qApp, &QCoreApplication::quit); return false; } else if(parser.isSet(QStringLiteral("version"))) { MessageConfig versionConfig {MessageConfig::TypeMessageBox, MessageConfig::SubTypeInformation}; - versionConfig.setTitle(tr("Application Version")); - versionConfig.setText(QGuiApplication::applicationDisplayName() + - QLatin1Char(' ') + - QCoreApplication::applicationVersion()); - versionConfig.setButtons(MessageConfig::Ok); + versionConfig.setTitle(tr("Application Version")) + .setText(QGuiApplication::applicationDisplayName() + + QLatin1Char(' ') + + QCoreApplication::applicationVersion()) + .setButtons(MessageConfig::Ok); auto res = showDialog(versionConfig); connect(res, &MessageResult::dialogDone, qApp, &QCoreApplication::quit); @@ -126,9 +126,9 @@ bool CoreApp::autoParse(QCommandLineParser &parser, const QStringList &arguments return true; } else { MessageConfig errorConfig {MessageConfig::TypeMessageBox, MessageConfig::SubTypeCritical}; - errorConfig.setTitle(tr("Invalid Arguments")); - errorConfig.setText(parser.errorText()); - errorConfig.setButtons(MessageConfig::Ok); + errorConfig.setTitle(tr("Invalid Arguments")) + .setText(parser.errorText()) + .setButtons(MessageConfig::Ok); auto res = showDialog(errorConfig); connect(res, &MessageResult::dialogDone, qApp, [](){ @@ -197,11 +197,21 @@ void CoreAppPrivate::showViewModel(const QMetaObject *metaObject, const QVariant void CoreAppPrivate::showDialog(const MessageConfig &config, MessageResult *result) { - if(presenter) - presenter->showDialog(config, result); + if(presenter) { + try { + presenter->showDialog(config, result); + } catch(QException &e) { + logCritical() << "Failed to show dialog ff type" + << config.type() << ":" << config.subType() + << "with error:" + << e.what(); + result->complete(MessageConfig::NoButton); + } + } else { logCritical() << "Failed to show dialog ff type" << config.type() << ":" << config.subType() << "- no presenter was set"; + result->complete(MessageConfig::NoButton); } } diff --git a/src/mvvmcore/message.cpp b/src/mvvmcore/message.cpp index ff75bb2..c4bf300 100644 --- a/src/mvvmcore/message.cpp +++ b/src/mvvmcore/message.cpp @@ -82,57 +82,67 @@ QVariantMap MessageConfig::viewProperties() const return d->editProperties; } -void MessageConfig::setType(const QByteArray &type) +MessageConfig &MessageConfig::setType(const QByteArray &type) { d->type = type; + return (*this); } -void MessageConfig::setSubType(const QByteArray &subType) +MessageConfig &MessageConfig::setSubType(const QByteArray &subType) { d->subType = subType; + return (*this); } -void MessageConfig::setTitle(const QString &title) +MessageConfig &MessageConfig::setTitle(const QString &title) { d->title = title; + return (*this); } -void MessageConfig::setText(const QString &text) +MessageConfig &MessageConfig::setText(const QString &text) { d->text = text; + return (*this); } -void MessageConfig::setButtons(StandardButtons buttons) +MessageConfig &MessageConfig::setButtons(StandardButtons buttons) { d->buttons = buttons; + return (*this); } -void MessageConfig::setButtonTexts(const QHash &buttonTexts) +MessageConfig &MessageConfig::setButtonTexts(const QHash &buttonTexts) { d->buttonTexts = buttonTexts; + return (*this); } -void MessageConfig::setButtonText(MessageConfig::StandardButton button, const QString &text) +MessageConfig &MessageConfig::setButtonText(MessageConfig::StandardButton button, const QString &text) { d->buttonTexts.insert(button, text); + return (*this); } -void MessageConfig::setDefaultValue(const QVariant &defaultValue) +MessageConfig &MessageConfig::setDefaultValue(const QVariant &defaultValue) { d->defaultValue = defaultValue; + return (*this); } -void MessageConfig::setViewProperties(const QVariantMap &editProperties) +MessageConfig &MessageConfig::setViewProperties(const QVariantMap &editProperties) { d->editProperties = editProperties; + return (*this); } -void MessageConfig::setViewProperty(const QString &key, const QVariant &value) +MessageConfig &MessageConfig::setViewProperty(const QString &key, const QVariant &value) { d->editProperties.insert(key, value); + return (*this); } -void MessageConfig::resetSubType() +MessageConfig &MessageConfig::resetSubType() { if(d->type == TypeMessageBox) d->subType = SubTypeInformation; @@ -140,9 +150,10 @@ void MessageConfig::resetSubType() d->subType = QMetaType::typeName(QMetaType::QString); else d->subType.clear(); + return (*this); } -void MessageConfig::resetButtons() +MessageConfig &MessageConfig::resetButtons() { if(d->type == TypeMessageBox) { if(d->subType == SubTypeQuestion) @@ -157,6 +168,7 @@ void MessageConfig::resetButtons() d->buttons = Ok; d->buttonTexts.clear(); + return (*this); } QVariantMap MessageConfig::buttonTextsMap() const diff --git a/src/mvvmcore/message.h b/src/mvvmcore/message.h index 461ae83..057d812 100644 --- a/src/mvvmcore/message.h +++ b/src/mvvmcore/message.h @@ -87,19 +87,19 @@ public: QVariant defaultValue() const; QVariantMap viewProperties() const; - void setType(const QByteArray &type); - void setSubType(const QByteArray &subType); - void setTitle(const QString &title); - void setText(const QString &text); - void setButtons(StandardButtons buttons); - void setButtonTexts(const QHash &buttonTexts); - void setButtonText(StandardButton button, const QString &text); - void setDefaultValue(const QVariant &defaultValue); - void setViewProperties(const QVariantMap &viewProperties); - void setViewProperty(const QString &key, const QVariant &value); - - void resetSubType(); - void resetButtons(); + MessageConfig &setType(const QByteArray &type); + MessageConfig &setSubType(const QByteArray &subType); + MessageConfig &setTitle(const QString &title); + MessageConfig &setText(const QString &text); + MessageConfig &setButtons(StandardButtons buttons); + MessageConfig &setButtonTexts(const QHash &buttonTexts); + MessageConfig &setButtonText(StandardButton button, const QString &text); + MessageConfig &setDefaultValue(const QVariant &defaultValue); + MessageConfig &setViewProperties(const QVariantMap &viewProperties); + MessageConfig &setViewProperty(const QString &key, const QVariant &value); + + MessageConfig &resetSubType(); + MessageConfig &resetButtons(); private: QSharedDataPointer d; diff --git a/src/mvvmcore/settingsviewmodel.cpp b/src/mvvmcore/settingsviewmodel.cpp index ad9d1c3..2addcf0 100644 --- a/src/mvvmcore/settingsviewmodel.cpp +++ b/src/mvvmcore/settingsviewmodel.cpp @@ -31,12 +31,10 @@ bool SettingsViewModel::canRestoreDefaults() const MessageConfig SettingsViewModel::restoreConfig() const { - MessageConfig config; - config.setType(MessageConfig::TypeMessageBox); - config.setSubType(MessageConfig::SubTypeWarning); - config.setTitle(tr("Restore Defaults?")); - config.setText(tr("All custom changes will be deleted and the defaults restored. This cannot be undone!")); - config.setButtons(MessageConfig::Yes | MessageConfig::No); + MessageConfig config {MessageConfig::TypeMessageBox, MessageConfig::SubTypeWarning}; + config.setTitle(tr("Restore Defaults?")) + .setText(tr("All custom changes will be deleted and the defaults restored. This cannot be undone!")) + .setButtons(MessageConfig::Yes | MessageConfig::No); return config; } diff --git a/src/mvvmdatasynccore/accountmodel.cpp b/src/mvvmdatasynccore/accountmodel.cpp new file mode 100644 index 0000000..ed56cde --- /dev/null +++ b/src/mvvmdatasynccore/accountmodel.cpp @@ -0,0 +1,131 @@ +#include "accountmodel.h" +#include "accountmodel_p.h" + +#include + +using namespace QtMvvm; +using namespace QtDataSync; + +AccountModel::AccountModel(QObject *parent) : + QAbstractListModel(parent), + d(new AccountModelPrivate()) +{} + +AccountModel::~AccountModel() {} + +void AccountModel::setup(AccountManager *manager) +{ + beginResetModel(); + d->devices.clear(); + if(d->manager) + d->manager->disconnect(this); + d->manager = manager; + endResetModel(); + + connect(d->manager, &AccountManager::accountDevices, + this, &AccountModel::accountDevices); + connect(d->manager, &AccountManager::importAccepted, + this, &AccountModel::reload); + connect(d->manager, &AccountManager::accountAccessGranted, + this, &AccountModel::reload); + + if(d->manager->replica()->isInitialized()) + d->manager->listDevices(); + else { + connect(d->manager->replica(), &QRemoteObjectReplica::initialized, + d->manager, &AccountManager::listDevices); + } + +} + +QVariant AccountModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation != Qt::Horizontal) + return {}; + + switch (section) { + case 0: + switch(role) { + case NameRole: + return tr("Name"); + case FingerPrintRole: + return tr("Fingerprint"); + default: + break; + } + break; + case 1: + if(role == Qt::DisplayRole) + return tr("Fingerprint"); + break; + default: + break; + } + + return {}; +} + +int AccountModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + else + return d->devices.size(); +} + +QVariant AccountModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + switch (index.column()) { + case 0: + switch(role) { + case NameRole: + return d->devices.value(index.row()).name(); + case FingerPrintRole: + return d->devices.value(index.row()).fingerprint(); + default: + break; + } + break; + case 1: + if(role == Qt::DisplayRole) + return d->devices.value(index.row()).fingerprint(); + break; + default: + break; + } + + return {}; +} + +bool AccountModel::removeDevice(const QModelIndex &index) +{ + if (!index.isValid()) + return false; + else { + d->manager->removeDevice(d->devices.value(index.row())); + return true; + } +} + +void AccountModel::reload() +{ + if(d->manager) + d->manager->listDevices(); +} + +void AccountModel::accountDevices(const QList &devices) +{ + beginResetModel(); + d->devices = devices; + endResetModel(); +} + +// ------------- Private Implementation ------------- + +AccountModelPrivate::AccountModelPrivate() : + manager(nullptr), + devices() +{} diff --git a/src/mvvmdatasynccore/accountmodel.h b/src/mvvmdatasynccore/accountmodel.h new file mode 100644 index 0000000..4aa8d56 --- /dev/null +++ b/src/mvvmdatasynccore/accountmodel.h @@ -0,0 +1,51 @@ +#ifndef QTMVVM_ACCOUNTMODEL_H +#define QTMVVM_ACCOUNTMODEL_H + +#include +#include + +#include + +#include "QtMvvmDataSyncCore/qtmvvmdatasynccore_global.h" + +namespace QtMvvm { + +class AccountModelPrivate; +class Q_MVVMDATASYNCCORE_EXPORT AccountModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + NameRole = Qt::DisplayRole, + FingerPrintRole = Qt::UserRole + 1 + }; + Q_ENUM(Roles) + + explicit AccountModel(QObject *parent = nullptr); + ~AccountModel(); + + Q_INVOKABLE void setup(QtDataSync::AccountManager *manager); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + Q_INVOKABLE bool removeDevice(const QModelIndex &index); + Q_INVOKABLE inline bool removeDevice(int row) { + return removeDevice(index(row)); + } + +public Q_SLOTS: + void reload(); + +private Q_SLOTS: + void accountDevices(const QList &devices); + +private: + QScopedPointer d; +}; + +} + +#endif // QTMVVM_ACCOUNTMODEL_H diff --git a/src/mvvmdatasynccore/accountmodel_p.h b/src/mvvmdatasynccore/accountmodel_p.h new file mode 100644 index 0000000..fa9a8d6 --- /dev/null +++ b/src/mvvmdatasynccore/accountmodel_p.h @@ -0,0 +1,20 @@ +#ifndef QTMVVM_ACCOUNTMODEL_P_H +#define QTMVVM_ACCOUNTMODEL_P_H + +#include "qtmvvmdatasynccore_global.h" +#include "accountmodel.h" + +namespace QtMvvm { + +class AccountModelPrivate +{ +public: + AccountModelPrivate(); + + QtDataSync::AccountManager *manager; + QList devices; +}; + +} + +#endif // QTMVVM_ACCOUNTMODEL_P_H diff --git a/src/mvvmdatasynccore/application-x-datasync-account-data.xml b/src/mvvmdatasynccore/application-x-datasync-account-data.xml new file mode 100644 index 0000000..fe06a74 --- /dev/null +++ b/src/mvvmdatasynccore/application-x-datasync-account-data.xml @@ -0,0 +1,11 @@ + + + + Account data of a datasync account as file export + Account data of a datasync account as file export + Account-Daten eines Datasync-Accounts als Dateiexport + + + + + diff --git a/src/mvvmdatasynccore/datasyncviewmodel.cpp b/src/mvvmdatasynccore/datasyncviewmodel.cpp new file mode 100644 index 0000000..ac524d4 --- /dev/null +++ b/src/mvvmdatasynccore/datasyncviewmodel.cpp @@ -0,0 +1,422 @@ +#include "datasyncviewmodel.h" +#include "datasyncviewmodel_p.h" +#include "exportsetupviewmodel_p.h" + +#include +#include + +#include + +#include +#include + +#include + +#undef logDebug +#undef logInfo +#undef logWarning +#undef logCritical +#include + +using namespace QtMvvm; +using namespace QtDataSync; + +const QString DataSyncViewModel::paramSetup(QStringLiteral("setup")); +const QString DataSyncViewModel::paramReplicaNode(QStringLiteral("node")); + +QVariantHash DataSyncViewModel::showParams(const QString &setup) +{ + return { + {paramSetup, setup} + }; +} + +QVariantHash DataSyncViewModel::showParams(QRemoteObjectNode *node) +{ + return { + {paramReplicaNode, QVariant::fromValue(node)} + }; +} + +DataSyncViewModel::DataSyncViewModel(QObject *parent) : + ViewModel(parent), + d(new DataSyncViewModelPrivate(this)) +{ + resetColorMap(); +} + +DataSyncViewModel::~DataSyncViewModel() {} + +SyncManager *DataSyncViewModel::syncManager() const +{ + return d->syncManager; +} + +AccountManager *DataSyncViewModel::accountManager() const +{ + return d->accountManager; +} + +DataSyncViewModel::ColorMap DataSyncViewModel::colorMap() const +{ + return d->colorMap; +} + +QString DataSyncViewModel::statusString() const +{ + if(!d->syncManager) + return {}; + auto state = d->syncManager->syncState(); + + QString baseText = QStringLiteral("%2") + .arg(d->colorMap.value(state).name()); + switch (state) { + case SyncManager::Initializing: + return baseText.arg(tr("Connecting…")); + case SyncManager::Downloading: + return baseText.arg(tr("Downloading…")); + case SyncManager::Uploading: + return baseText.arg(tr("Uploading…")); + case SyncManager::Synchronized: + return baseText.arg(tr("Synchronized")); + case SyncManager::Error: + return baseText.arg(tr("Error!")); + case SyncManager::Disconnected: + return baseText.arg(tr("Disconnected")); + default: + Q_UNREACHABLE(); + return QString(); + } +} + +AccountModel *DataSyncViewModel::accountModel() const +{ + return d->accountModel; +} + +QString DataSyncViewModel::formatFingerPrint(const QByteArray &fingerPrint) +{ + QByteArrayList res; + for(char c : fingerPrint) + res.append(QByteArray(1, c).toHex().toUpper()); + return QString::fromUtf8(res.join(':')); +} + +void DataSyncViewModel::showDeviceInfo() +{ + Q_UNIMPLEMENTED(); +} + +void DataSyncViewModel::startExport() +{ + showForResult(DataSyncViewModelPrivate::ExportRequestCode); +} + +void DataSyncViewModel::startImport() +{ + auto home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + getOpenFile(this, [this](QUrl url) { + if(url.isValid()) { + QSharedPointer device; + //TODO add support for android content device + if(url.isLocalFile()) + device.reset(new QFile(url.toLocalFile())); + else { + critical(tr("Import failed"), tr("Unsupported URL Scheme: %1").arg(url.scheme())); + return; + } + + if(!device->open(QIODevice::ReadOnly | QIODevice::Text)) { + critical(tr("Import failed"), + tr("Failed to open URL \"%1\" with error: %1") + .arg(url.toString()) + .arg(device->errorString())); + return; + } + auto data = device->readAll(); + device->close(); + + if(AccountManager::isTrustedImport(data)) { + MessageConfig config{MessageConfig::TypeInputDialog, QMetaType::typeName(QMetaType::QString)}; + config.setTitle(tr("Import account data")) + .setText(tr("Enter the password to decrypt the account data. " + "Then choose whether you want to keep you local data or not:")) + .setButtons(MessageConfig::YesToAll | MessageConfig::Yes | MessageConfig::Cancel) //TODO adjust to get ideal placement + .setButtonText(MessageConfig::YesToAll, tr("Reset data")) + .setButtonText(MessageConfig::Yes, tr("Keep data")) + .setViewProperties({ + {QStringLiteral("echoMode"), 2} //QLineEdit::Password + }); + auto res = CoreApp::showDialog(config); + connect(res, &MessageResult::dialogDone, this, [this, res, data](MessageConfig::StandardButton btn) { + switch (btn) { + case MessageConfig::YesToAll: + d->performImport(true, res->result().toString(), data, false); + break; + case MessageConfig::Yes: + d->performImport(true, res->result().toString(), data, true); + break; + default: + break; + } + }); + } else { + MessageConfig config{MessageConfig::TypeMessageBox, MessageConfig::SubTypeQuestion}; + config.setTitle(tr("Import account data")) + .setText(tr("Keep the local data after changing the account?")) + .setButtons(MessageConfig::YesToAll | MessageConfig::Yes | MessageConfig::Cancel) //TODO adjust to get ideal placement + .setButtonText(MessageConfig::YesToAll, tr("Reset data")) + .setButtonText(MessageConfig::Yes, tr("Keep data")); + auto res = CoreApp::showDialog(config); + connect(res, &MessageResult::dialogDone, this, [this, res, data](MessageConfig::StandardButton btn) { + switch (btn) { + case MessageConfig::YesToAll: + d->performImport(false, {}, data, false); + break; + case MessageConfig::Yes: + d->performImport(false, {}, data, true); + break; + default: + break; + } + }); + } + } + }, tr("Import account data"), + {QStringLiteral("application/x-datasync-account-data"), QStringLiteral("application/octet-stream")}, + QUrl::fromLocalFile(home)); +} + +void DataSyncViewModel::performReset() +{ + MessageConfig config {MessageConfig::TypeMessageBox, MessageConfig::SubTypeQuestion}; + config.setTitle(tr("Reset Account?")) + .setText(tr("Do you want to reset your account? " + "You will loose the connection to all other devices and get a new identity." + "You can either keep your data or reset it as well." + "This cannot be undone!")) + .setButtons(MessageConfig::YesToAll | MessageConfig::Yes | MessageConfig::Cancel) //TODO adjust to get ideal placement + .setButtonText(MessageConfig::YesToAll, tr("Reset data")) + .setButtonText(MessageConfig::Yes, tr("Keep data")); + auto res = CoreApp::showDialog(config); + connect(res, &MessageResult::dialogDone, this, [this](MessageConfig::StandardButton btn) { + switch (btn) { + case MessageConfig::YesToAll: + d->accountManager->resetAccount(false); + break; + case MessageConfig::Yes: + d->accountManager->resetAccount(true); + break; + default: + break; + } + }); +} + +void DataSyncViewModel::changeRemote() +{ + Q_UNIMPLEMENTED(); +} + +void DataSyncViewModel::startNetworkExchange() +{ + Q_UNIMPLEMENTED(); +} + +void DataSyncViewModel::setColorMap(DataSyncViewModel::ColorMap colorMap) +{ + if (d->colorMap == colorMap) + return; + + d->colorMap = colorMap; + emit colorMapChanged(d->colorMap); +} + +void DataSyncViewModel::resetColorMap() +{ + d->colorMap.clear(); + d->colorMap.insert(SyncManager::Initializing, Qt::darkCyan); + d->colorMap.insert(SyncManager::Downloading, Qt::darkCyan); + d->colorMap.insert(SyncManager::Uploading, Qt::darkCyan); + d->colorMap.insert(SyncManager::Synchronized, Qt::darkGreen); + d->colorMap.insert(SyncManager::Error, Qt::darkRed); + d->colorMap.insert(SyncManager::Disconnected, Qt::darkYellow); + emit colorMapChanged(d->colorMap); +} + +void DataSyncViewModel::showImportDialog(LoginRequest request) +{ + if(request.handled()) + return; + question(tr("Login requested!"), + tr("

A device wants to log into your account:

" + "

Name: %1
" + "Fingerprint: %2

" + "

Do you want accept the request?

") + .arg(request.device().name()) + .arg(formatFingerPrint(request.device().fingerprint())), + this, [this, request](bool ok) { + auto req = request; + if(!req.handled()) { + if(ok) + req.accept(); + else + req.reject(); + } + }); +} + +void DataSyncViewModel::showImportAccepted() +{ + information(tr("Import accepted"), + tr("The partner has accepted the import. You are now logged in.")); +} + +void DataSyncViewModel::showAccessGranted(const QUuid &id) +{ + d->pendingGrants.insert(id); + //no need to call list devices, is done by the model +} + +void DataSyncViewModel::triggerGranted(const QList &devices) +{ + for(auto device : devices) { + if(d->pendingGrants.remove(device.deviceId())) { + information(tr("Account access granted"), + tr("

Account access has been granted to device:

" + "

Name: %1
" + "Fingerprint: %2

") + .arg(device.name()) + .arg(formatFingerPrint(device.fingerprint()))); + } + } +} + +void DataSyncViewModel::onInit(const QVariantHash ¶ms) +{ + try { + if(params.contains(paramReplicaNode)) { + auto node = params.value(paramReplicaNode).value(); + d->syncManager = new SyncManager(node, this); + d->accountManager = new AccountManager(node, this); + } else { + auto setup = params.value(paramSetup, QtDataSync::DefaultSetup).toString(); + d->syncManager = new SyncManager(setup, this); + d->accountManager = new AccountManager(setup, this); + } + + //sync manager + connect(d->syncManager, &SyncManager::syncStateChanged, + this, &DataSyncViewModel::statusStringChanged); + connect(d->syncManager, &SyncManager::lastErrorChanged, + this, &DataSyncViewModel::statusStringChanged); + + //account manager + connect(d->accountManager, &AccountManager::loginRequested, + this, &DataSyncViewModel::showImportDialog); + connect(d->accountManager, &AccountManager::importAccepted, + this, &DataSyncViewModel::showImportAccepted); + connect(d->accountManager, &AccountManager::accountAccessGranted, + this, &DataSyncViewModel::showAccessGranted); + connect(d->accountManager, &AccountManager::accountDevices, + this, &DataSyncViewModel::triggerGranted); + d->accountModel->setup(d->accountManager); + + emit syncManagerChanged(d->syncManager); + emit accountManagerChanged(d->accountManager); + } catch(SetupDoesNotExistException &e) { + logCritical() << "Failed to init DataSyncViewModel with error:" + << e.what(); + } +} + +void DataSyncViewModel::onResult(quint32 requestCode, const QVariant &result) +{ + switch (requestCode) { + case DataSyncViewModelPrivate::ExportRequestCode: + if(result.isValid()) { + auto res = ExportSetupViewModel::result(result); + d->performExport(std::get<0>(res), std::get<1>(res), std::get<2>(res)); + } + break; + default: + break; + } +} + +// ------------- Private Implementation ------------- + +QtMvvm::DataSyncViewModelPrivate::DataSyncViewModelPrivate(DataSyncViewModel *q_ptr) : + q(q_ptr), + syncManager(nullptr), + accountManager(nullptr), + colorMap(), + accountModel(new AccountModel(q_ptr)) +{} + +void DataSyncViewModelPrivate::performExport(bool trusted, bool includeServer, const QString &password) +{ + auto home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + getSaveFile(q, [this, trusted, includeServer, password](QUrl url) { + if(url.isValid()) { + QSharedPointer device; + //TODO add support for android content device + if(url.isLocalFile()) + device.reset(new QFile(url.toLocalFile())); + else { + critical(DataSyncViewModel::tr("Export failed"), + DataSyncViewModel::tr("Unsupported URL Scheme: %1").arg(url.scheme())); + return; + } + + if(!device->open(QIODevice::WriteOnly | QIODevice::Text)) { + critical(DataSyncViewModel::tr("Export failed"), + DataSyncViewModel::tr("Failed to open URL \"%1\" with error: %1") + .arg(url.toString()) + .arg(device->errorString())); + return; + } + + QPointer qPtr(q); + auto resFn = [this, qPtr, device](QByteArray data) { + if(!qPtr) + return; + device->write(data); + device->close(); + information(DataSyncViewModel::tr("Export completed"), + DataSyncViewModel::tr("Data was successfully exported.")); + }; + auto errFn = [this, qPtr, device](QString error){ + if(!qPtr) + return; + critical(DataSyncViewModel::tr("Export failed"), error); + }; + + if(trusted) + accountManager->exportAccountTrusted(includeServer, password, resFn, errFn); + else + accountManager->exportAccount(includeServer, resFn, errFn); + } + }, DataSyncViewModel::tr("Export account data"), + {QStringLiteral("application/x-datasync-account-data"), QStringLiteral("application/octet-stream")}, + QUrl::fromLocalFile(home)); +} + +void DataSyncViewModelPrivate::performImport(bool trusted, const QString &password, const QByteArray &data, bool keepData) +{ + QPointer qPtr(q); + auto resFn = [this, qPtr](bool ok, QString error) { + if(!qPtr) + return; + if(ok) { + information(DataSyncViewModel::tr("Import completed"), + DataSyncViewModel::tr("Data was successfully imported.")); + } else + critical(DataSyncViewModel::tr("Import failed"), error); + }; + + if(trusted) + accountManager->importAccountTrusted(data, password, resFn, keepData); + else + accountManager->importAccount(data, resFn, keepData); +} diff --git a/src/mvvmdatasynccore/datasyncviewmodel.h b/src/mvvmdatasynccore/datasyncviewmodel.h new file mode 100644 index 0000000..8bd1651 --- /dev/null +++ b/src/mvvmdatasynccore/datasyncviewmodel.h @@ -0,0 +1,86 @@ +#ifndef QTMVVM_DATASYNCVIEWMODEL_H +#define QTMVVM_DATASYNCVIEWMODEL_H + +#include +#include + +#include + +#include + +#include +#include + +#include "QtMvvmDataSyncCore/qtmvvmdatasynccore_global.h" +#include "QtMvvmDataSyncCore/accountmodel.h" + +namespace QtMvvm { + +class DataSyncViewModelPrivate; +class Q_MVVMDATASYNCCORE_EXPORT DataSyncViewModel : public ViewModel +{ + Q_OBJECT + + Q_PROPERTY(QtDataSync::SyncManager* syncManager READ syncManager NOTIFY syncManagerChanged) + Q_PROPERTY(QtDataSync::AccountManager* accountManager READ accountManager NOTIFY accountManagerChanged) + + Q_PROPERTY(ColorMap colorMap READ colorMap WRITE setColorMap RESET resetColorMap NOTIFY colorMapChanged) + Q_PROPERTY(QString statusString READ statusString NOTIFY statusStringChanged) + + Q_PROPERTY(QtMvvm::AccountModel* accountModel READ accountModel CONSTANT) + +public: + typedef QMap ColorMap; + + static const QString paramSetup; + static const QString paramReplicaNode; + + static QVariantHash showParams(const QString &setup); + static QVariantHash showParams(QRemoteObjectNode *node); + + Q_INVOKABLE explicit DataSyncViewModel(QObject *parent = nullptr); + ~DataSyncViewModel(); + + QtDataSync::SyncManager* syncManager() const; + QtDataSync::AccountManager* accountManager() const; + ColorMap colorMap() const; + QString statusString() const; + + AccountModel *accountModel() const; + + Q_INVOKABLE static QString formatFingerPrint(const QByteArray &fingerPrint); + +public Q_SLOTS: + void showDeviceInfo(); + void startExport(); + void startImport(); + void performReset(); + void changeRemote(); + void startNetworkExchange(); + + void setColorMap(ColorMap colorMap); + void resetColorMap(); + +private Q_SLOTS: + void showImportDialog(QtDataSync::LoginRequest request); + void showImportAccepted(); + void showAccessGranted(const QUuid &id); + void triggerGranted(const QList &devices); + +Q_SIGNALS: + void syncManagerChanged(QtDataSync::SyncManager* syncManager); + void accountManagerChanged(QtDataSync::AccountManager* accountManager); + void colorMapChanged(ColorMap colorMap); + void statusStringChanged(); + +protected: + void onInit(const QVariantHash ¶ms) override; + void onResult(quint32 requestCode, const QVariant &result) override; + +private: + QScopedPointer d; +}; + +} + +#endif // QTMVVM_DATASYNCVIEWMODEL_H diff --git a/src/mvvmdatasynccore/datasyncviewmodel_p.h b/src/mvvmdatasynccore/datasyncviewmodel_p.h new file mode 100644 index 0000000..09dc792 --- /dev/null +++ b/src/mvvmdatasynccore/datasyncviewmodel_p.h @@ -0,0 +1,32 @@ +#ifndef QTMVVM_DATASYNCVIEWMODEL_P_H +#define QTMVVM_DATASYNCVIEWMODEL_P_H + +#include + +#include "qtmvvmdatasynccore_global.h" +#include "datasyncviewmodel.h" + +namespace QtMvvm { + +class DataSyncViewModelPrivate +{ +public: + static const quint32 ExportRequestCode = 0xb201; + + DataSyncViewModelPrivate(DataSyncViewModel *q_ptr); + + DataSyncViewModel *q; + QtDataSync::SyncManager *syncManager; + QtDataSync::AccountManager *accountManager; + DataSyncViewModel::ColorMap colorMap; + AccountModel *accountModel; + + QSet pendingGrants; + + void performExport(bool trusted, bool includeServer, const QString &password); + void performImport(bool trusted, const QString &password, const QByteArray &data, bool keepData); +}; + +} + +#endif // QTMVVM_DATASYNCVIEWMODEL_P_H diff --git a/src/mvvmdatasynccore/exportsetupviewmodel.cpp b/src/mvvmdatasynccore/exportsetupviewmodel.cpp new file mode 100644 index 0000000..7e877e0 --- /dev/null +++ b/src/mvvmdatasynccore/exportsetupviewmodel.cpp @@ -0,0 +1,82 @@ +#include "exportsetupviewmodel_p.h" +using namespace QtMvvm; + +std::tuple ExportSetupViewModel::result(const QVariant &data) +{ + auto map = data.toHash(); + return std::make_tuple(map.value(QStringLiteral("trusted")).toBool(), + map.value(QStringLiteral("includeServer")).toBool(), + map.value(QStringLiteral("password")).toString()); +} + +ExportSetupViewModel::ExportSetupViewModel(QObject *parent) : + ViewModel(parent), + _trusted(false), + _includeServer(false), + _password() +{ + connect(this, &ExportSetupViewModel::trustedChanged, + this, &ExportSetupViewModel::validChanged); + connect(this, &ExportSetupViewModel::passwordChanged, + this, &ExportSetupViewModel::validChanged); +} + +bool ExportSetupViewModel::trusted() const +{ + return _trusted; +} + +bool ExportSetupViewModel::includeServer() const +{ + return _includeServer; +} + +QString ExportSetupViewModel::password() const +{ + return _password; +} + +bool ExportSetupViewModel::isValid() const +{ + return !_trusted || !_password.isEmpty(); +} + +bool ExportSetupViewModel::completeSetup() +{ + if(!isValid()) + return false; + + QVariantHash hash; + hash[QStringLiteral("trusted")] = _trusted; + hash[QStringLiteral("includeServer")] = _includeServer; + hash[QStringLiteral("password")] = _trusted ? _password : QString(); + emit resultReady(hash); + return true; +} + +void ExportSetupViewModel::setTrusted(bool trusted) +{ + if (_trusted == trusted) + return; + + _trusted = trusted; + emit trustedChanged(_trusted); +} + +void ExportSetupViewModel::setIncludeServer(bool includeServer) +{ + if (_includeServer == includeServer) + return; + + _includeServer = includeServer; + emit includeServerChanged(_includeServer); +} + +void ExportSetupViewModel::setPassword(QString password) +{ + if (_password == password) + return; + + _password = password; + emit passwordChanged(_password); +} diff --git a/src/mvvmdatasynccore/exportsetupviewmodel_p.h b/src/mvvmdatasynccore/exportsetupviewmodel_p.h new file mode 100644 index 0000000..500753e --- /dev/null +++ b/src/mvvmdatasynccore/exportsetupviewmodel_p.h @@ -0,0 +1,53 @@ +#ifndef QTMVVM_EXPORTSETUPVIEWMODEL_P_H +#define QTMVVM_EXPORTSETUPVIEWMODEL_P_H + +#include + +#include + +#include "qtmvvmdatasynccore_global.h" + +namespace QtMvvm { + +class Q_MVVMDATASYNCCORE_EXPORT ExportSetupViewModel : public ViewModel +{ + Q_OBJECT + + Q_PROPERTY(bool trusted READ trusted WRITE setTrusted NOTIFY trustedChanged) + Q_PROPERTY(bool includeServer READ includeServer WRITE setIncludeServer NOTIFY includeServerChanged) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) + + Q_PROPERTY(bool valid READ isValid NOTIFY validChanged) + +public: + static std::tuple result(const QVariant &data); + + Q_INVOKABLE explicit ExportSetupViewModel(QObject *parent = nullptr); + + bool trusted() const; + bool includeServer() const; + QString password() const; + bool isValid() const; + +public Q_SLOTS: + bool completeSetup(); + + void setTrusted(bool trusted); + void setIncludeServer(bool includeServer); + void setPassword(QString password); + +Q_SIGNALS: + void trustedChanged(bool trusted); + void includeServerChanged(bool includeServer); + void passwordChanged(QString password); + void validChanged(); + +private: + bool _trusted; + QString _password; + bool _includeServer; +}; + +} + +#endif // QTMVVM_EXPORTSETUPVIEWMODEL_P_H diff --git a/src/mvvmdatasynccore/mvvmdatasynccore.pro b/src/mvvmdatasynccore/mvvmdatasynccore.pro new file mode 100644 index 0000000..097f5a3 --- /dev/null +++ b/src/mvvmdatasynccore/mvvmdatasynccore.pro @@ -0,0 +1,43 @@ +TARGET = QtMvvmDataSyncCore + +QT = core gui mvvmcore datasync mvvmcore-private + +HEADERS += \ + qtmvvmdatasynccore_global.h \ + datasyncviewmodel.h \ + datasyncviewmodel_p.h \ + accountmodel.h \ + accountmodel_p.h \ + exportsetupviewmodel_p.h + +SOURCES += \ + datasyncviewmodel.cpp \ + accountmodel.cpp \ + exportsetupviewmodel.cpp + +TRANSLATIONS += \ + translations/qtmvvmdatasynccore_de.ts \ + translations/qtmvvmdatasynccore_template.ts + +DISTFILES += $$TRANSLATIONS \ + application-x-datasync-account-data.xml + +qpmx_ts_target.path = $$[QT_INSTALL_TRANSLATIONS] +qpmx_ts_target.depends += lrelease + +load(qt_module) + +win32 { + QMAKE_TARGET_PRODUCT = "$$TARGET" + QMAKE_TARGET_COMPANY = "Skycoder42" + QMAKE_TARGET_COPYRIGHT = "Felix Barz" +} else:mac { + QMAKE_TARGET_BUNDLE_PREFIX = "com.skycoder42." +} + +!ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): error(qpmx initialization failed. Check the compilation log for details.) +else: include($$OUT_PWD/qpmx_generated.pri) + +qpmx_ts_target.files -= $$OUT_PWD/$$QPMX_WORKINGDIR/qtmvvmdatasynccore_template.qm +qpmx_ts_target.files += translations/qtmvvmdatasynccore_template.ts + diff --git a/src/mvvmdatasynccore/qpmx.json b/src/mvvmdatasynccore/qpmx.json new file mode 100644 index 0000000..461fe91 --- /dev/null +++ b/src/mvvmdatasynccore/qpmx.json @@ -0,0 +1,14 @@ +{ + "dependencies": [], + "license": { + "file": "", + "name": "" + }, + "prcFile": "", + "priFile": "", + "priIncludes": [ + ], + "publishers": { + }, + "source": false +} diff --git a/src/mvvmdatasynccore/qtmvvmdatasynccore_global.h b/src/mvvmdatasynccore/qtmvvmdatasynccore_global.h new file mode 100644 index 0000000..55867c8 --- /dev/null +++ b/src/mvvmdatasynccore/qtmvvmdatasynccore_global.h @@ -0,0 +1,12 @@ +#ifndef QTMVVMDATASYNCCORE_GLOBAL_H +#define QTMVVMDATASYNCCORE_GLOBAL_H + +#include + +#if defined(QT_BUILD_MVVMDATASYNCCORE_LIB) +# define Q_MVVMDATASYNCCORE_EXPORT Q_DECL_EXPORT +#else +# define Q_MVVMDATASYNCCORE_EXPORT Q_DECL_IMPORT +#endif + +#endif // QTMVVMDATASYNCCORE_GLOBAL_H diff --git a/src/mvvmdatasynccore/translations/qtmvvmdatasynccore_de.ts b/src/mvvmdatasynccore/translations/qtmvvmdatasynccore_de.ts new file mode 100644 index 0000000..1552582 --- /dev/null +++ b/src/mvvmdatasynccore/translations/qtmvvmdatasynccore_de.ts @@ -0,0 +1,4 @@ + + + + diff --git a/src/mvvmdatasynccore/translations/qtmvvmdatasynccore_template.ts b/src/mvvmdatasynccore/translations/qtmvvmdatasynccore_template.ts new file mode 100644 index 0000000..6401616 --- /dev/null +++ b/src/mvvmdatasynccore/translations/qtmvvmdatasynccore_template.ts @@ -0,0 +1,4 @@ + + + + diff --git a/src/mvvmquick/quickpresenter.cpp b/src/mvvmquick/quickpresenter.cpp index 1c75749..846e3c0 100644 --- a/src/mvvmquick/quickpresenter.cpp +++ b/src/mvvmquick/quickpresenter.cpp @@ -64,7 +64,7 @@ void QuickPresenter::present(QtMvvm::ViewModel *viewModel, const QVariantHash &p Q_ARG(QtMvvm::ViewModel*, viewModel), Q_ARG(QVariantHash, params), Q_ARG(QUrl, url), - Q_ARG(QPointer, parent)); + Q_ARG(QPointer, parent)); //TODO invoke with result? } else throw PresenterException("QML presenter not ready - cannot present yet"); } @@ -74,7 +74,7 @@ void QuickPresenter::showDialog(const QtMvvm::MessageConfig &config, QtMvvm::Mes if(d->qmlPresenter) { QMetaObject::invokeMethod(d->qmlPresenter, "showDialog", Q_ARG(QtMvvm::MessageConfig, config), - Q_ARG(QtMvvm::MessageResult*, result)); + Q_ARG(QtMvvm::MessageResult*, result)); //TODO invoke with result? } else throw PresenterException("QML presenter not ready - cannot present yet"); } diff --git a/src/mvvmwidgets/inputwidgetfactory.cpp b/src/mvvmwidgets/inputwidgetfactory.cpp index 8ef4a6d..b9e20a8 100644 --- a/src/mvvmwidgets/inputwidgetfactory.cpp +++ b/src/mvvmwidgets/inputwidgetfactory.cpp @@ -66,7 +66,7 @@ QWidget *InputWidgetFactory::createInput(const QByteArray &type, QWidget *parent widget = new SelectComboBox(parent); else { logCritical() << "Failed to find any input view for input type:" << type; - return nullptr; + return nullptr; //TODO throw? } for(auto it = viewProperties.constBegin(); it != viewProperties.constEnd(); it++) diff --git a/src/mvvmwidgets/widgetspresenter.cpp b/src/mvvmwidgets/widgetspresenter.cpp index 348d0a9..59d3263 100644 --- a/src/mvvmwidgets/widgetspresenter.cpp +++ b/src/mvvmwidgets/widgetspresenter.cpp @@ -288,8 +288,10 @@ void WidgetsPresenter::presentMessageBox(const MessageConfig &config, QPointer result) { auto input = d->inputViewFactory->createInput(config.subType(), nullptr, config.viewProperties()); - if(!input) - return; + if(!input) { + throw PresenterException(QByteArrayLiteral("Unable to find an input for type") + + config.subType()); + } QWidget *parent = nullptr; if(!config.viewProperties().value(QStringLiteral("modal"), false).toBool()) diff --git a/src/src.pro b/src/src.pro index e4a4ccb..ed84261 100644 --- a/src/src.pro +++ b/src/src.pro @@ -6,5 +6,9 @@ SUBDIRS += mvvmcore \ mvvmquick \ imports +qtHaveModule(datasync) { + SUBDIRS += mvvmdatasynccore +} + prepareRecursiveTarget(lrelease) QMAKE_EXTRA_TARGETS += lrelease diff --git a/sync.profile b/sync.profile index e062f8d..caa885d 100644 --- a/sync.profile +++ b/sync.profile @@ -1,7 +1,8 @@ %modules = ( "QtMvvmCore" => "$basedir/src/mvvmcore", "QtMvvmWidgets" => "$basedir/src/mvvmwidgets", - "QtMvvmQuick" => "$basedir/src/mvvmquick" + "QtMvvmQuick" => "$basedir/src/mvvmquick", + "QtMvvmDataSyncCore" => "$basedir/src/mvvmdatasynccore" ); # Force generation of camel case headers for classes inside QtDataSync namespaces