uonedb-qt team mailing list archive
-
uonedb-qt team
-
Mailing list archive
-
Message #00265
[Merge] lp:~kalikiana/u1db-qt/syncWithU1 into lp:u1db-qt
Christian Dywan has proposed merging lp:~kalikiana/u1db-qt/syncWithU1 into lp:u1db-qt.
Commit message:
Make sync with U1 and authentication possible
Requested reviews:
U1DB Qt developers (uonedb-qt)
Related bugs:
Bug #1210450 in U1DB Qt/ QML: "Signal to handle sync failure"
https://bugs.launchpad.net/u1db-qt/+bug/1210450
Bug #1212153 in U1DB Qt/ QML: "Synchronization needs authentication support"
https://bugs.launchpad.net/u1db-qt/+bug/1212153
Bug #1219433 in U1DB Qt/ QML: "u1db-qt-example-6.qml:146: Unable to assign QVariantMap to QString"
https://bugs.launchpad.net/u1db-qt/+bug/1219433
For more details, see:
https://code.launchpad.net/~kalikiana/u1db-qt/syncWithU1/+merge/202508
This branch incorporates:
- Improvemed error handling. HTTP status codes, U1Db JSON errors and JSON validation all end up in sync_output as errors now.
- Rewritten example including both test and live server, editable notes and credentials test UI.
- New target syntax: "uri" is sufficient to define local and remote targets with IP or hostname and optional port. Notably it's no longer required to know the IP.
- Code unification between Database and Synchronizer for getting the replica_id.
Progress: 100%
- Caveat: New data isn't pushed to the remote, this is bug 1273688.
--
https://code.launchpad.net/~kalikiana/u1db-qt/syncWithU1/+merge/202508
Your team U1DB Qt developers is requested to review the proposed merge of lp:~kalikiana/u1db-qt/syncWithU1 into lp:u1db-qt.
=== modified file 'debian/control'
--- debian/control 2014-01-22 13:14:22 +0000
+++ debian/control 2014-01-28 13:37:26 +0000
@@ -65,6 +65,8 @@
Section: doc
Architecture: all
Depends: libu1db-qt5-3 (>= ${source:Version}),
+ qtdeclarative5-ubuntuone1.0,
+ qtdeclarative5-ubuntu-ui-toolkit-plugin,
${misc:Depends},
Description: Qt5 binding and QtQuick2 plugin for U1DB - examples
Simple Qt5 binding and QtQuick2 plugin for U1DB (https://launchpad.net/u1db).
=== renamed file 'examples/u1db-qt-example-6/u1db-qt-example-6.qdoc' => 'documentation/u1db-qt-example-6.qdoc'
=== renamed directory 'examples/u1db-qt-example-6' => 'examples/notes-cloud'
=== renamed file 'examples/u1db-qt-example-6/u1db-qt-example-6.qml' => 'examples/notes-cloud/notes-cloud.qml'
--- examples/u1db-qt-example-6/u1db-qt-example-6.qml 2013-08-09 10:42:18 +0000
+++ examples/notes-cloud/notes-cloud.qml 2014-01-28 13:37:26 +0000
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2013 Canonical, Ltd.
+ * Copyright (C) 2014 Canonical, Ltd.
*
* Authors:
- * Kevin Wright <kevin.wright@xxxxxxxxxxxxx>
+ * Christian Dywan <christian.dywan@xxxxxxxxxxxxx>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -20,154 +20,268 @@
import QtQuick 2.0
import U1db 1.0 as U1db
import Ubuntu.Components 0.1
-
-
-Item {
-
- width: units.gu(45)
- height: units.gu(80)
-
- U1db.Database {
- id: aDatabase
- path: "aDatabase6"
- }
-
- U1db.Document {
- id: aDocument1
- database: aDatabase
- docId: 'helloworld'
- create: true
- contents:{"hello": { "world": [ { "message": "Hello World" } ] } }
-
- }
-
- U1db.Index{
- database: aDatabase
- id: by_helloworld
- expression: ["hello.world.message"]
- }
-
- U1db.Query{
- id: aQuery
- index: by_helloworld
- query: [{"message":"Hel*"}]
- }
-
- U1db.Synchronizer{
- id: aSynchronizer
- source: aDatabase
- targets: [{remote:true},
- {remote:true,
- ip:"127.0.0.1",
- port: 7777,
- name:"example1.u1db",
- resolve_to_source:true},
- {remote:"OK"}]
- synchronize: false
- }
-
- MainView {
-
- id: u1dbView
- width: units.gu(45)
- height: units.gu(80)
- anchors.top: parent.top;
-
- Tabs {
- id: tabs
+import UbuntuOne 1.0
+import Ubuntu.Components.Popups 0.1
+
+MainView {
+ applicationName: "com.ubuntu.developer.foobar.notes-cloud"
+
+ width: units.gu(50)
+ height: units.gu(75)
+
+ /*
+ Notes database for both local and remote data
+ */
+ U1db.Database {
+ id: db
+ path: inMemory.checked ? ":memory:" : filename
+ property string filename: "notes.db"
+ }
+ U1db.Document {
+ id: doc
+ database: db
+ docId: 'text'
+ create: true
+ defaults: { "notes": "Lorem ipsum" }
+ }
+
+ /*
+ Use this to reset switches and indicator to avoid "broken" appearance
+ when error would be too fast to see anything happen at all
+ */
+ Timer {
+ id: smoothReset
+ interval: 900
+
+ onRunningChanged: {
+ testSyncActive.enabled = !liveSyncActive.checked
+ liveSyncActive.enabled = !testSyncActive.checked
+ syncIndicator.running = testSync.synchronize || liveSync.synchronize || running
+ }
+
+ onTriggered: {
+ running = false
+ testSyncActive.checked = testSync.synchronize
+ liveSyncActive.checked = liveSync.synchronize
+ }
+ }
+
+ function syncOutput(server, sync_output) {
+ var prettyOutput = '%1'.arg(server)
+ for (var i in sync_output)
+ prettyOutput += '\n%1'.arg(sync_output[i].message_value)
+ syncLabel.text = prettyOutput
+ }
+
+ /*
+ U1 Sync
+ */
+ U1db.Synchronizer {
+ id: testSync
+ source: db
+ targets: [ {
+ uri: "http://127.0.0.1:7777/" + db.filename,
+ resolve_to_source: true,
+ // Legacy syntax
+ ip: "127.0.0.1",
+ port: 7777,
+ name: db.filename,
+ remote: true,
+ }, ]
+ synchronize: false
+ onSynchronizeChanged: smoothReset.start()
+ onSyncOutputChanged: syncOutput(targets[0].uri, sync_output)
+ }
+
+
+ U1db.Synchronizer {
+ id: liveSync
+ source: db
+ targets: [ {
+ uri: "https://u1db.one.ubuntu.com/~/notes-cloud-" + db.path,
+ resolve_to_source: true,
+ }, ]
+ synchronize: false
+ onNetworkRequest: {
+ request.handled = true
+ u1credservice.onUrlSigned.connect(onUrlSigned.bind(request, request))
+ u1credservice.onUrlSigningError.connect(onUrlSigningError.bind(request, request))
+ u1credservice.signUrl(request.uri, request.method)
+ }
+ function onUrlSigned(request, signedUrl) {
+ u1credservice.onUrlSigned.disconnect(onUrlSigned.bind(request, request))
+ u1credservice.onUrlSigningError.disconnect(onUrlSigningError.bind(request, request))
+ request.authorization = signedUrl
+ request.send()
+ }
+ function onUrlSigningError(request, errorMessage) {
+ u1credservice.onUrlSigned.disconnect(onUrlSigned.bind(request, request))
+ u1credservice.onUrlSigningError.disconnect(onUrlSigningError.bind(request, request))
+ request.cancel()
+ }
+ onSynchronizeChanged: smoothReset.start()
+ onSyncOutputChanged: syncOutput(targets[0].uri, sync_output)
+ }
+
+ UbuntuOneCredentialsService {
+ id: u1credservice
+ onCredentialsFound: {
+ statusLabel.text = i18n.tr('Credentials found')
+ }
+ onCredentialsNotFound: {
+ statusLabel.text = i18n.tr('No account credentials found')
+ }
+ onCredentialsDeleted: {
+ statusLabel.text = i18n.tr('Clear credentials successfully')
+ }
+ onUrlSigned: {
+ statusLabel.text = i18n.tr('Signed URL: %1').arg(signedUrl)
+ }
+ onUrlSigningError: {
+ statusLabel.text = i18n.tr('Error signing: %1').arg(errorMessage)
+ }
+
+ onLoginOrRegisterSuccess: {
+ statusLabel.text = i18n.tr('Login successful')
+ }
+ onLoginOrRegisterError: {
+ statusLabel.text = i18n.tr('Login failed')
+ }
+ onTwoFactorAuthRequired: {
+ statusLabel.text = i18n.tr('Two Factor Auth required')
+ }
+ }
+
+ /*
+ UI: Login credentials
+ */
+
+ Component {
+ id: loginPopCom
+ Popover {
+ id: loginPop
+ Column {
+ anchors.centerIn: parent
+ spacing: units.gu(1)
+ Label {
+ text: i18n.tr('Please fill in your credentails')
+ }
+ TextField {
+ id: email
+ placeholderText: i18n.tr('email')
+ }
+ TextField {
+ id: pass
+ placeholderText: i18n.tr('password')
+ }
+ TextField {
+ id: two
+ inputMethodHints: Qt.ImhHiddenText
+ placeholderText: i18n.tr('two-factor code')
+ }
+ Button {
+ text: i18n.tr('Confirm')
+ onClicked: u1credservice.login(email.text, pass.text, two.text)
+ }
+ }
+ }
+ }
+
+ /*
+ UI: Creds/ sync results, notes input
+ */
+ Page {
+
+ title: i18n.tr("Notes in the Cloud")
+
+ Item {
+ anchors.margins: units.gu(1)
anchors.fill: parent
- Tab {
- objectName: "Tab1"
-
- title: i18n.tr("Hello U1Db!")
-
- page: Page {
- id: helloPage
-
- Rectangle {
- width: units.gu(45)
- height: units.gu(40)
- anchors.top: parent.top;
- border.width: 1
-
- Text {
- id: sourceLabel
- anchors.top: parent.top;
- font.bold: true
- text: "aDatabase6 Contents"
- }
-
- ListView {
- id: sourceListView
- width: units.gu(45)
- height: units.gu(35)
- anchors.top: sourceLabel.bottom;
- model: aQuery
-
- delegate: Text {
- wrapMode: Text.WordWrap
- x: 6; y: 77
- text: {
- text: "(" + index + ") '" + contents.message + "'"
- }
- }
- }
-
-
- }
-
- Rectangle {
- id: lowerRectangle
- width: units.gu(45)
- height: units.gu(35)
- anchors.bottom: parent.bottom;
- border.width: 1
-
- Text {
- id: errorsLabel
- anchors.top: parent.top;
- font.bold: true
- text: "Log:"
- }
-
- ListView {
-
- parent: lowerRectangle
- width: units.gu(45)
- height: units.gu(30)
- anchors.top: errorsLabel.bottom;
- model: aSynchronizer
- delegate:Text {
- width: units.gu(40)
- anchors.left: parent.left
- anchors.right: parent.right
- wrapMode: Text.WordWrap
- text: {
- text: sync_output
- }
- }
- }
-
- Button{
- parent: lowerRectangle
- anchors.bottom: parent.bottom;
- text: "Sync"
- onClicked: aSynchronizer.synchronize = true
- anchors.left: parent.left
- anchors.right: parent.right
-
- }
-
- }
- }
-
- }
-
+ Column {
+ id: col
+ spacing: units.gu(1)
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ TextArea {
+ id: statusLabel
+ text: i18n.tr('N/A')
+ readOnly: true
+ autoSize: true
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+
+ Row {
+ spacing: units.gu(1)
+ Button {
+ text: i18n.tr('Verify creds')
+ onClicked: u1credservice.checkCredentials()
+ }
+ Button {
+ text: i18n.tr('Clear creds')
+ onClicked: {
+ statusLabel.text = i18n.tr('N/A')
+ u1credservice.invalidateCredentials()
+ }
+ }
+ Button {
+ id: loginButton
+ text: i18n.tr('Login')
+ onClicked: PopupUtils.open(loginPopCom, loginButton)
+ }
+ ActivityIndicator {
+ id: syncIndicator
+ }
+ }
+
+ TextArea {
+ id: syncLabel
+ text: i18n.tr('No sync target enabled')
+ readOnly: true
+ autoSize: true
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+
+ Row {
+ spacing: units.gu(1)
+ Label {
+ text: i18n.tr('Test')
+ }
+
+ Switch {
+ id: testSyncActive
+ onClicked: testSync.synchronize = true
+ }
+ Label {
+ text: i18n.tr('U1')
+ }
+ Switch {
+ id: liveSyncActive
+ onClicked: liveSync.synchronize = true
+ }
+ Label {
+ text: i18n.tr('Memory')
+ }
+ Switch {
+ id: inMemory
+ }
+ }
+
+ }
+
+ TextArea {
+ text: doc.contents.notes
+ onFocusChanged: doc.contents = { "notes": text }
+ anchors.margins: units.gu(1)
+ anchors.top: col.bottom
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
}
-
}
-
}
-
-
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2013-08-08 10:11:39 +0000
+++ src/CMakeLists.txt 2014-01-28 13:37:26 +0000
@@ -7,6 +7,7 @@
index.cpp
query.cpp
synchronizer.cpp
+ request.cpp
)
# Generated files
@@ -16,6 +17,7 @@
moc_index.cpp
moc_query.cpp
moc_synchronizer.cpp
+ moc_request.cpp
)
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${U1DB_QT_GENERATED}")
@@ -54,7 +56,7 @@
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
-install(FILES global.h database.h document.h index.h query.h synchronizer.h
+install(FILES global.h database.h document.h index.h query.h synchronizer.h request.h
DESTINATION ${INCLUDE_INSTALL_DIR}
)
=== modified file 'src/database.cpp'
--- src/database.cpp 2013-08-14 12:07:11 +0000
+++ src/database.cpp 2014-01-28 13:37:26 +0000
@@ -156,6 +156,16 @@
return setError(QString("Failed to read internal schema: FileError %1").arg(file.error()));
}
}
+
+ // index_storage is read by the python sqlite implementation
+ // it wasn't set in in earlier u1db-qt versions so always check it
+ QSqlQuery query(m_db.exec(
+ "SELECT value FROM u1db_config WHERE name = 'index_storage'"));
+ if (!query.next())
+ query.prepare("INSERT INTO u1db_config VALUES ('index_storage', 'expand referenced')");
+ if (!query.exec())
+ return setError(QString("Failed to set index storage: %1\n%2").arg(m_db.lastError().text()).arg(query.lastQuery()));
+
return true;
}
=== modified file 'src/database.h'
--- src/database.h 2013-08-12 15:28:13 +0000
+++ src/database.h 2014-01-28 13:37:26 +0000
@@ -62,6 +62,7 @@
void updateSyncLog(bool insert, QString uid, QString generation, QString transaction_id);
QList<QString> listTransactionsSince(int generation);
QMap<QString,QVariant> getSyncLogInfo(QMap<QString,QVariant> lastSyncInformation, QString uid, QString prefix);
+ QString getReplicaUid();
Q_SIGNALS:
void pathChanged(const QString& path);
@@ -80,7 +81,6 @@
QSqlDatabase m_db;
QString m_error;
- QString getReplicaUid();
bool isInitialized();
bool initializeIfNeeded(const QString& path=":memory:");
bool setError(const QString& error);
=== added file 'src/request.cpp'
--- src/request.cpp 1970-01-01 00:00:00 +0000
+++ src/request.cpp 2014-01-28 13:37:26 +0000
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 Canonical, Ltd.
+ *
+ * Authors:
+ * Christian Dywan <christian.dywan@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+
+#include "request.h"
+#include "private.h"
+
+QT_BEGIN_NAMESPACE_U1DB
+
+Request::Request(const QString& uri, const QString& method, QObject *parent) :
+ QObject(parent), m_uri(uri), m_method(method)
+{
+}
+
+QString Request::getUri()
+{
+ return m_uri;
+}
+
+void Request::setUri(const QString& uri)
+{
+ m_uri = uri;
+}
+
+QString Request::getMethod()
+{
+ return m_method;
+}
+
+void Request::setMethod(const QString& method)
+{
+ m_method = method;
+}
+
+bool Request::getHandled()
+{
+ return m_handled;
+}
+
+void Request::setHandled(bool handled)
+{
+ m_handled = handled;
+}
+
+QString Request::getAuthorization()
+{
+ return m_authorization;
+}
+
+void Request::setAuthorization(const QString& authorization)
+{
+ m_authorization = authorization;
+}
+
+QT_END_NAMESPACE_U1DB
+
+#include "moc_request.cpp"
+
=== added file 'src/request.h'
--- src/request.h 1970-01-01 00:00:00 +0000
+++ src/request.h 2014-01-28 13:37:26 +0000
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 Canonical, Ltd.
+ *
+ * Authors:
+ * Christian Dywan <christian.dywan@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef U1DB_REQUEST_H
+#define U1DB_REQUEST_H
+
+#include <QtCore/QObject>
+#include <QVariant>
+#include <QMetaType>
+#include <QtNetwork>
+#include <QNetworkAccessManager>
+
+
+#include "global.h"
+
+QT_BEGIN_NAMESPACE_U1DB
+
+class Q_DECL_EXPORT Request : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(QString uri READ getUri WRITE setUri)
+ Q_PROPERTY(QString method READ getMethod WRITE setMethod)
+ Q_PROPERTY(bool handled READ getHandled WRITE setHandled)
+ Q_PROPERTY(QString authorization READ getAuthorization WRITE setAuthorization)
+
+public:
+ Request(const QString& uri, const QString& method, QObject* parent = 0);
+
+ QString getUri();
+ void setUri(const QString& uri);
+ QString getMethod();
+ void setMethod(const QString& method);
+ bool getHandled();
+ void setHandled(bool handled);
+ QString getAuthorization();
+ void setAuthorization(const QString& authorization);
+
+ // FIXME private
+ QNetworkRequest m_request;
+
+Q_SIGNALS:
+
+ void send();
+ void cancel();
+
+private:
+ //Q_DISABLE_COPY(Request)
+ QString m_uri;
+ QString m_method;
+ bool m_handled;
+ QString m_authorization;
+};
+
+QT_END_NAMESPACE_U1DB
+
+
+#endif // U1DB_REQUEST_H
+
+
=== modified file 'src/synchronizer.cpp'
--- src/synchronizer.cpp 2013-08-12 08:58:02 +0000
+++ src/synchronizer.cpp 2014-01-28 13:37:26 +0000
@@ -1,8 +1,9 @@
/*
- * Copyright (C) 2013 Canonical, Ltd.
+ * Copyright (C) 2013-2014 Canonical, Ltd.
*
* Authors:
* Kevin Wright <kevin.wright@xxxxxxxxxxxxx>
+ * Christian Dywan <christian.dywan@xxxxxxxxxxxxx>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -239,6 +240,57 @@
Q_EMIT syncChanged(synchronize);
}
+void Synchronizer::sendNetworkRequest(QNetworkAccessManager* manager, QNetworkRequest request, QString method)
+{
+ Request* _request = new Request(request.url().toString(), method);
+ request.setOriginatingObject(manager);
+ _request->m_request = request;
+
+ connect(_request, &Request::send, this, &Synchronizer::networkRequestSent);
+ connect(_request, &Request::cancel, this, &Synchronizer::networkRequestCancelled);
+ connect(this, &Synchronizer::networkRequest, this, &Synchronizer::networkRequestEmitted);
+ Q_EMIT networkRequest(_request);
+}
+
+void Synchronizer::networkRequestEmitted(Request* _request)
+{
+ if (!_request->getHandled())
+ Q_EMIT _request->send();
+}
+
+void Synchronizer::networkRequestSent()
+{
+ Request* _request = qobject_cast<Request *>(QObject::sender());
+ QNetworkRequest request = _request->m_request;
+
+ if (!_request->getAuthorization().isEmpty())
+ request.setRawHeader(QStringLiteral("Authorization").toUtf8(),
+ _request->getAuthorization().toUtf8());
+
+ QNetworkAccessManager* manager = qobject_cast<QNetworkAccessManager *>(request.originatingObject());
+ if (_request->getMethod() == "GET")
+ manager->get(request);
+ else if (_request->getMethod() == "POST")
+ manager->post(request, manager->property("postString").toByteArray());
+ else {
+ QVariantMap output_map;
+ output_map.insert("message_type", "error");
+ output_map.insert("message_value", "Invalid Request.method: " + _request->getMethod());
+ m_sync_output.append(output_map);
+ Q_EMIT syncOutputChanged(m_sync_output);
+ }
+}
+
+void Synchronizer::networkRequestCancelled()
+{
+ disconnect(this, &Synchronizer::networkRequest, this, &Synchronizer::networkRequestEmitted);
+
+ QVariantMap output_map;
+ output_map.insert("message_type", "error");
+ output_map.insert("message_value", "Request was cancelled");
+ m_sync_output.append(output_map);
+ Q_EMIT syncOutputChanged(m_sync_output);
+}
/*!
* \property Synchronizer::resolve_to_source
@@ -381,6 +433,7 @@
QMap<QString,QString>validator;
validator.insert("remote","bool");
+ validator.insert("uri","QString");
validator.insert("location","QString");
validator.insert("resolve_to_source","bool");
@@ -393,10 +446,11 @@
QList<QString>mandatory;
- mandatory.append("remote");
+ mandatory.append("uri");
mandatory.append("resolve_to_source");
if(synchronize == true){
+ m_sync_output.clear();
/*!
A list of valid sync target databases is generated by calling the getValidTargets(validator, mandatory) method, and adding the return value to a QList (sync_targets).
@@ -542,7 +596,7 @@
target.insert("sync",true);
sync_targets.append(target);
- QString message_value = "Mandatory properties were included and their values are valid.";
+ QString message_value = "Mandatory properties included and valid.";
QVariantMap output_map;
output_map.insert("concerning_property","targets");
@@ -588,14 +642,15 @@
if(target.typeName()== QStringLiteral("QVariantMap")){
QMap<QString,QVariant> target_map = target.toMap();
- if(target_map.contains("remote")&&target_map["remote"]==false){
- if(target_map.contains("sync")&&target_map["sync"]==true){
+ QVariantMap output_map;
+ output_map.insert("concerning_property", "targets");
+ output_map.insert("concerning_index", target_index);
+ bool sync = target_map.contains("sync") && target_map["sync"].toBool();
+ QUrl url(target_map["uri"].toString());
+ if (url.isLocalFile()) {
+ if (sync) {
QString message_value = "Valid local target.";
-
- QVariantMap output_map;
- output_map.insert("concerning_property","targets");
- output_map.insert("concerning_index",target_index);
output_map.insert("message_type","no-errors");
output_map.insert("message_value",message_value);
m_sync_output.append(output_map);
@@ -603,24 +658,16 @@
syncLocalToLocal(source, target_map);
}
}
- else if(target_map.contains("remote")&&target_map["remote"]==true){
- if(target_map.contains("sync")&&target_map["sync"]==true){
-
- //ip
- //port
- //name
+ else if (url.scheme() == "http" || url.scheme() == "https") {
+ if (sync) {
//GET /thedb/sync-from/my_replica_uid
QString source_uid = getUidFromLocalDb(source->getPath());
- QString get_string = target_map["name"].toString()+"/sync-from/"+source_uid;
- QString url_string = "http://"+target_map["ip"].toString();
- QString full_get_request = url_string+"/"+get_string;
- int port_number = target_map["port"].toInt();
+ QString full_get_request = target_map["uri"].toString() + "/sync-from/" + source_uid;
QNetworkAccessManager *manager = new QNetworkAccessManager(source);
QUrl url(full_get_request);
- url.setPort(port_number);
QNetworkRequest request(url);
@@ -628,24 +675,17 @@
QString message_value = "Valid remote target.";
- QVariantMap output_map;
- output_map.insert("concerning_property","targets");
- output_map.insert("concerning_index",target_index);
output_map.insert("message_type","no-errors");
output_map.insert("message_value",message_value);
m_sync_output.append(output_map);
- manager->get(QNetworkRequest(request));
-
+ sendNetworkRequest(manager, request, "GET");
}
}
else{
- QString message_value = "Unknown error. Please check properties";
+ QString message_value = "Invalid URI, must be file, http or https";
- QVariantMap output_map;
- output_map.insert("concerning_property","targets");
- output_map.insert("concerning_index",target_index);
output_map.insert("message_type","error");
output_map.insert("message_value",message_value);
m_sync_output.append(output_map);
@@ -671,7 +711,7 @@
void Synchronizer::syncLocalToLocal(Database *sourceDb, QMap<QString,QVariant> target)
{
- QString target_db_name = target["location"].toString();
+ QString target_db_name = QUrl(target["uri"].toString()).toLocalFile();
Database *targetDb;
Index *targetIndex;
@@ -967,64 +1007,27 @@
QString Synchronizer::getUidFromLocalDb(QString dbFileName)
{
-
QString dbUid;
-
- QSqlDatabase db;
-
- db = QSqlDatabase::addDatabase("QSQLITE",QUuid::createUuid().toString());
-
- QFile db_file(dbFileName);
-
- if(!db_file.exists())
- {
-
- QString message_value = "Database does not exist.";
-
+ Database localDb;
+
+ localDb.setPath(dbFileName);
+ QString lastError(localDb.lastError());
+ if (lastError.isEmpty()) {
+ dbUid = localDb.getReplicaUid();
+ dbUid = dbUid.replace("{","");
+ dbUid = dbUid.replace("}","");
+ lastError = localDb.lastError();
+ }
+
+ if (!lastError.isEmpty()) {
+ QString message_value = localDb.lastError();
QVariantMap output_map;
+
output_map.insert("concerning_property","source|targets");
output_map.insert("concerning_database",dbFileName);
output_map.insert("message_type","error");
output_map.insert("message_value",message_value);
m_sync_output.append(output_map);
-
- return dbUid;
- }
- else
- {
- db.setDatabaseName(dbFileName);
- if (!db.open()){
-
- QString message_value = db.lastError().text();
-
- QVariantMap output_map;
- output_map.insert("concerning_property","source|targets");
- output_map.insert("concerning_database",dbFileName);
- output_map.insert("message_type","error");
- output_map.insert("message_value",message_value);
- m_sync_output.append(output_map);
-
- }
- else{
- QSqlQuery query (db.exec("SELECT value FROM u1db_config WHERE name = 'replica_uid'"));
-
- if(!query.lastError().isValid() && query.next()){
- dbUid = query.value(0).toString();
- db.close();
-
- dbUid = dbUid.replace("{","");
-
- dbUid = dbUid.replace("}","");
-
- return dbUid;
- }
- else{
- qDebug() << query.lastError().text();
- db.close();
- return dbUid;
- }
- }
-
}
return dbUid;
@@ -1048,6 +1051,20 @@
void Synchronizer::remoteGetSyncInfoFinished(QNetworkReply* reply)
{
+ QVariant statusCodeVariant = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+ int statusCode(statusCodeVariant.isValid() ? statusCodeVariant.toInt() : -1);
+
+ if(!(statusCode == 200 || statusCode == 401)) {
+ QVariant reasonVariant = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
+ QString reason(reasonVariant.isValid() ? reasonVariant.toString() : "N/A");
+ QVariantMap output_map;
+ output_map.insert("concerning_property", "targets");
+ output_map.insert("message_type", "error");
+ output_map.insert("message_value", QString("Pre-sync bad HTTP code: %1 - %2").arg(statusCode).arg(reason));
+ m_sync_output.append(output_map);
+ Q_EMIT syncOutputChanged(m_sync_output);
+ return;
+ }
QNetworkAccessManager *manager = reply->manager();
@@ -1127,12 +1144,11 @@
QByteArray postDataSize = QByteArray::number(postString.size());
QNetworkRequest request(postUrl);
- request.setRawHeader("User-Agent", "U1Db-Qt v1.0");
- request.setRawHeader("X-Custom-User-Agent", "U1Db-Qt v1.0");
request.setRawHeader("Content-Type", "application/x-u1db-sync-stream");
request.setRawHeader("Content-Length", postDataSize);
- manager->post(QNetworkRequest(request),postString);
+ manager->setProperty("postString", postString);
+ sendNetworkRequest(manager, request, "POST");
}
@@ -1151,6 +1167,20 @@
void Synchronizer::remotePostSyncInfoFinished(QNetworkReply* reply)
{
+ QVariant statusCodeVariant = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+ int statusCode(statusCodeVariant.isValid() ? statusCodeVariant.toInt() : -1);
+
+ if(!(statusCode == 200 || statusCode == 409 || statusCode == 401)) {
+ QVariant reasonVariant = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
+ QString reason(reasonVariant.isValid() ? reasonVariant.toString() : "N/A");
+ QVariantMap output_map;
+ output_map.insert("concerning_property", "targets");
+ output_map.insert("message_type", "error");
+ output_map.insert("message_value", QString("Post-sync bad HTTP code: %1 - %2").arg(statusCode).arg(reason));
+ m_sync_output.append(output_map);
+ Q_EMIT syncOutputChanged(m_sync_output);
+ return;
+ }
QNetworkAccessManager *manager = reply->manager();
@@ -1181,9 +1211,31 @@
replyData = replyData.replace("\r\n","");
- QJsonDocument replyJson = QJsonDocument::fromJson(replyData.toUtf8());
-
- QVariant replyVariant = replyJson.toVariant();
+ QJsonParseError parseError;
+ QJsonDocument replyJson = QJsonDocument::fromJson(replyData.toUtf8(), &parseError);
+ QString syncError;
+ QVariant replyVariant;
+
+ if (parseError.error == QJsonParseError::NoError) {
+ // Valid JSON: errors may be returned as { "error": "Foo bar" }
+ replyVariant = replyJson.toVariant();
+ QVariantMap replyMap(replyVariant.toMap());
+ if (replyMap.contains("error"))
+ syncError = QString("Server - %1: %2").arg(replyMap["error"].toString()).arg(replyMap["message"].toString());
+ } else {
+ // The response is likely an HTML page but can also be malformed JSON
+ syncError = QString("JSON - %1").arg(parseError.errorString());
+ }
+
+ if (!syncError.isEmpty()) {
+ QVariantMap output_map;
+ output_map.insert("concerning_property", "targets");
+ output_map.insert("message_type", "error");
+ output_map.insert("message_value", "Error: " + syncError);
+ m_sync_output.append(output_map);
+ Q_EMIT syncOutputChanged(m_sync_output);
+ return;
+ }
QVariantList replyList = replyVariant.toList();
@@ -1191,6 +1243,8 @@
int index = -1;
+ QString changelog;
+
while(i.hasNext()){
index++;
@@ -1241,6 +1295,7 @@
{
source->putDoc(content,id);
source->updateDocRevisionNumber(id,rev);
+ changelog += QString(" %1@%2").arg(id).arg(rev);
}
}
@@ -1249,6 +1304,15 @@
}
+ if (changelog.isEmpty())
+ changelog = "No changes";
+
+ QVariantMap output_map;
+ output_map.insert("concerning_property", "source|targets");
+ output_map.insert("message_type", "no-errors");
+ output_map.insert("message_value", "Sync done:" + changelog);
+ m_sync_output.append(output_map);
+ Q_EMIT syncOutputChanged(m_sync_output);
}
QT_END_NAMESPACE_U1DB
=== modified file 'src/synchronizer.h'
--- src/synchronizer.h 2013-08-12 08:58:02 +0000
+++ src/synchronizer.h 2014-01-28 13:37:26 +0000
@@ -31,6 +31,7 @@
#include "database.h"
#include "index.h"
#include "query.h"
+#include "request.h"
QT_BEGIN_NAMESPACE_U1DB
@@ -123,6 +124,12 @@
void syncCompleted();
+ /*!
+ * \brief networkRequest
+ * \param request
+ */
+ void networkRequest(Request* request);
+
private:
//Q_DISABLE_COPY(Synchronizer)
Database* m_source;
@@ -135,6 +142,11 @@
void remoteGetSyncInfoFinished(QNetworkReply* reply);
void remotePostSyncInfoFinished(QNetworkReply* reply);
+ void sendNetworkRequest(QNetworkAccessManager* manager, QNetworkRequest request, QString method);
+ void networkRequestSent();
+ void networkRequestCancelled();
+ void networkRequestEmitted(Request* request);
+
};
QT_END_NAMESPACE_U1DB
Follow ups