24 changed files with 1286 additions and 50 deletions
@ -0,0 +1,244 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!DOCTYPE TS> |
|||
<TS version="2.1" language="de_DE"> |
|||
<context> |
|||
<name>DrawerViewModel</name> |
|||
<message> |
|||
<location filename="drawerviewmodel.cpp" line="+11"/> |
|||
<source>Main Sample</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Tab Sample</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Settings</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
</context> |
|||
<context> |
|||
<name>SampleViewModel</name> |
|||
<message> |
|||
<location filename="sampleviewmodel.cpp" line="+81"/> |
|||
<source>Random input</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Enter a number:</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+12"/> |
|||
<source>Open Files:</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+12"/> |
|||
<source>Clear Eventlist</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Do you really want to clear the eventlist?</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+9"/> |
|||
<source>QtMvvm sample application</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+2"/> |
|||
<source>BSD 3 Clause</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
</context> |
|||
<context> |
|||
<name>TabItemViewModel</name> |
|||
<message> |
|||
<location filename="tabviewmodel.cpp" line="+28"/> |
|||
<source>No Title</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
</context> |
|||
<context> |
|||
<name>TabViewModel</name> |
|||
<message> |
|||
<location line="-13"/> |
|||
<source>New Tab</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+0"/> |
|||
<source>Enter a tab title:</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
</context> |
|||
<context> |
|||
<name>qtmvvm_settings_xml</name> |
|||
<message> |
|||
<location filename=".ts-dummy/settings.tsdummy.cpp" line="+4"/> |
|||
<source>property</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Text 1</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Value C</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Value A</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Another Section</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Value B</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Enter a nice name</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Value B+C</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Variant A</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>This is a tooltip</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Variant C</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Value A+B</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Sub-Group</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>&Check me</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Enter a &value</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>The value must be between 0 and 1</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Open &system settings</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Another main category</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Value A+C</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Variant B</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Choose a &font</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>This is another section</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>bool</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Select a &mode</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>I am a checkbox!</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Value A+B+C</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Enter a &number</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>You can use this to trigger whatever kind of action you need</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Text 2</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Enter a &website</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>Enter a &name</source> |
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
<message> |
|||
<location line="+1"/> |
|||
<source>https://example.org/test</source>
|
|||
<translation type="unfinished"></translation> |
|||
</message> |
|||
</context> |
|||
</TS> |
@ -0,0 +1,131 @@ |
|||
#include "accountmodel.h" |
|||
#include "accountmodel_p.h" |
|||
|
|||
#include <QRemoteObjectReplica> |
|||
|
|||
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<DeviceInfo> &devices) |
|||
{ |
|||
beginResetModel(); |
|||
d->devices = devices; |
|||
endResetModel(); |
|||
} |
|||
|
|||
// ------------- Private Implementation -------------
|
|||
|
|||
AccountModelPrivate::AccountModelPrivate() : |
|||
manager(nullptr), |
|||
devices() |
|||
{} |
@ -0,0 +1,51 @@ |
|||
#ifndef QTMVVM_ACCOUNTMODEL_H |
|||
#define QTMVVM_ACCOUNTMODEL_H |
|||
|
|||
#include <QtCore/qabstractitemmodel.h> |
|||
#include <QtCore/qscopedpointer.h> |
|||
|
|||
#include <QtDataSync/accountmanager.h> |
|||
|
|||
#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<QtDataSync::DeviceInfo> &devices); |
|||
|
|||
private: |
|||
QScopedPointer<AccountModelPrivate> d; |
|||
}; |
|||
|
|||
} |
|||
|
|||
#endif // QTMVVM_ACCOUNTMODEL_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<QtDataSync::DeviceInfo> devices; |
|||
}; |
|||
|
|||
} |
|||
|
|||
#endif // QTMVVM_ACCOUNTMODEL_P_H
|
@ -0,0 +1,11 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> |
|||
<mime-type type="application/x-datasync-account-data"> |
|||
<comment>Account data of a datasync account as file export</comment> |
|||
<comment xml:lang="en">Account data of a datasync account as file export</comment> |
|||
<comment xml:lang="de">Account-Daten eines Datasync-Accounts als Dateiexport</comment> |
|||
<icon name="x-datasync-account-data"/> |
|||
<glob-deleteall/> |
|||
<glob pattern="*.dsad"/> |
|||
</mime-type> |
|||
</mime-info> |
@ -0,0 +1,422 @@ |
|||
#include "datasyncviewmodel.h" |
|||
#include "datasyncviewmodel_p.h" |
|||
#include "exportsetupviewmodel_p.h" |
|||
|
|||
#include <QtCore/QStandardPaths> |
|||
#include <QtCore/QFile> |
|||
|
|||
#include <QtRemoteObjects/QRemoteObjectNode> |
|||
|
|||
#include <QtMvvmCore/CoreApp> |
|||
#include <QtMvvmCore/Messages> |
|||
|
|||
#include <QtDataSync/SetupDoesNotExistException> |
|||
|
|||
#undef logDebug |
|||
#undef logInfo |
|||
#undef logWarning |
|||
#undef logCritical |
|||
#include <QtMvvmCore/private/qtmvvm_logging_p.h> |
|||
|
|||
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("<font color=\"%1\">%2</font>") |
|||
.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<ExportSetupViewModel>(DataSyncViewModelPrivate::ExportRequestCode); |
|||
} |
|||
|
|||
void DataSyncViewModel::startImport() |
|||
{ |
|||
auto home = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); |
|||
getOpenFile(this, [this](QUrl url) { |
|||
if(url.isValid()) { |
|||
QSharedPointer<QIODevice> 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("<p>A device wants to log into your account:</p>" |
|||
"<p>Name: %1<br/>" |
|||
"Fingerprint: %2</p>" |
|||
"<p>Do you want accept the request?</p>") |
|||
.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<DeviceInfo> &devices) |
|||
{ |
|||
for(auto device : devices) { |
|||
if(d->pendingGrants.remove(device.deviceId())) { |
|||
information(tr("Account access granted"), |
|||
tr("<p>Account access has been granted to device:</p>" |
|||
"<p>Name: %1<br/>" |
|||
"Fingerprint: %2</p>") |
|||
.arg(device.name()) |
|||
.arg(formatFingerPrint(device.fingerprint()))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void DataSyncViewModel::onInit(const QVariantHash ¶ms) |
|||
{ |
|||
try { |
|||
if(params.contains(paramReplicaNode)) { |
|||
auto node = params.value(paramReplicaNode).value<QRemoteObjectNode*>(); |
|||
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<QIODevice> 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<DataSyncViewModel> 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<DataSyncViewModel> 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); |
|||
} |
@ -0,0 +1,86 @@ |
|||
#ifndef QTMVVM_DATASYNCVIEWMODEL_H |
|||
#define QTMVVM_DATASYNCVIEWMODEL_H |
|||
|
|||
#include <QtCore/qmap.h> |
|||
#include <QtCore/qscopedpointer.h> |
|||
|
|||
#include <QtGui/qcolor.h> |
|||
|
|||
#include <QtMvvmCore/viewmodel.h> |
|||
|
|||
#include <QtDataSync/syncmanager.h> |
|||
#include <QtDataSync/accountmanager.h> |
|||
|
|||
#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<QtDataSync::SyncManager::SyncState, QColor> 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<QtDataSync::DeviceInfo> &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<DataSyncViewModelPrivate> d; |
|||
}; |
|||
|
|||
} |
|||
|
|||
#endif // QTMVVM_DATASYNCVIEWMODEL_H
|
@ -0,0 +1,32 @@ |
|||
#ifndef QTMVVM_DATASYNCVIEWMODEL_P_H |
|||
#define QTMVVM_DATASYNCVIEWMODEL_P_H |
|||
|
|||
#include <QtCore/QSet> |
|||
|
|||
#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<QUuid> 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
|
@ -0,0 +1,82 @@ |
|||
#include "exportsetupviewmodel_p.h" |
|||
using namespace QtMvvm; |
|||
|
|||
std::tuple<bool, bool, QString> 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); |
|||
} |
@ -0,0 +1,53 @@ |
|||
#ifndef QTMVVM_EXPORTSETUPVIEWMODEL_P_H |
|||
#define QTMVVM_EXPORTSETUPVIEWMODEL_P_H |
|||
|
|||
#include <tuple> |
|||
|
|||
#include <QtMvvmCore/ViewModel> |
|||
|
|||
#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<bool, bool, QString> 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
|
@ -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 |
|||
|
@ -0,0 +1,14 @@ |
|||
{ |
|||
"dependencies": [], |
|||
"license": { |
|||
"file": "", |
|||
"name": "" |
|||
}, |
|||
"prcFile": "", |
|||
"priFile": "", |
|||
"priIncludes": [ |
|||
], |
|||
"publishers": { |
|||
}, |
|||
"source": false |
|||
} |
@ -0,0 +1,12 @@ |
|||
#ifndef QTMVVMDATASYNCCORE_GLOBAL_H |
|||
#define QTMVVMDATASYNCCORE_GLOBAL_H |
|||
|
|||
#include <QtCore/qglobal.h> |
|||
|
|||
#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
|
@ -0,0 +1,4 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!DOCTYPE TS> |
|||
<TS version="2.1" language="de_DE"> |
|||
</TS> |
@ -0,0 +1,4 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!DOCTYPE TS> |
|||
<TS version="2.1"> |
|||
</TS> |
Loading…
Reference in new issue