← Back to team overview

uonedb-qt team mailing list archive

[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