Merge lp:~kevin-wright-1/u1db-qt/synchronizer-18-juni-i into lp:u1db-qt

Proposed by Cris Dywan
Status: Merged
Merged at revision: 100
Proposed branch: lp:~kevin-wright-1/u1db-qt/synchronizer-18-juni-i
Merge into: lp:u1db-qt
Diff against target: 2269 lines (+1981/-7) (has conflicts)
13 files modified
CMakeLists.txt (+2/-2)
examples/u1db-qt-example-6/u1db-qt-example-6.qdoc (+387/-0)
examples/u1db-qt-example-6/u1db-qt-example-6.qml (+227/-0)
modules/U1db/CMakeLists.txt (+4/-0)
modules/U1db/plugin.cpp (+2/-0)
src/CMakeLists.txt (+5/-1)
src/database.cpp (+319/-2)
src/database.h (+20/-1)
src/index.cpp (+13/-1)
src/query.cpp (+9/-0)
src/query.h (+2/-0)
src/synchronizer.cpp (+879/-0)
src/synchronizer.h (+112/-0)
Text conflict in src/database.cpp
Text conflict in src/index.cpp
To merge this branch: bzr merge lp:~kevin-wright-1/u1db-qt/synchronizer-18-juni-i
Reviewer Review Type Date Requested Status
U1DB Qt developers Pending
Review via email: mp+170623@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Cris Dywan (kalikiana) wrote :

syncLocalToLocal should use U1db::Database to re-use the initialization, which would also give it the existing sanity checks

the logic of onSyncChanged is probably best moved into a separate callback. Changing resolve_to_source and targets would trigger it as well. Thus it becomes possible to delay/ thread the real sync and avoid any delay at startup.

Even with keeping in mind it's incomplete //uncommented code and conflicts make reviewing a little hard.

I love the description of the sync via HTTP/ JSON, this would be nice to get into one qdoc-ified comment and make it accessible as documentation.

100. By Kevin Wright

Migrated listTransactionsSince function from Syncrhonizer class to Database class. Previously this class used the path of a database file rather than an instance of an active Database class' database connection. With the function now hosted by the Database class itself the path of the file and opening of the database connection within the scope of the function is no longer required (i.e. the class' active database connection is used instead).

101. By Kevin Wright

Migrated getSyncLogInfoFromLocalDb function from Synchronizer class to Database class, and renamed getSyncLogInfo. Previously this class used the path of a database file rather than an instance of an active Database class' database connection. With the function now hosted by the Database class itself the path of the file and opening of the database connection within the scope of the function is no longer required (i.e. the class' active database connection is used instead). This function is used by the Synchronizer class' getLastSyncInformation function to retrieve data from the source and target databases. The data represents information that one database has about the other the last time they synced (if ever).

102. By Kevin Wright

Fixed a regression relating to the active source Database for Synchronizer. In the function syncLocalToLocal is the ability to overide the main source database (the 'source' property) with another database when a query is defined within an individual 'target' definition (with the key 'source_query').

103. By Kevin Wright

Added the initial steps for sync with a remote database. There are several steps in the archtitecure for remote sync, whereby the server and client communicate information between each other (e.g. previous sync transations, new data). This commit consists of the first step, where the client initiates the sync transaction and the server responds with known information about previous syncs if any. Also in this commit is the first roughed in code for the second stage of a sync transaction, where the client (aka source) begins sharing information about new data since the previous sync between the two databases (if any previous transaction occured).

104. By Kevin Wright

This commit continues the work to support the second stage of a local to remote sync transaction, where the client (aka source) begins sharing information about new data since the previous sync between the two databases (if any previous transaction occured). The code now is up to the point of initiating the post to the remote server, but at this time is showing bad request errors. The post is not sending complete data at this time, which may be the reason for the bad request error.

105. By Kevin Wright

This commit continues the work to support the second stage of a local to remote sync transaction, where the client (aka source) begins sharing information about new data since the previous sync between the two databases (if any previous transaction occured). The post is sending complete data as of this commit, but the server is still replying with a bad request error. On the surface everything appears to comply with the high level architecture documentation, but something is obviously still incorrect.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-05-01 22:49:50 +0000
3+++ CMakeLists.txt 2013-07-04 14:13:28 +0000
4@@ -7,11 +7,12 @@
5 # Dependencies
6 include(FindPkgConfig)
7 find_package(Qt5Core REQUIRED)
8+find_package(Qt5Network REQUIRED)
9 find_package(Qt5Sql REQUIRED)
10 add_definitions(-DWITHQT5=1)
11
12 set(U1DB_QT_LIBNAME u1db-qt5)
13-set(QT_PKGCONFIG_DEPENDENCIES "Qt5Core Qt5Quick Qt5Sql")
14+set(QT_PKGCONFIG_DEPENDENCIES "Qt5Core Qt5Network Qt5Quick Qt5Sql")
15 set(QT_U1DB_PKGCONFIG_FILE lib${U1DB_QT_LIBNAME}.pc)
16
17 # Build flags
18@@ -39,4 +40,3 @@
19 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${QT_U1DB_PKGCONFIG_FILE}
20 DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig
21 )
22-
23
24=== added directory 'examples/u1db-qt-example-6'
25=== added file 'examples/u1db-qt-example-6/u1db-qt-example-6.qdoc'
26--- examples/u1db-qt-example-6/u1db-qt-example-6.qdoc 1970-01-01 00:00:00 +0000
27+++ examples/u1db-qt-example-6/u1db-qt-example-6.qdoc 2013-07-04 14:13:28 +0000
28@@ -0,0 +1,387 @@
29+/*!
30+
31+\page u1db-qt-tutorial-6.html
32+
33+\title U1Db-Qt Synchronizing Tutorial
34+
35+This tutorial is designed to demonstrate a variety of essential U1Db-Qt functionality and usage, including:
36+
37+\list 1
38+ \li Synchronizing two databases
39+ \li Utilizing the U1db-Qt Index element
40+ \li Various approaches to define U1db-Qt Document elements when using the Index element
41+ \li Partnering the U1db-Qt Index element and a QML ListView element
42+\endlist
43+
44+\section1 Storing Data
45+
46+\section2 The Database Element
47+
48+\section3 Creating a Database
49+
50+A Database is very simple to create. It only needs an id and a path where the file will be created. A Database is a model, which can be used by elements, such as the ListView further in this example.
51+
52+\code
53+U1db.Database {
54+ id: aDatabase
55+ path: "aDatabase4"
56+}
57+\endcode
58+
59+\section1 The Document Element
60+
61+\section2 Declaring Documents (at Runtime)
62+
63+A Document can be instantiated at runtime, or generated dynamically. The examples below demonstrate the former.
64+
65+A very basic Document could include its unique 'id' and 'docId' properties. While it is not mandatory to define these properties, in some cases they can be convenient references. More advanced applications would likely find these very useful, and in some cases may be an absolute necessity to achieve the objectives of the program.
66+
67+This example of a very simple Document will not initially do anything, until more properties are added and defined:
68+
69+\code
70+U1db.Document {
71+ id: aDocument1
72+ docId: 'helloworld1'
73+}
74+\endcode
75+
76+A basic but still practical Document definition contains several essential properties. In addition to 'id' and 'docId' (discussed above), the 'database', 'create', and 'defaults' properties are also very important, and are introduced below.
77+
78+The 'database' property ensures that the Document is attached to an already defined (or possibly soon to be defined one) identified by its id (in this case 'aDatabase'). For example:
79+
80+\code
81+U1db.Document {
82+ id: aDocument1
83+ database: aDatabase
84+ docId: 'helloworld1'
85+}
86+\endcode
87+
88+Should the Database not already contain a Document with the same docId ('hellowworld1' in this example) when a 'create' property is present and set to true it will be generated. For example:
89+
90+\code
91+U1db.Document {
92+ id: aDocument1
93+ database: aDatabase
94+ docId: 'helloworld1'
95+ create: true
96+}
97+\endcode
98+
99+However, the Document still requires some data to be useful, which is what the 'defaults' property provides. The value of 'defaults' is a map of data that will be stored in the database (again when the create property is et to true). It contain key:value pairs, where the value can be a string, number, or nested object (e.g. additional fields, lists). For example:
100+
101+\code
102+U1db.Document {
103+ id: aDocument1
104+ database: aDatabase
105+ docId: 'helloworld1'
106+ create: true
107+ defaults:{"hello": { "world": { "message":"Hello World", "id": 1 } } }
108+}
109+\endcode
110+
111+As mentioned above, lists can also be nested in Document data. Lists provide a convenient method for producing multiple instances of the same key (AKA 'field' or 'sub-field'). The example code below shows valid use of the 'message' and 'id' sub-fields multiple times within the same object.
112+
113+\code
114+U1db.Document {
115+ id: aDocument2
116+ database: aDatabase
117+ docId: 'helloworld2'
118+ create: true
119+ defaults:{"hello": { "world": [
120+ { "message":"Hello World", "id": 2 },
121+ { "message":"Hello World", "id": 2.5 }
122+ ] } }
123+}
124+\endcode
125+
126+When the default Javascript Object Notation itself is formatted with appropriate line breaks and indentation, it becomes easier to visualize an embedded list, containing sub-fields 'message' and 'id' (and their respective values):
127+
128+\code
129+{"hello":
130+ { "world":
131+ [
132+ { "message":"Hello World", "id": 2 },
133+ { "message":"Hello World", "id": 2.5 }
134+ ]
135+ }
136+}
137+\endcode
138+
139+In dot notation these sub-fields are represented by 'hello.world.message' and 'hello.world.id' respectively. Later in this tutorial these will be utilized within the 'expression' property of U1Db-Qt's Index element, in close collaboration with a QML ListView's delegates.
140+
141+
142+Normally when a docId already exists in a database, and when the set flag is set to true, the value in 'defaults' will be ignored (and the existing data in the database will remain untouched). Sometimes a developer needs to easily overwrite the data in an existing document. The 'contents' property can be used for just that purpose. When 'contents' is defined, its value will replace existing data in the database, for the document identified by the docId. In addition, 'contents' can be used to add new documents, in the same way as the 'create: true' + 'defaults' combination does; in other words, if the document defined by 'docId' does not exist it will be created.
143+
144+\code
145+U1db.Document {
146+ id: aDocument3
147+ database: aDatabase
148+ docId: 'helloworld3'
149+ contents:{"hello": { "world": [
150+ { "message":"Hello World", "id": 3 },
151+ { "message":"Hello World", "id": 3.33 },
152+ { "message":"Hello World", "id": 3.66 }
153+ ] } }
154+}
155+\endcode
156+
157+If 'defaults' exists, 'create' is set to 'true' (or 'false' for that matter) and 'contents' is also defined, it is the latter that takes precidence. In other words, 'create' and 'defaults' will be ignored. The following example demonstrates this scenario:
158+
159+\code
160+U1db.Document {
161+ id: aDocument3
162+ database: aDatabase
163+ docId: 'helloworld3'
164+ create: true
165+ default:{"hello": { "world": [{ "message":"Hello World", "id": 3 }] } }
166+ contents:{"hello": { "world": [
167+ { "message":"Hello World", "id": 3 },
168+ { "message":"Hello World", "id": 3.33 },
169+ { "message":"Hello World", "id": 3.66 }
170+ ] } }
171+}
172+\endcode
173+
174+This snippet simply represents the absence of the 'create' property, which is synonymous with 'create: false'. The Document can still be recognized within the application, but until applicable properties (such as those outlined above) are added and/or modified then nothing will be added or modified in the database, and this instance may have very little practical value.
175+
176+\code
177+U1db.Document {
178+ id: aDocument4
179+ database: aDatabase
180+ docId: 'helloworld4'
181+ defaults:{"hello": { "world": { "message":"Hello World", "id": 4 } } }
182+}
183+\endcode
184+
185+\section3 Samples of Stored Documents
186+
187+The data stored in the database after defining the above Document elements (and then running the application, will consist of the following:
188+
189+\table
190+\header
191+ \li docId
192+ \li content
193+\row
194+
195+ \li 'helloworld1'
196+ \li
197+\code
198+{
199+ "hello": {
200+ "world": {
201+ "id": 1,
202+ "message": "Hello World"
203+ }
204+ }
205+}
206+
207+\endcode
208+
209+
210+\row
211+
212+ \li 'helloworld2'
213+ \li
214+\code
215+{
216+ "hello": {
217+ "world": [
218+ {
219+ "id": 2,
220+ "message": "Hello World"
221+ },
222+ {
223+ "id": 2.5,
224+ "message": "Hello World"
225+ }
226+ ]
227+ }
228+}
229+\endcode
230+
231+\row
232+
233+ \li 'helloworld3'
234+ \li
235+\code
236+{
237+ "hello": {
238+ "world": [
239+ {
240+ "id": 3,
241+ "message": "Hello World"
242+ },
243+ {
244+ "id": 3.33,
245+ "message": "Hello World"
246+ },
247+ {
248+ "id": 3.66,
249+ "message": "Hello World"
250+ }
251+ ]
252+ }
253+}
254+\endcode
255+
256+
257+\endtable
258+
259+\section1 Retrieving Data
260+
261+To retrieve the Documents that were declared earlier requires two additional elements: Index and Query.
262+
263+\section2 The Index Element
264+
265+\section3 Creating and Index Element
266+
267+The Index element requires both a unique 'id' and a pointer to a 'database' in order to begin becoming useful, as demonstrated here:
268+
269+\code
270+U1db.Index{
271+ database: aDatabase
272+ id: by_helloworld
273+}
274+\endcode
275+
276+In the future, the Index element will support on disk storage of appropriate results / data. At the present time only in memory indexing is done, but once the storing capability is implemented, defining and identifying it is as simple as using the 'name' property (which will be stored in the database along with the relvent data that goes with it). The snippet below shows the use of the 'name' property:
277+
278+\code
279+U1db.Index{
280+ database: aDatabase
281+ id: by_helloworld
282+ //name: "by-helloworld"
283+}
284+\endcode
285+
286+The Index element describes, using dot notation, the fields and sub-fields where the developer expects to find information. That information is defined in a list, and added as the value for the 'expression' property. The list can contain one or more entries, as exemplified here (the property is commented out due to its current status):
287+
288+\code
289+U1db.Index{
290+ database: aDatabase
291+ id: by_helloworld
292+ //name: "by-helloworld"
293+ expression: ["hello.world.id","hello.world.message"]
294+}
295+\endcode
296+
297+\section2 The QueryElement
298+
299+\section3 Creating a Query Element
300+
301+The Query element has two responsibilities: a bridge from Database+Index to other parts of the application, as well as further filtering of data in the database (in addition to what Index provides).
302+
303+In order to fulfil its duties as a bridge to an Index (and Database), the 'index' property must point to an Index element, identified by its 'id'. For example:
304+
305+\code
306+U1db.Query{
307+ id: aQuery
308+ index: by_helloworld
309+}
310+\endcode
311+
312+While Index helps to filter data based on 'where' it is located (e.g. field.sub-field), Query helps determine the additional set of criteria for 'what' is being searched for. The intent of the 'query' property is to provide the mechanism for defnining the search criteria, but at the time of writing that functionality is not yet available. However, once the implementation is in place, using it is only requires defining the property's value (e.g. "Hello World"). Wild card searches using '*' are supported, which is the default query (i.e. if 'query' is not set it is assumed to be '*'). For example (the property is commented out due to its current status):
313+
314+\code
315+U1db.Query{
316+ id: aQuery
317+ index: by_helloworld
318+ //query: "*"
319+}
320+\endcode
321+
322+When the 'query' property becomes available, only wildcard search definitions for "starts with" will be suppoprted. Thus the following would be supported:
323+
324+\code
325+U1db.Query{
326+ id: aQuery
327+ index: by_helloworld
328+ //query: "Hello*"
329+}
330+\endcode
331+
332+
333+But this would not:
334+
335+\code
336+U1db.Query{
337+ id: aQuery
338+ index: by_helloworld
339+ //query: "*World"
340+}
341+\endcode
342+
343+Note: again, the 'query' property is commented out in the above two snippets due to its current status
344+
345+\section1 Using Data
346+
347+\section2 Data and the Application UI
348+
349+\section3 Using Data With Models and Views
350+
351+This simple snippet represents how to attach a ListModel to a ListView. In this instance the model 'aQuery' is representative of the Query + Index combination defined earlier:
352+
353+\code
354+ListView {
355+ width: units.gu(45)
356+ height: units.gu(80)
357+ model: aQuery
358+}
359+\endcode
360+
361+\section4 Data and Delegates
362+
363+How a model and ListView + delegates work together is a common QML concept, and not specific to U1Db-Qt. However, the asynchronous nature of this relationship is important to understand. When using QML ListView, delegates will be created based on particular properties such as the size of the application window, ListView, and delegate itself (amongst other factors). Each delegate can then represent a Document retrieved from the Database based on the record's index. This example demonstrates some of the property definitions that contribute to determining the number of delegates a ListView will contain:
364+
365+\code
366+ListView {
367+ width: units.gu(45)
368+ height: units.gu(80)
369+ model: aQuery
370+
371+ delegate: Text {
372+ x: 66; y: 77
373+ }
374+
375+}
376+\endcode
377+
378+
379+When the number of Documents is less than or equal to the number of delegates then there is a one to one mapping of index to delegate (e.g. the first delegate will represent the Document with an index = 0; the second, index = 1; and so on).
380+
381+When there are more Documents than delegates the ListView will request a new index depending on the situation (e.g. a user scrolls up or down). For example, if a ListView has 10 delegates, but 32 Documents to handle, when a user initially scrolls the first delegate will change from representing the Document with index = 0 to the Document that might have index = 8; the second, from index = 1 to index = 9; ...; the 10th delegate from index = 9 to index = 17. A second scrolling gesture the first index may change to 15, and the final index 24. And so on. Scrolling in the opposite direction will have a similar effect, but the Document index numbers for each delegate will obviously start to decline (towards their original values).
382+
383+The following snippet, which modifies the above delegate definition, could demonstrate this effect if there were enough Documents to do so (i.e. some number greater than the number of delegates):
384+
385+\code
386+ListView {
387+ width: units.gu(45)
388+ height: units.gu(80)
389+ model: aQuery
390+
391+ delegate: Text {
392+ x: 66; y: 77
393+ text: index
394+ }
395+
396+}
397+\endcode
398+
399+The object called 'contents' contains one or more properties. This example demonstrates the retrieval of data based on the U1db.Index defined earlier (id: by-helloworld). In this instance the Index contained two expressions simultaniously, "hello.world.id" and "hello.world.message"
400+
401+\code
402+ListView {
403+ width: units.gu(45)
404+ height: units.gu(80)
405+ model: aQuery
406+
407+ delegate: Text {
408+ x: 66; y: 77
409+ text: "(" + index + ") '" + contents.message + " " + contents.id + "'"
410+ }
411+
412+}
413+\endcode
414+
415+*/
416
417=== added file 'examples/u1db-qt-example-6/u1db-qt-example-6.qml'
418--- examples/u1db-qt-example-6/u1db-qt-example-6.qml 1970-01-01 00:00:00 +0000
419+++ examples/u1db-qt-example-6/u1db-qt-example-6.qml 2013-07-04 14:13:28 +0000
420@@ -0,0 +1,227 @@
421+/*
422+ * Copyright (C) 2013 Canonical, Ltd.
423+ *
424+ * Authors:
425+ * Kevin Wright <kevin.wright@canonical.com>
426+ *
427+ * This program is free software; you can redistribute it and/or modify
428+ * it under the terms of the GNU Lesser General Public License as published by
429+ * the Free Software Foundation; version 3.
430+ *
431+ * This program is distributed in the hope that it will be useful,
432+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
433+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
434+ * GNU Lesser General Public License for more details.
435+ *
436+ * You should have received a copy of the GNU Lesser General Public License
437+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
438+ */
439+
440+import QtQuick 2.0
441+import U1db 1.0 as U1db
442+import Ubuntu.Components 0.1
443+
444+
445+Item {
446+
447+ width: units.gu(45)
448+ height: units.gu(80)
449+
450+ U1db.Database {
451+ id: aDatabase
452+ path: "aDatabase6"
453+ }
454+
455+ U1db.Database {
456+ id: aTargetDatabase
457+ path: "aTargetDatabase6"
458+ }
459+
460+ U1db.Document {
461+ id: aDocument1
462+ database: aDatabase
463+ docId: 'helloworld'
464+ create: true
465+ defaults:{"hello": { "world": { "message":"Hello World", "id": 1 } } }
466+
467+ }
468+
469+ U1db.Document {
470+ id: aDocument2
471+ database: aTargetDatabase
472+ docId: 'helloworld'
473+ create: true
474+ defaults:{"hello": { "world": [
475+ { "message":"Hello World", "id": 2 },
476+ { "message":"Hello World", "id": 2.5 }
477+ ] } }
478+ }
479+
480+ U1db.Index{
481+ database: aDatabase
482+ id: by_helloworld
483+ expression: ["hello.world.id","hello.world.message"]
484+ }
485+
486+ U1db.Query{
487+ id: aQuery
488+ index: by_helloworld
489+ query: [{"id":"*"},{"message":"Hel*"}]
490+ }
491+
492+ U1db.Index{
493+ database: aTargetDatabase
494+ id: by_target_helloworld
495+ expression: ["hello.world.id","hello.world.message"]
496+ }
497+
498+ U1db.Query{
499+ id: aTargetQuery
500+ index: by_target_helloworld
501+ query: [{"id":"*"},{"message":"Hel*"}]
502+ }
503+
504+ U1db.Synchronizer{
505+ id: aSynchronizer
506+ source: aDatabase
507+ targets: [{remote:true},
508+ {remote:false,
509+ id:aTargetQuery.index.database,
510+ resolve_to_source:true},
511+ {remote:true,
512+ ip:"127.0.0.1",
513+ port:":7777",
514+ name:"example1.u1db",
515+ resolve_to_source:true},
516+ {remote:"OK"}]
517+ synchronize: false
518+ }
519+
520+ MainView {
521+
522+ id: u1dbView
523+ width: units.gu(45)
524+ height: units.gu(80)
525+ anchors.top: parent.top;
526+
527+ Tabs {
528+ id: tabs
529+ anchors.fill: parent
530+
531+ Tab {
532+ objectName: "Tab1"
533+
534+ title: i18n.tr("Hello U1Db!")
535+
536+ page: Page {
537+ id: helloPage
538+
539+ Rectangle {
540+ width: units.gu(45)
541+ height: units.gu(40)
542+ anchors.top: parent.top;
543+ border.width: 1
544+
545+ Text {
546+ id: sourceLabel
547+ anchors.top: parent.top;
548+ font.bold: true
549+ text: "aDatabase6 Contents"
550+ }
551+
552+
553+ ListView {
554+ id: sourceListView
555+ width: units.gu(45)
556+ height: units.gu(20)
557+ anchors.top: sourceLabel.bottom;
558+ model: aQuery
559+
560+ delegate: Text {
561+ wrapMode: Text.WordWrap
562+ x: 6; y: 77
563+ text: {
564+ text: "(" + index + ") '" + contents.message + " " + contents.id + "'"
565+ }
566+ }
567+ }
568+
569+ Text {
570+ id: targetLabel
571+ anchors.top: sourceListView.bottom;
572+ font.bold: true
573+ text: "aTargetDatabase6 Contents"
574+ }
575+
576+
577+ ListView {
578+
579+ width: units.gu(45)
580+ height: units.gu(20)
581+ anchors.top: targetLabel.bottom;
582+ model: aTargetQuery
583+
584+ delegate: Text {
585+ wrapMode: Text.WordWrap
586+ x: 6; y: 77
587+ text: {
588+ text: "(" + index + ") '" + contents.message + " " + contents.id + "'"
589+ }
590+ }
591+ }
592+ }
593+
594+ Rectangle {
595+ id: lowerRectangle
596+ width: units.gu(45)
597+ height: units.gu(35)
598+ anchors.bottom: parent.bottom;
599+ border.width: 1
600+
601+ Text {
602+ id: errorsLabel
603+ anchors.top: parent.top;
604+ font.bold: true
605+ text: "Log:"
606+ }
607+
608+ ListView {
609+
610+ parent: lowerRectangle
611+ width: units.gu(45)
612+ height: units.gu(30)
613+ anchors.top: errorsLabel.bottom;
614+ model: aSynchronizer
615+ delegate:Text {
616+ width: units.gu(40)
617+ anchors.left: parent.left
618+ anchors.right: parent.right
619+ wrapMode: Text.WordWrap
620+ text: {
621+ text: errors
622+ }
623+ }
624+ }
625+
626+ Button{
627+ parent: lowerRectangle
628+ anchors.bottom: parent.bottom;
629+ text: "Sync"
630+ onClicked: aSynchronizer.synchronize = true
631+ anchors.left: parent.left
632+ anchors.right: parent.right
633+
634+ }
635+
636+ }
637+ }
638+
639+ }
640+
641+ }
642+
643+ }
644+
645+}
646+
647+
648
649=== modified file 'modules/U1db/CMakeLists.txt'
650--- modules/U1db/CMakeLists.txt 2013-05-01 22:49:50 +0000
651+++ modules/U1db/CMakeLists.txt 2013-07-04 14:13:28 +0000
652@@ -1,4 +1,5 @@
653 find_package(Qt5Quick REQUIRED)
654+find_package(Qt5Network REQUIRED)
655
656 get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION)
657 # See http://doc-snapshot.qt-project.org/5.0/qtcore/qlibraryinfo.html#LibraryLocation-enum
658@@ -13,6 +14,7 @@
659 include_directories(
660 ${CMAKE_CURRENT_SOURCE_DIR}
661 ${Qt5Sql_INCLUDE_DIRS}
662+ ${Qt5Network_INCLUDE_DIRS}
663 )
664
665 add_library(U1DBPlugin SHARED ${U1DBPlugin_SRCS})
666@@ -21,6 +23,7 @@
667 target_link_libraries(U1DBPlugin
668 ${U1DB_QT_LIBNAME}
669 ${Qt5Quick_LIBRARIES}
670+ ${Qt5Network_LIBRARIES}
671 )
672
673 include_directories(
674@@ -28,6 +31,7 @@
675 ${U1DB_INCLUDE_DIRS}
676 ${CMAKE_CURRENT_BINARY_DIR}
677 ${Qt5Quick_INCLUDE_DIRS}
678+ ${Qt5Network_INCLUDE_DIRS}
679 )
680
681 # copy qmldir file into build directory for shadow builds
682
683=== modified file 'modules/U1db/plugin.cpp'
684--- modules/U1db/plugin.cpp 2013-02-15 11:43:45 +0000
685+++ modules/U1db/plugin.cpp 2013-07-04 14:13:28 +0000
686@@ -21,6 +21,7 @@
687 #include "document.h"
688 #include "index.h"
689 #include "query.h"
690+#include "synchronizer.h"
691 #include "plugin.h"
692 #include <qqml.h>
693
694@@ -32,5 +33,6 @@
695 qmlRegisterType<Document>(uri, 1, 0, "Document");
696 qmlRegisterType<Index>(uri, 1, 0, "Index");
697 qmlRegisterType<Query>(uri, 1, 0, "Query");
698+ qmlRegisterType<Synchronizer>(uri, 1, 0, "Synchronizer");
699 }
700
701
702=== modified file 'src/CMakeLists.txt'
703--- src/CMakeLists.txt 2013-04-23 10:39:54 +0000
704+++ src/CMakeLists.txt 2013-07-04 14:13:28 +0000
705@@ -6,6 +6,7 @@
706 document.cpp
707 index.cpp
708 query.cpp
709+ synchronizer.cpp
710 )
711
712 # Generated files
713@@ -14,6 +15,7 @@
714 moc_document.cpp
715 moc_index.cpp
716 moc_query.cpp
717+ moc_synchronizer.cpp
718 )
719 set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${U1DB_QT_GENERATED}")
720
721@@ -27,6 +29,7 @@
722 ${CMAKE_CURRENT_SOURCE_DIR}
723 ${CMAKE_CURRENT_BINARY_DIR}
724 ${Qt5Core_INCLUDE_DIRS}
725+ ${Qt5Network_INCLUDE_DIRS}
726 ${Qt5Sql_INCLUDE_DIRS}
727 ${U1DB_INCLUDE_DIRS}
728 )
729@@ -35,6 +38,7 @@
730 target_link_libraries(${U1DB_QT_LIBNAME}
731 ${Qt5Core_LIBRARIES}
732 ${Qt5Sql_LIBRARIES}
733+ ${Qt5Network_LIBRARIES}
734 ${U1DB_LDFLAGS}
735 )
736
737@@ -50,7 +54,7 @@
738 LIBRARY DESTINATION lib${LIB_SUFFIX}
739 )
740
741-install(FILES global.h database.h document.h index.h query.h
742+install(FILES global.h database.h document.h index.h query.h synchronizer.h
743 DESTINATION ${INCLUDE_INSTALL_DIR}
744 )
745
746
747=== modified file 'src/database.cpp'
748--- src/database.cpp 2013-04-29 23:54:19 +0000
749+++ src/database.cpp 2013-07-04 14:13:28 +0000
750@@ -106,7 +106,11 @@
751 /* A unique ID is used for the connection name to ensure that we aren't
752 re-using or replacing other opend databases. */
753 if (!m_db.isValid())
754+<<<<<<< TREE
755 m_db = QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString());
756+=======
757+ m_db = QSqlDatabase::addDatabase("QSQLITE",QUuid::createUuid().toString());
758+>>>>>>> MERGE-SOURCE
759 if (!m_db.isValid())
760 return setError("QSqlDatabase error");
761 m_db.setDatabaseName(path);
762@@ -250,6 +254,7 @@
763 return QVariant();
764 }
765
766+
767 /*!
768 Returns the contents of a document by \a docId in a form that QML recognizes
769 as a Variant object, it's identical to Document::getContents() with the
770@@ -284,6 +289,9 @@
771 return setError(QString("Failed to get document %1: %2\n%3").arg(docId).arg(query.lastError().text()).arg(query.lastQuery())) ? QVariant() : QVariant();
772 }
773
774+/*!
775+ The increaseVectorClockRev(int oldRev) function is deprecated.
776+ */
777 static int
778 increaseVectorClockRev(int oldRev)
779 {
780@@ -291,6 +299,233 @@
781 }
782
783 /*!
784+ The getNextDocRevisionNumber(QString doc_id) function
785+creates a new revision number. It returns a string for use
786+in the document table's 'doc_rev' field.
787+ */
788+
789+QString Database::getNextDocRevisionNumber(QString doc_id)
790+{
791+
792+ QString revision_number = getReplicaUid()+":1";
793+
794+ QString current_revision_number = getCurrentDocRevisionNumber(doc_id);
795+
796+ /*!
797+ Some revisions contain information from previous
798+conflicts/syncs. Revisions are delimited by '|'.
799+
800+ */
801+
802+ QStringList current_revision_list = current_revision_number.split("|");
803+
804+ Q_FOREACH (QString current_revision, current_revision_list) {
805+
806+ /*!
807+ Each revision contains two pieces of information,
808+the uid of the database that made the revsion, and a counter
809+for the revsion. This information is delimited by ':'.
810+
811+ */
812+
813+ QStringList current_revision_number_list = current_revision.split(":");
814+
815+ if(current_revision_number_list[0]==getReplicaUid()) {
816+
817+ /*!
818+ If the current revision uid is the same as this Database's uid the counter portion is increased by one.
819+
820+ */
821+
822+ int revision_generation_number = current_revision_number_list[1].toInt()+1;
823+
824+ revision_number = getReplicaUid()+":"+QString::number(revision_generation_number);
825+
826+ }
827+ else {
828+
829+ /*!
830+ If the current revision uid is not the same as this Database's uid then the revision represents a change that originated in another database.
831+
832+ */
833+ //revision_number+="|"+current_revision;
834+
835+ /* ##KW## Not sure if the above is necessary,
836+ *and did not appear to be working as intended either.
837+ *
838+ * Commented out, but maybe OK to delete.
839+ */
840+ }
841+
842+ }
843+
844+ /*!
845+ The Database UID has curly brackets, but they are not required for the revision number and need to be removed.
846+
847+ */
848+
849+ revision_number = revision_number.replace("{","");
850+
851+ revision_number = revision_number.replace("}","");
852+
853+ return revision_number;
854+
855+}
856+
857+/*!
858+
859+ The getCurrentDocRevisionNumber(QString doc_id) function
860+returns the current string value from the document table's
861+doc_rev field.
862+
863+ */
864+
865+QString Database::getCurrentDocRevisionNumber(QString doc_id){
866+ if (!initializeIfNeeded())
867+ return QString();
868+
869+ QSqlQuery query(m_db.exec());
870+
871+ query.prepare("SELECT doc_rev from document WHERE doc_id = :docId");
872+ query.bindValue(":docId", doc_id);
873+ if (query.exec())
874+ {
875+ while (query.next())
876+ {
877+ return query.value("doc_rev").toString();
878+ }
879+
880+ }
881+ return QString();
882+}
883+
884+/*!
885+ * \brief Database::updateSyncLog
886+ * \param uid
887+ * \param generation
888+ * \param transaction_id
889+ *
890+ * This method is used at the end of a synchronization session,
891+ * to update the database with the latest information known about the peer
892+ * database that was synced against.
893+ */
894+void Database::updateSyncLog(bool insert, QString uid, QString generation, QString transaction_id)
895+{
896+
897+ if (!initializeIfNeeded())
898+ return;
899+
900+ QSqlQuery query(m_db.exec());
901+
902+ if(insert==true){
903+ query.prepare("INSERT INTO sync_log(known_generation,known_transation_id,known_transation_id) VALUES(:knownGeneration, :knownTransactionId, :replicaUid)");
904+
905+ }
906+ else{
907+ query.prepare("UPDATE sync_log SET known_generation = :knownGeneration, known_transation_id = :knownTransactionId WHERE replica_uid = :replicaUid");
908+ }
909+
910+
911+ query.bindValue(":replicaUid", uid);
912+ query.bindValue(":knownGeneration", generation);
913+ query.bindValue(":knownTransactionId", transaction_id);
914+ if (!query.exec())
915+ {
916+ qDebug() << query.lastError();
917+
918+ }
919+
920+}
921+
922+void Database::updateDocRevisionNumber(QString doc_id,QString revision){
923+ if (!initializeIfNeeded())
924+ return;
925+
926+ QSqlQuery query(m_db.exec());
927+
928+ query.prepare("UPDATE document SET doc_rev = :revisionId WHERE doc_id = :docId");
929+ query.bindValue(":docId", doc_id);
930+ query.bindValue(":revisionId", revision);
931+ if (!query.exec())
932+ {
933+ qDebug() << query.lastError();
934+
935+ }
936+
937+}
938+
939+/*!
940+ The getCurrentGenerationNumber() function searches for the
941+current generation number from the sqlite_sequence table.
942+The return value can then be used during a synchronization session,
943+amongst other things.
944+
945+ */
946+
947+int Database::getCurrentGenerationNumber(){
948+
949+ int sequence_number = -1;
950+
951+ QSqlQuery query(m_db.exec());
952+
953+ query.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'transaction_log'");
954+
955+ if (query.exec())
956+ {
957+ while (query.next())
958+ {
959+ sequence_number = (query.value("seq").toInt());
960+
961+ }
962+
963+ }
964+
965+ return sequence_number;
966+
967+}
968+
969+/*!
970+ The generateNewTransactionId() function generates a random
971+transaction id string, for use when creating new transations.
972+
973+ */
974+
975+QString Database::generateNewTransactionId(){
976+
977+ QString uid = "T-"+QUuid::createUuid().toString();
978+ uid = uid.replace("}","");
979+ uid = uid.replace("{","");
980+ return uid;
981+
982+}
983+
984+/*!
985+ Each time a document in the Database is created or updated a
986+new transaction is performed, and information about it inserted into the
987+transation_log table using the createNewTransaction(QString doc_id)
988+function.
989+
990+ */
991+
992+int Database::createNewTransaction(QString doc_id){
993+
994+ QString transaction_id = generateNewTransactionId();
995+
996+ QSqlQuery query(m_db.exec());
997+
998+ QString queryString = "INSERT INTO transaction_log(doc_id, transaction_id) VALUES('"+doc_id+"', '"+transaction_id+"')";
999+
1000+ if (!query.exec(queryString)){
1001+ return -1;
1002+ }
1003+ else{
1004+ return 0;
1005+ }
1006+
1007+ return -1;
1008+}
1009+
1010+/*!
1011 Updates the existing \a contents of the document identified by \a docId if
1012 there's no error.
1013 If no \a docId is given or \a docId is an empty string the \a contents will be
1014@@ -308,12 +543,16 @@
1015 /* FIXME: Conflicts */
1016
1017 int newRev = increaseVectorClockRev(7/*contents.rev*/);
1018+ // ##KW## maybe this can be removed as it is replaced by revision_number.
1019+
1020+ QString revision_number = getNextDocRevisionNumber(newOrEmptyDocId);
1021+
1022 QSqlQuery query(m_db.exec());
1023 if (oldDoc.isValid())
1024 {
1025 query.prepare("UPDATE document SET doc_rev=:docRev, content=:docJson WHERE doc_id = :docId");
1026 query.bindValue(":docId", newOrEmptyDocId);
1027- query.bindValue(":docRev", newRev);
1028+ query.bindValue(":docRev", revision_number);
1029 // Parse Variant from QML as JsonDocument, fallback to string
1030 QString json(QJsonDocument::fromVariant(contents).toJson());
1031 query.bindValue(":docJson", json.isEmpty() ? contents : json);
1032@@ -323,6 +562,9 @@
1033 query.bindValue(":docId", newOrEmptyDocId);
1034 if (!query.exec())
1035 return setError(QString("Failed to delete document field %1: %2\n%3").arg(newOrEmptyDocId).arg(query.lastError().text()).arg(query.lastQuery())) ? -1 : -1;
1036+
1037+ createNewTransaction(newOrEmptyDocId);
1038+
1039 }
1040 else
1041 {
1042@@ -333,12 +575,14 @@
1043
1044 query.prepare("INSERT INTO document (doc_id, doc_rev, content) VALUES (:docId, :docRev, :docJson)");
1045 query.bindValue(":docId", newOrEmptyDocId);
1046- query.bindValue(":docRev", newRev);
1047+ query.bindValue(":docRev", revision_number);
1048 // Parse Variant from QML as JsonDocument, fallback to string
1049 QJsonDocument json(QJsonDocument::fromVariant(contents));
1050 query.bindValue(":docJson", json.isEmpty() ? contents : json.toJson());
1051 if (!query.exec())
1052 return setError(QString("Failed to put document %1: %2\n%3").arg(docId).arg(query.lastError().text()).arg(query.lastQuery())) ? -1 : -1;
1053+
1054+ createNewTransaction(newOrEmptyDocId);
1055 }
1056
1057 beginResetModel();
1058@@ -353,6 +597,14 @@
1059 return newRev;
1060 }
1061
1062+void Database::resetModel(){
1063+
1064+ beginResetModel();
1065+ endResetModel();
1066+
1067+}
1068+
1069+
1070 /*!
1071 Returns a list of all stored documents by their docId.
1072 */
1073@@ -516,6 +768,71 @@
1074 return list;
1075 }
1076
1077+/* Handy functions for synchronization. */
1078+
1079+QList<QString> Database::listTransactionsSince(int generation){
1080+
1081+ QList<QString> list;
1082+
1083+ if (!initializeIfNeeded())
1084+ return list;
1085+
1086+ QSqlQuery query(m_db.exec());
1087+
1088+ QString queryStmt = "SELECT generation, doc_id, transaction_id FROM transaction_log where generation > "+QString::number(generation);
1089+
1090+ if (query.exec(queryStmt))
1091+ {
1092+ while (query.next())
1093+ {
1094+ list.append(query.value("generation").toString()+"|"+query.value("doc_id").toString()+"|"+query.value("transaction_id").toString());
1095+ }
1096+
1097+ return list;
1098+
1099+ }
1100+
1101+ return list;
1102+
1103+}
1104+
1105+QMap<QString,QVariant> Database::getSyncLogInfo(QMap<QString,QVariant> lastSyncInformation, QString uid, QString prefix){
1106+
1107+ if (!initializeIfNeeded())
1108+ return lastSyncInformation;
1109+
1110+ QString queryStmt = "SELECT known_transaction_id, known_generation FROM sync_log WHERE replica_uid = '"+uid +"'";
1111+
1112+ QSqlQuery query(m_db.exec());
1113+
1114+ if (query.exec(queryStmt))
1115+ {
1116+ while (query.next())
1117+ {
1118+ lastSyncInformation.insert(prefix + "_replica_generation", query.value(1).toInt());
1119+ lastSyncInformation.insert(prefix + "_replica_transaction_id",query.value(0).toString());
1120+ return lastSyncInformation;
1121+ }
1122+
1123+ }
1124+
1125+ /* Below does not work from the Database class.
1126+ * This is leftover from the migration of this function
1127+ * from the Synchronizer class.
1128+ *
1129+ * Why is it left here? As a reminder to do something to
1130+ * resolve the issue of not being able to update Sychnronizer's
1131+ * m_errors from the Database class.
1132+ */
1133+
1134+ //m_errors.append(m_db.lastError().text());
1135+
1136+
1137+ return lastSyncInformation;
1138+}
1139+
1140+
1141+
1142 QT_END_NAMESPACE_U1DB
1143
1144 #include "moc_database.cpp"
1145
1146=== modified file 'src/database.h'
1147--- src/database.h 2013-04-23 15:17:24 +0000
1148+++ src/database.h 2013-07-04 14:13:28 +0000
1149@@ -36,10 +36,12 @@
1150 public:
1151 Database(QObject* parent = 0);
1152
1153+
1154 // QAbstractListModel
1155 QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
1156 QHash<int, QByteArray>roleNames() const;
1157 int rowCount(const QModelIndex & parent = QModelIndex()) const;
1158+ void resetModel();
1159
1160 QString getPath();
1161 void setPath(const QString& path);
1162@@ -51,6 +53,18 @@
1163 Q_INVOKABLE QString putIndex(const QString& index_name, QStringList expressions);
1164 Q_INVOKABLE QStringList getIndexExpressions(const QString& indexName);
1165 Q_INVOKABLE QStringList getIndexKeys(const QString& indexName);
1166+
1167+ /* Functions handy for Synchronization */
1168+ QString getNextDocRevisionNumber(QString doc_id);
1169+ QString getCurrentDocRevisionNumber(QString doc_id);
1170+ void updateDocRevisionNumber(QString doc_id,QString revision);
1171+ void updateSyncLog(bool insert, QString uid, QString generation, QString transaction_id);
1172+ QList<QString> listTransactionsSince(int generation);
1173+ QMap<QString,QVariant> getSyncLogInfo(QMap<QString,QVariant> lastSyncInformation, QString uid, QString prefix);
1174+
1175+
1176+
1177+
1178 Q_SIGNALS:
1179 void pathChanged(const QString& path);
1180 void errorChanged(const QString& error);
1181@@ -63,7 +77,7 @@
1182 */
1183 void docLoaded(const QString& docId, QVariant content) const;
1184 private:
1185- Q_DISABLE_COPY(Database)
1186+ //Q_DISABLE_COPY(Database)
1187 QString m_path;
1188 QSqlDatabase m_db;
1189 QString m_error;
1190@@ -73,6 +87,11 @@
1191 bool initializeIfNeeded(const QString& path=":memory:");
1192 bool setError(const QString& error);
1193 QString getDocIdByRow(int row) const;
1194+
1195+ int createNewTransaction(QString doc_id);
1196+ QString generateNewTransactionId();
1197+ int getCurrentGenerationNumber();
1198+
1199 };
1200
1201 QT_END_NAMESPACE_U1DB
1202
1203=== modified file 'src/index.cpp'
1204--- src/index.cpp 2013-05-10 13:22:44 +0000
1205+++ src/index.cpp 2013-07-04 14:13:28 +0000
1206@@ -146,7 +146,19 @@
1207 if (m_expression == expression)
1208 return;
1209
1210- m_expression = expression;
1211+<<<<<<< TREE
1212+ m_expression = expression;
1213+=======
1214+ m_expression = expression;
1215+
1216+ if (m_database)
1217+ {
1218+ m_database->putIndex(m_name, expression);
1219+ Q_EMIT dataInvalidated();
1220+ }
1221+
1222+
1223+>>>>>>> MERGE-SOURCE
1224
1225 if (m_database)
1226 {
1227
1228=== modified file 'src/query.cpp'
1229--- src/query.cpp 2013-05-01 23:06:01 +0000
1230+++ src/query.cpp 2013-07-04 14:13:28 +0000
1231@@ -59,6 +59,7 @@
1232 QVariant
1233 Query::data(const QModelIndex & index, int role) const
1234 {
1235+
1236 if (role == 0) // contents
1237 return m_results.at(index.row());
1238 if (role == 1) // docId
1239@@ -150,8 +151,16 @@
1240
1241 }
1242
1243+ resetModel();
1244+
1245 Q_EMIT documentsChanged(m_documents);
1246 Q_EMIT resultsChanged(m_results);
1247+
1248+}
1249+
1250+void Query::resetModel(){
1251+ beginResetModel();
1252+ endResetModel();
1253 }
1254
1255 /*!
1256
1257=== modified file 'src/query.h'
1258--- src/query.h 2013-04-25 13:38:33 +0000
1259+++ src/query.h 2013-07-04 14:13:28 +0000
1260@@ -52,6 +52,8 @@
1261 QStringList getDocuments();
1262 QList<QVariant> getResults();
1263
1264+ void resetModel();
1265+
1266 Q_SIGNALS:
1267 void indexChanged(Index* index);
1268 void queryChanged(QVariant query);
1269
1270=== added file 'src/synchronizer.cpp'
1271--- src/synchronizer.cpp 1970-01-01 00:00:00 +0000
1272+++ src/synchronizer.cpp 2013-07-04 14:13:28 +0000
1273@@ -0,0 +1,879 @@
1274+/*
1275+ * Copyright (C) 2013 Canonical, Ltd.
1276+ *
1277+ * Authors:
1278+ * Kevin Wright <kevin.wright@canonical.com>
1279+ *
1280+ * This program is free software; you can redistribute it and/or modify
1281+ * it under the terms of the GNU Lesser General Public License as published by
1282+ * the Free Software Foundation; version 3.
1283+ *
1284+ * This program is distributed in the hope that it will be useful,
1285+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1286+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1287+ * GNU Lesser General Public License for more details.
1288+ *
1289+ * You should have received a copy of the GNU Lesser General Public License
1290+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1291+ */
1292+
1293+#include <QDebug>
1294+#include <QSqlQuery>
1295+#include <QFile>
1296+#include <QSqlError>
1297+#include <QUuid>
1298+#include <QStringList>
1299+#include <QJsonDocument>
1300+
1301+#include "synchronizer.h"
1302+#include "private.h"
1303+
1304+QT_BEGIN_NAMESPACE_U1DB
1305+
1306+/*!
1307+ \class Synchronizer
1308+ \inmodule U1db
1309+ \ingroup modules
1310+
1311+ \brief The Synchronizer class handles synchronizing between two databases.
1312+
1313+*/
1314+
1315+/*
1316+
1317+ Below this line are methods that are standards in declarative API design. They include the element instantiation, default settings for properties and get/set methods for properties.
1318+
1319+*/
1320+
1321+/*!
1322+ Instantiate a new Synchronizer with an optional \a parent, usually by declaring it as a QML item.
1323+
1324+ Synchronizer elements sync two databases together, a 'source' database and a remote or local 'target' database.
1325+
1326+ */
1327+
1328+Synchronizer::Synchronizer(QObject *parent) :
1329+ QAbstractListModel(parent), m_synchronize(false)
1330+{
1331+ QObject::connect(this, &Synchronizer::syncChanged, this, &Synchronizer::onSyncChanged);
1332+}
1333+
1334+/*!
1335+ \internal
1336+ *Used to implement QAbstractListModel
1337+ *Implements the variables exposed to the Delegate in a model
1338+ */
1339+QVariant
1340+Synchronizer::data(const QModelIndex & index, int role) const
1341+{
1342+ if (role == 0) // errors
1343+ return m_errors.at(index.row());
1344+ return QVariant();
1345+}
1346+
1347+/*!
1348+ \internal
1349+ Used to implement QAbstractListModel
1350+ The number of rows: the number of documents given by the query.
1351+ */
1352+int
1353+Synchronizer::rowCount(const QModelIndex & parent) const
1354+{
1355+ return m_errors.count();
1356+}
1357+
1358+/*!
1359+ \internal
1360+ Used to implement QAbstractListModel
1361+ Defines \b{errors} as the variable exposed to the Delegate in a model
1362+ \b{index} is supported out of the box.
1363+ */
1364+QHash<int, QByteArray>
1365+Synchronizer::roleNames() const
1366+{
1367+ QHash<int, QByteArray> roles;
1368+ roles.insert(0, "errors");
1369+ return roles;
1370+}
1371+
1372+
1373+/*!
1374+ \property Synchronizer::source
1375+ */
1376+void Synchronizer::setSource(Database* source)
1377+{
1378+
1379+ if (m_source == source)
1380+ return;
1381+
1382+ if (m_source)
1383+ QObject::disconnect(m_source, 0, this, 0);
1384+
1385+ m_source = source;
1386+
1387+ Q_EMIT sourceChanged(source);
1388+}
1389+
1390+/*!
1391+ \property Synchronizer::targets
1392+ */
1393+void Synchronizer::setTargets(QVariant targets)
1394+{
1395+
1396+ if (m_targets == targets)
1397+ return;
1398+
1399+ //if (m_targets)
1400+ // QObject::disconnect(m_targets, 0, this, 0);
1401+
1402+ m_targets = targets;
1403+ Q_EMIT targetsChanged(targets);
1404+}
1405+
1406+/*!
1407+ \property Synchronizer::synchronize
1408+ */
1409+void Synchronizer::setSync(bool synchronize)
1410+{
1411+
1412+ if (m_synchronize == synchronize)
1413+ return;
1414+
1415+ m_synchronize = synchronize;
1416+ Q_EMIT syncChanged(synchronize);
1417+}
1418+
1419+/*!
1420+ \property Synchronizer::errors
1421+ */
1422+void Synchronizer::setResolveToSource(bool resolve_to_source)
1423+{
1424+ if (m_resolve_to_source == resolve_to_source)
1425+ return;
1426+
1427+ m_resolve_to_source = resolve_to_source;
1428+ Q_EMIT resolveToSourceChanged(resolve_to_source);
1429+}
1430+
1431+/*!
1432+ \property Synchronizer::errors
1433+ */
1434+void Synchronizer::setErrors(QList<QString> errors)
1435+{
1436+ if (m_errors == errors)
1437+ return;
1438+
1439+ m_errors = errors;
1440+ Q_EMIT errorsChanged(errors);
1441+}
1442+
1443+/*!
1444+
1445+ */
1446+Database* Synchronizer::getSource()
1447+{
1448+ return m_source;
1449+}
1450+
1451+/*!
1452+
1453+ */
1454+QVariant Synchronizer::getTargets()
1455+{
1456+ return m_targets;
1457+}
1458+
1459+
1460+/*!
1461+
1462+ */
1463+bool Synchronizer::getSync()
1464+{
1465+ return m_synchronize;
1466+}
1467+
1468+
1469+/*!
1470+
1471+ */
1472+bool Synchronizer::getResolveToSource(){
1473+ return m_resolve_to_source;
1474+}
1475+
1476+/*!
1477+
1478+ */
1479+QList<QString> Synchronizer::getErrors(){
1480+ return m_errors;
1481+}
1482+
1483+
1484+
1485+/*
1486+
1487+ Below this line represents the unique API methods that are not part of standard declarative API design.
1488+
1489+*/
1490+
1491+/*!
1492+ The onSyncChanged(bool synchronize) method is where all the sync
1493+magic starts to happen.
1494+
1495+ */
1496+void Synchronizer::onSyncChanged(bool synchronize){
1497+
1498+ Database* source = getSource();
1499+
1500+ QList<QVariant> sync_targets;
1501+
1502+ /*!
1503+ * The validator map contains key and value pair definitions,
1504+ *that are used to confirm that the values provided for each
1505+ *database target are of the expected type for a particular key.
1506+ */
1507+
1508+ QMap<QString,QString>validator;
1509+
1510+ validator.insert("remote","bool");
1511+ validator.insert("location","QString");
1512+ validator.insert("resolve_to_source","bool");
1513+ validator.insert("errors", "QStringList");
1514+
1515+ /*!
1516+ * The mandatory map contains the keys that are used to confirm
1517+ *that a database target definition contains all the mandatory keys
1518+ *necessary for synchronizing.
1519+ */
1520+
1521+ QList<QString>mandatory;
1522+
1523+ mandatory.append("remote");
1524+ mandatory.append("resolve_to_source");
1525+
1526+ if(synchronize == true){
1527+
1528+ /*!
1529+ 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).
1530+ */
1531+
1532+ sync_targets=getValidTargets(validator, mandatory);
1533+
1534+ /*!
1535+ * Once the list of sync targets has been generated the sync activity
1536+ *can be initiated via the synchronizeTargets function.
1537+ */
1538+
1539+ synchronizeTargets(source, sync_targets);
1540+
1541+ /*!
1542+ * After the synchronization is complete the model is reset so that
1543+ *log and error messages are available at the application level.
1544+ *
1545+ */
1546+
1547+ beginResetModel();
1548+ endResetModel();
1549+
1550+ /*!
1551+ The convenience signals errorsChanged and syncCompleted are
1552+emitted after the model has been reset.
1553+ */
1554+
1555+ Q_EMIT errorsChanged(m_errors);
1556+ Q_EMIT syncCompleted();
1557+
1558+ /*!
1559+ * The sync boolean value is reset to its default value (false)
1560+ *once all sync activity is complete.
1561+ */
1562+
1563+ setSync(false);
1564+
1565+ }
1566+ else{
1567+
1568+ }
1569+}
1570+
1571+
1572+void Synchronizer::remotePostSyncInfoFinished(QNetworkReply* reply)
1573+{
1574+
1575+ QUrl postUrl = reply->request().url();
1576+
1577+ QByteArray data = reply->readAll();
1578+
1579+ QString replyData = QString(data);
1580+
1581+ qDebug() << replyData;
1582+
1583+ reply->close();
1584+
1585+}
1586+
1587+void Synchronizer::remoteGetSyncInfoFinished(QNetworkReply* reply)
1588+{
1589+
1590+ QUrl postUrl = reply->request().url();
1591+
1592+ QByteArray data = reply->readAll();
1593+
1594+ QString replyData = QString(data);
1595+
1596+ reply->close();
1597+
1598+ QVariantMap replyMap;
1599+
1600+ QJsonDocument replyJson = QJsonDocument::fromJson(replyData.toUtf8());
1601+
1602+ QVariant replyVariant = replyJson.toVariant();
1603+
1604+ replyMap = replyVariant.toMap();
1605+
1606+ double source_replica_generation = replyMap["source_replica_generation"].toDouble();
1607+ QString source_replica_uid = replyMap["source_replica_uid"].toString();
1608+ QString source_transaction_id = replyMap["source_transaction_id"].toString();
1609+ double target_replica_generation = replyMap["target_replica_generation"].toDouble();
1610+ QString target_replica_transaction_id = replyMap["target_replica_transaction_id"].toString();
1611+ QString target_replica_uid = replyMap["target_replica_uid"].toString();
1612+
1613+ QNetworkRequest request(postUrl);
1614+
1615+ request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-u1db-sync-stream");
1616+
1617+ QNetworkAccessManager *manager = new QNetworkAccessManager(this);
1618+
1619+ connect(manager, &QNetworkAccessManager::finished, this, &Synchronizer::remotePostSyncInfoFinished);
1620+
1621+ QString postData;
1622+
1623+ postData = "[\r\n";
1624+ postData += "{\"last_known_generation\": 0, \"last_known_trans_id\": \"\"},\r\n";
1625+
1626+ QList<QString> transactions = m_source->listTransactionsSince(source_replica_generation);
1627+
1628+ Q_FOREACH(QString transaction,transactions){
1629+ QStringList transactionData = transaction.split("|");
1630+ postData += "{\"id\": \""+transactionData[1]+"\", \"rev\": \""+m_source->getCurrentDocRevisionNumber(transactionData[1])+"\", \"content\": \"{}\", \"generation\": "+transactionData[0]+", \"trans_id\": \""+transactionData[2]+"\"},\r\n";
1631+ }
1632+
1633+
1634+ postData += "]";
1635+
1636+ QNetworkReply *nextReply = manager->post(QNetworkRequest(request),postData.toUtf8());
1637+
1638+ qDebug()<<nextReply->errorString() << " " << postData.toUtf8();
1639+
1640+
1641+}
1642+
1643+/*!
1644+ * \brief Synchronizer::getValidTargets confirms that each sync target definition is valid.
1645+ * \param validator
1646+ * \param mandatory
1647+ * \return
1648+ */
1649+
1650+QList<QVariant> Synchronizer::getValidTargets(QMap<QString,QString>validator, QList<QString>mandatory){
1651+
1652+ QList<QVariant> sync_targets;
1653+
1654+ int index = 0;
1655+
1656+ QList<QVariant> targets = getTargets().toList();
1657+
1658+ Q_FOREACH (QVariant target_variant, targets)
1659+ {
1660+ index++;
1661+ QString index_number = QString::number(index);
1662+
1663+ QMap<QString, QVariant> target = target_variant.toMap();
1664+
1665+ bool valid = true;
1666+ bool complete = true;
1667+
1668+ QMapIterator<QString, QVariant> i(target);
1669+ while (i.hasNext()) {
1670+
1671+ i.next();
1672+
1673+ //qDebug() << i.value().typeName();
1674+
1675+ if(validator.contains(i.key())&&validator[i.key()]!=i.value().typeName()){
1676+ valid = false;
1677+
1678+ m_errors.append("<b><font color=\"red\">Database "+index_number+"</font></b>: For Key: `" + i.key() + "` Expecting type `" + validator[i.key()] + "`, but received type `" + i.value().typeName()+"`");
1679+ target.insert("sync",false);
1680+
1681+ break;
1682+ }
1683+
1684+ if(valid==false){
1685+ targets.removeOne(target);
1686+ break;
1687+ }
1688+ else{
1689+ QListIterator<QString> j(mandatory);
1690+
1691+ while(j.hasNext()){
1692+ QString value = j.next();
1693+ if(!target.contains(value)){
1694+ m_errors.append("<b><font color=\"red\">Database "+index_number+"</font></b>: Expected key `" + value + "`, but it is not present.");
1695+ target.insert("sync",false);
1696+ targets.removeOne(target);
1697+ complete = false;
1698+ break;
1699+ }
1700+ }
1701+ if(complete==false){
1702+ break;
1703+ }
1704+
1705+ }
1706+ }
1707+
1708+ if(target.contains("sync")&&target["sync"]==false){
1709+ m_errors.append("<b><font color=\"red\">Error</font></b>: Database Index "+index_number+" was not synced due to errors. Please check its properties and try again later.");
1710+ }
1711+ else
1712+ {
1713+ target.insert("sync",true);
1714+ sync_targets.append(target);
1715+ }
1716+
1717+ }
1718+
1719+ return sync_targets;
1720+
1721+}
1722+
1723+void Synchronizer::synchronizeTargets(Database *source, QVariant targets){
1724+
1725+ if(targets.typeName()== QStringLiteral("QVariantList")){
1726+
1727+ QList<QVariant> target_list = targets.toList();
1728+
1729+ QListIterator<QVariant> i(target_list);
1730+
1731+ int target_index = -1;
1732+
1733+ while(i.hasNext()){
1734+
1735+ target_index++;
1736+
1737+ QVariant target = i.next();
1738+
1739+ if(target.typeName()== QStringLiteral("QVariantMap")){
1740+ QMap<QString,QVariant> target_map = target.toMap();
1741+
1742+ if(target_map.contains("remote")&&target_map["remote"]==false){
1743+ if(target_map.contains("sync")&&target_map["sync"]==true){
1744+ m_errors.append("<b><font color=\"green\">Log</font></b>: Valid target index "+QString::number(target_index)+" good for local to local sync with source database.");
1745+ syncLocalToLocal(source, target_map);
1746+ }
1747+ }
1748+ else if(target_map.contains("remote")&&target_map["remote"]==true){
1749+ if(target_map.contains("sync")&&target_map["sync"]==true){
1750+ m_errors.append("<b><font color=\"red\">Error</font></b>: Remote database sync is under construction. Valid target index "+QString::number(target_index)+" was not synced. Try again later.");
1751+ //ip
1752+ //port
1753+ //name
1754+ //GET /thedb/sync-from/my_replica_uid
1755+
1756+ QString source_uid = getUidFromLocalDb(source->getPath());
1757+ QString get_string = target_map["name"].toString()+"/sync-from/"+source_uid;
1758+ QString url_string = "http://"+target_map["ip"].toString();
1759+ QString full_get_request = url_string+"/"+get_string;
1760+
1761+ QNetworkAccessManager *manager = new QNetworkAccessManager(this);
1762+
1763+
1764+ QUrl url(full_get_request);
1765+ url.setPort(7777);
1766+
1767+
1768+ QNetworkRequest request(url);
1769+
1770+ connect(manager, &QNetworkAccessManager::finished, this, &Synchronizer::remoteGetSyncInfoFinished);
1771+
1772+ QNetworkReply *reply = manager->get(QNetworkRequest(request));
1773+
1774+ qDebug() << "Reply: " << reply->errorString();
1775+
1776+ }
1777+ }
1778+ else{
1779+ m_errors.append("<b><font color=\"red\">Undefined Error</font></b>: Valid target index "+QString::number(target_index)+" was not synced. Please check its properties and try again later.");
1780+ }
1781+ }
1782+
1783+
1784+ }
1785+
1786+ }
1787+
1788+}
1789+
1790+void Synchronizer::syncLocalToLocal(Database *sourceDb, QMap<QString,QVariant> target)
1791+{
1792+
1793+ QString target_db_name = target["location"].toString();
1794+
1795+ Database *targetDb;
1796+ Index *targetIndex;
1797+ Query *targetQuery;
1798+
1799+ Index *sourceIndex;
1800+ Query *sourceQuery;
1801+
1802+ /*
1803+ * ##KW## The two lists below are currently not used for anything
1804+ * as they were added for functionality that turned out not to be
1805+ * necessary. They are left here in case they might be convenient
1806+ * for something else.
1807+ *
1808+ * Note: These relate to the function syncDocument's return value.
1809+ */
1810+
1811+ QList<QVariant> source_results;
1812+ QList<QVariant> target_results;
1813+
1814+ if(target.contains("id")){
1815+ targetDb = (Database*)target["id"].value<QObject*>();
1816+ }
1817+
1818+ if(target.contains("target_query")){
1819+ targetQuery = (Query*)target["target_query"].value<QObject*>();
1820+ targetIndex = targetQuery->getIndex();
1821+ targetDb = targetIndex->getDatabase();
1822+ }
1823+
1824+ if(target.contains("source_query")){
1825+ sourceQuery = (Query*)target["source_query"].value<QObject*>();
1826+ sourceIndex = sourceQuery->getIndex();
1827+ sourceDb = sourceIndex->getDatabase();
1828+ }
1829+
1830+ if(sourceDb == NULL || targetDb == NULL){
1831+ m_errors.append("<b><font color=\"Red\">Error</font></b>: Either the source or target database does not exist or is not active.");
1832+ return;
1833+ }
1834+
1835+ QMap<QString,QVariant> lastSyncInformation;
1836+
1837+ lastSyncInformation.insert("target_replica_uid",getUidFromLocalDb(target_db_name));
1838+ lastSyncInformation.insert("target_replica_generation","");
1839+ lastSyncInformation.insert("target_replica_transaction_id",-1);
1840+ lastSyncInformation.insert("source_replica_uid",getUidFromLocalDb(sourceDb->getPath()));
1841+ lastSyncInformation.insert("source_replica_generation","");
1842+ lastSyncInformation.insert("source_replica_transaction_id",-1);
1843+
1844+ lastSyncInformation = getLastSyncInformation(sourceDb, targetDb, false, lastSyncInformation);
1845+
1846+ QList<QString> transactionsFromSource;
1847+ QList<QString> transactionsFromTarget;
1848+
1849+ // Check if target and source have ever been synced before
1850+
1851+ if(lastSyncInformation["target_replica_uid"].toString() != "" && lastSyncInformation["target_replica_generation"].toString() != "" && lastSyncInformation["target_replica_transaction_id"].toInt() != -1 && lastSyncInformation["source_replica_uid"].toString() != "" && lastSyncInformation["source_replica_generation"].toString() != "" && lastSyncInformation["source_replica_transaction_id"].toInt() != -1)
1852+ {
1853+
1854+ m_errors.append("<b><font color=\"green\">Log</font></b>:"+sourceDb->getPath()+" and "+target_db_name+" have previously been synced. Sync procedure will commence.");
1855+
1856+ //Do some syncing
1857+
1858+ transactionsFromSource = sourceDb->listTransactionsSince(lastSyncInformation["source_replica_generation"].toInt());
1859+
1860+ transactionsFromTarget = targetDb->listTransactionsSince(lastSyncInformation["target_replica_generation"].toInt());
1861+
1862+
1863+ }
1864+ else{
1865+
1866+ m_errors.append("<b><font color=\"Orange\">Warning</font></b>:"+sourceDb->getPath()+" and "+target_db_name+" have no details of ever being synced.");
1867+
1868+ //There is a first time for everything, let's sync!
1869+
1870+ transactionsFromSource = sourceDb->listTransactionsSince(0);
1871+
1872+ transactionsFromTarget = targetDb->listTransactionsSince(0);
1873+ }
1874+
1875+ /*!
1876+ * With two distinct lists present, it is now possible to check what
1877+ * updates should be made, or new documents created in one or the other
1878+ * database, depending on conditions.
1879+ *
1880+ * However, two additional lists containing transactions IDs are required
1881+ * because the information is contained within delimited strings (see
1882+ * below for details).
1883+ *
1884+ */
1885+
1886+ QList<QString> transactionIdsFromSource;
1887+ QList<QString> transactionIdsFromTarget;
1888+
1889+ Q_FOREACH(QString sourceTransaction, transactionsFromSource){
1890+
1891+ /*!
1892+ * Each sourceTransaction is a pipe delimited string containing
1893+ * generation number, document ID, and transaction ID details
1894+ * in that order.
1895+ *
1896+ * Splitting the string into its component pieces provides a
1897+ * document ID (the second key in the list).
1898+ */
1899+
1900+ QStringList transactionDetails = sourceTransaction.split("|");
1901+
1902+ /*!
1903+ * It is only necessary to have unique instances of the
1904+ * document ID.
1905+ */
1906+
1907+ if(!transactionIdsFromSource.contains(transactionDetails[1]))
1908+ transactionIdsFromSource.append(transactionDetails[1]);
1909+
1910+ }
1911+
1912+ Q_FOREACH(QString targetTransaction, transactionsFromTarget){
1913+
1914+ /*!
1915+ * Each targetTransaction is a pipe delimited string containing
1916+ * generation number, document ID, and transaction ID details
1917+ * in that order.
1918+ *
1919+ * Splitting the string into its component pieces provides a
1920+ * document ID (the second key in the list).
1921+ */
1922+
1923+ QStringList transactionDetails = targetTransaction.split("|");
1924+
1925+ /*!
1926+ * It is only necessary to have unique instances of the
1927+ * document ID.
1928+ */
1929+
1930+ if(!transactionIdsFromTarget.contains(transactionDetails[1]))
1931+ transactionIdsFromTarget.append(transactionDetails[1]);
1932+
1933+ }
1934+
1935+ /*!
1936+ * The transactions IDs are searched for in the list of changes
1937+ * from the other database since the last sync (or from the start
1938+ * if this is the first sync) to determine whether to update an
1939+ * existing document, or create a new one, depending on conditions.
1940+ */
1941+
1942+ Q_FOREACH(QString sourceTransaction, transactionIdsFromSource){
1943+
1944+ if(transactionIdsFromTarget.contains(sourceTransaction)){
1945+ if(target["resolve_to_source"].toBool()==true)
1946+
1947+ //Update a Document from Source to Target
1948+
1949+ if(target.contains("id")){
1950+ target_results.append(syncDocument(sourceDb, targetDb, sourceTransaction));
1951+ }
1952+ }
1953+ else{
1954+
1955+ //New Document from Source to Target
1956+
1957+ if(target.contains("id")){
1958+ target_results.append(syncDocument(sourceDb, targetDb, sourceTransaction));
1959+ }
1960+
1961+ }
1962+
1963+ }
1964+
1965+ Q_FOREACH(QString targetTransaction, transactionIdsFromTarget){
1966+
1967+ if(transactionIdsFromSource.contains(targetTransaction)){
1968+ if(target["resolve_to_source"].toBool()==false){
1969+
1970+ //Update a Document from Target to Source
1971+
1972+ if(target.contains("id")){
1973+ source_results.append(syncDocument(targetDb, sourceDb, targetTransaction));
1974+ }
1975+ }
1976+ }
1977+ else{
1978+
1979+ //New Document from Target to Source
1980+
1981+ if(target.contains("id")){
1982+ source_results.append(syncDocument(targetDb, sourceDb, targetTransaction));
1983+ }
1984+ }
1985+ }
1986+
1987+
1988+ if(lastSyncInformation["target_replica_transaction_id"].toInt()==-1&&lastSyncInformation["source_replica_transaction_id"].toInt()==-1){
1989+ //targetDb->updateSyncLog(true, QString uid, QString generation, QString transaction_id);
1990+ //sourceDb->updateSyncLog(true, QString uid, QString generation, QString transaction_id);
1991+ }
1992+ else{
1993+ //targetDb->updateSyncLog(false, QString uid, QString generation, QString transaction_id);
1994+ //sourceDb->updateSyncLog(true, QString uid, QString generation, QString transaction_id);
1995+ }
1996+
1997+
1998+
1999+ /* The source replica asks the target replica for the information it has stored about the last time these two replicas were synchronised (if ever).*/
2000+
2001+
2002+ /*
2003+
2004+ The application wishing to synchronise sends the following GET request to the server:
2005+
2006+ GET /thedb/sync-from/my_replica_uid
2007+
2008+ Where thedb is the name of the database to be synchronised, and my_replica_uid is the replica id of the application’s (i.e. the local, or synchronisation source) database
2009+
2010+ */
2011+
2012+ /*
2013+
2014+ The target responds with a JSON document that looks like this:
2015+
2016+ {
2017+ "target_replica_uid": "other_replica_uid",
2018+ "target_replica_generation": 12,
2019+ "target_replica_transaction_id": "T-sdkfj92292j",
2020+ "source_replica_uid": "my_replica_uid",
2021+ "source_replica_generation": 23,
2022+ "source_transaction_id": "T-39299sdsfla8"
2023+ }
2024+
2025+ With all the information it has stored for the most recent synchronisation between itself and this particular source replica. In this case it tells us that the synchronisation target believes that when it and the source were last synchronised, the target was at generation 12 and the source at generation 23.
2026+
2027+ */
2028+
2029+
2030+ /* The source replica validates that its information regarding the last synchronisation is consistent with the target’s information, and raises an error if not. (This could happen for instance if one of the replicas was lost and restored from backup, or if a user inadvertently tries to synchronise a copied database.) */
2031+
2032+
2033+
2034+ /* The source replica generates a list of changes since the last change the target replica knows of. */
2035+
2036+
2037+
2038+ /* The source replica checks what the last change is it knows about on the target replica. */
2039+
2040+
2041+
2042+ /* If there have been no changes on either replica that the other side has not seen, the synchronisation stops here. */
2043+
2044+
2045+
2046+ /* The source replica sends the changed documents to the target, along with what the latest change is that it knows about on the target replica. */
2047+
2048+
2049+
2050+ /* The target processes the changed documents, and records the source replica’s latest change. */
2051+
2052+
2053+
2054+ /* The target responds with the documents that have changes that the source does not yet know about. */
2055+
2056+ /* The source processes the changed documents, and records the target replica’s latest change. */
2057+
2058+
2059+
2060+ /* If the source has seen no changes unrelated to the synchronisation during this whole process, it now sends the target what its latest change is, so that the next synchronisation does not have to consider changes that were the result of this one.*/
2061+
2062+
2063+}
2064+
2065+QVariant Synchronizer::syncDocument(Database *from, Database *to, QString docId)
2066+{
2067+ QVariant document = from->getDoc(docId);
2068+ to->putDoc(document, docId);
2069+ QString revision = from->getCurrentDocRevisionNumber(docId);
2070+ to->updateDocRevisionNumber(docId,revision);
2071+
2072+ return document;
2073+}
2074+
2075+QMap<QString,QVariant> Synchronizer::getLastSyncInformation(Database *sourceDb, Database *targetDb, bool remote, QMap<QString,QVariant> lastSyncInformation){
2076+
2077+ if(remote == true){
2078+
2079+ m_errors.append("<b><font color=\"red\">Error</font></b>: Remote database sync is under construction. Nothing was synced. Try again later.");
2080+
2081+ return lastSyncInformation;
2082+
2083+ }
2084+ else{
2085+
2086+ lastSyncInformation["source_replica_uid"].toString();
2087+
2088+ lastSyncInformation = targetDb->getSyncLogInfo(lastSyncInformation, lastSyncInformation["source_replica_uid"].toString(),"target");
2089+
2090+ lastSyncInformation = sourceDb->getSyncLogInfo(lastSyncInformation, lastSyncInformation["target_replica_uid"].toString(),"source");
2091+
2092+ }
2093+
2094+ return lastSyncInformation;
2095+
2096+}
2097+
2098+
2099+QString Synchronizer::getUidFromLocalDb(QString dbFileName)
2100+{
2101+
2102+ QString dbUid;
2103+
2104+ QSqlDatabase db;
2105+
2106+ db = QSqlDatabase::addDatabase("QSQLITE",QUuid::createUuid().toString());
2107+
2108+ QFile db_file(dbFileName);
2109+
2110+ if(!db_file.exists())
2111+ {
2112+ m_errors.append("<b><font color\"red\">Error</font></b>: Local database " + dbFileName + " does not exist.");
2113+ return dbUid;
2114+ }
2115+ else
2116+ {
2117+ db.setDatabaseName(dbFileName);
2118+ if (!db.open()){
2119+ m_errors.append(db.lastError().text());
2120+ }
2121+ else{
2122+ QSqlQuery query (db.exec("SELECT value FROM u1db_config WHERE name = 'replica_uid'"));
2123+
2124+ if(!query.lastError().isValid() && query.next()){
2125+ dbUid = query.value(0).toString();
2126+ db.close();
2127+
2128+ dbUid = dbUid.replace("{","");
2129+
2130+ dbUid = dbUid.replace("}","");
2131+
2132+ return dbUid;
2133+ }
2134+ else{
2135+ qDebug() << query.lastError().text();
2136+ db.close();
2137+ return dbUid;
2138+ }
2139+ }
2140+
2141+ }
2142+
2143+ return dbUid;
2144+}
2145+
2146+
2147+
2148+
2149+QT_END_NAMESPACE_U1DB
2150+
2151+#include "moc_synchronizer.cpp"
2152+
2153
2154=== added file 'src/synchronizer.h'
2155--- src/synchronizer.h 1970-01-01 00:00:00 +0000
2156+++ src/synchronizer.h 2013-07-04 14:13:28 +0000
2157@@ -0,0 +1,112 @@
2158+/*
2159+ * Copyright (C) 2013 Canonical, Ltd.
2160+ *
2161+ * Authors:
2162+ * Kevin Wright <kevin.wright@canonical.com>
2163+ *
2164+ * This program is free software; you can redistribute it and/or modify
2165+ * it under the terms of the GNU Lesser General Public License as published by
2166+ * the Free Software Foundation; version 3.
2167+ *
2168+ * This program is distributed in the hope that it will be useful,
2169+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2170+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2171+ * GNU Lesser General Public License for more details.
2172+ *
2173+ * You should have received a copy of the GNU Lesser General Public License
2174+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2175+ */
2176+
2177+#ifndef U1DB_SYNCHRONIZER_H
2178+#define U1DB_SYNCHRONIZER_H
2179+
2180+#include <QtCore/QObject>
2181+#include <QSqlDatabase>
2182+#include <QVariant>
2183+#include <QMetaType>
2184+#include <QtNetwork>
2185+#include <QNetworkAccessManager>
2186+
2187+
2188+#include "database.h"
2189+#include "index.h"
2190+#include "query.h"
2191+
2192+QT_BEGIN_NAMESPACE_U1DB
2193+
2194+class Q_DECL_EXPORT Synchronizer : public QAbstractListModel {
2195+ Q_OBJECT
2196+#ifdef Q_QDOC
2197+ Q_PROPERTY(Database* source READ getSource WRITE setSource NOTIFY sourceChanged)
2198+#else
2199+ Q_PROPERTY(QT_PREPEND_NAMESPACE_U1DB(Database*) source READ getSource WRITE setSource NOTIFY sourceChanged)
2200+#endif
2201+ Q_PROPERTY(bool synchronize READ getSync WRITE setSync NOTIFY syncChanged)
2202+ Q_PROPERTY(bool resolve_to_source READ getResolveToSource WRITE setResolveToSource NOTIFY resolveToSourceChanged)
2203+ Q_PROPERTY(QVariant targets READ getTargets WRITE setTargets NOTIFY targetsChanged)
2204+ Q_PROPERTY(QList<QString> errors READ getErrors WRITE setErrors NOTIFY errorsChanged)
2205+
2206+public:
2207+ Synchronizer(QObject* parent = 0);
2208+
2209+ // QAbstractListModel
2210+ QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
2211+ QHash<int, QByteArray>roleNames() const;
2212+ int rowCount(const QModelIndex & parent = QModelIndex()) const;
2213+
2214+ QList<QVariant> getValidTargets(QMap<QString,QString>validator, QList<QString>mandatory);
2215+ QMap<QString,QVariant> getLastSyncInformation(Database *sourceDb, Database *targetDb, bool remote, QMap<QString,QVariant> lastSyncInformation);
2216+
2217+
2218+ Database* getSource();
2219+ QVariant getTargets();
2220+ bool getSync();
2221+ bool getResolveToSource();
2222+ QList<QString> getErrors();
2223+
2224+ void setSource(Database* source);
2225+ void setTargets(QVariant targets);
2226+ void setSync(bool synchronize);
2227+ void setResolveToSource(bool resolve_to_source);
2228+ void setErrors(QList<QString> errors);
2229+
2230+
2231+ void syncWithLocalTarget(Database *source, Database *target, bool resolve_to_source);
2232+ void syncWithRemoteTarget(Database *source, QString target_url, bool resolve_to_source);
2233+ void syncLocalToLocal(Database *sourceDb, QMap<QString,QVariant> target);
2234+ void synchronizeTargets(Database *source, QVariant targets);
2235+
2236+ QVariant syncDocument(Database *from, Database *to, QString docId);
2237+ QString getUidFromLocalDb(QString dbFileName);
2238+
2239+
2240+
2241+
2242+Q_SIGNALS:
2243+ void sourceChanged(Database* source);
2244+ void targetsChanged(QVariant targets);
2245+ void syncChanged(bool synchronize);
2246+ void resolveToSourceChanged(bool resolve_to_source);
2247+ void errorsChanged(QList<QString> errors);
2248+ void syncCompleted();
2249+private:
2250+ //Q_DISABLE_COPY(Synchronizer)
2251+ Database* m_source;
2252+ bool m_synchronize;
2253+ bool m_resolve_to_source;
2254+ QVariant m_targets;
2255+ QList<QString> m_errors;
2256+
2257+ void onSyncChanged(bool synchronize);
2258+ void remoteGetSyncInfoFinished(QNetworkReply* reply);
2259+ void remotePostSyncInfoFinished(QNetworkReply* reply);
2260+
2261+};
2262+
2263+QT_END_NAMESPACE_U1DB
2264+
2265+
2266+
2267+#endif // U1DB_SYNCHRONIZER_H
2268+
2269+

Subscribers

People subscribed via source and target branches

to all changes: