Merge lp:~kalikiana/u1db-qt/syncWithU1 into lp:u1db-qt
- syncWithU1
- Merge into trunk
Status: | Work in progress | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Proposed branch: | lp:~kalikiana/u1db-qt/syncWithU1 | ||||||||||||
Merge into: | lp:u1db-qt | ||||||||||||
Diff against target: |
1664 lines (+934/-295) 13 files modified
debian/control (+2/-0) examples/notes-cloud/notes-cloud.qml (+267/-148) modules/U1db/plugin.cpp (+2/-0) src/CMakeLists.txt (+5/-1) src/database.cpp (+10/-0) src/database.h (+1/-1) src/request.cpp (+87/-0) src/request.h (+77/-0) src/server.cpp (+94/-0) src/server.h (+67/-0) src/synchronizer.cpp (+204/-137) src/synchronizer.h (+21/-8) tests/tst_sync.qml (+97/-0) |
||||||||||||
To merge this branch: | bzr merge lp:~kalikiana/u1db-qt/syncWithU1 | ||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
U1DB Qt developers | Pending | ||
Review via email: mp+202508@code.launchpad.net |
Commit message
Make sync with U1 and authentication possible
Description of the change
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:118
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 119. By Cris Dywan
-
Merge lp:u1db-qt
- 120. By Cris Dywan
-
Fix synchronize property/ signal/ methods
- 121. By Cris Dywan
-
sync_output should be called syncOutput
- 122. By Cris Dywan
-
Reset synchronize after success/ error or if nothing happened
- 123. By Cris Dywan
-
Add message to syncOutput if no targets were specified
- 124. By Cris Dywan
-
Reset synchronize if URI is invalid
- 125. By Cris Dywan
-
Add unit tests for Synchronizer target syntax
- 126. By Cris Dywan
-
Introduce Server based on Request for sync unit tests
- 127. By Cris Dywan
-
Implement 5 minute timeout in Request
Unmerged revisions
- 127. By Cris Dywan
-
Implement 5 minute timeout in Request
- 126. By Cris Dywan
-
Introduce Server based on Request for sync unit tests
- 125. By Cris Dywan
-
Add unit tests for Synchronizer target syntax
- 124. By Cris Dywan
-
Reset synchronize if URI is invalid
- 123. By Cris Dywan
-
Add message to syncOutput if no targets were specified
- 122. By Cris Dywan
-
Reset synchronize after success/ error or if nothing happened
- 121. By Cris Dywan
-
sync_output should be called syncOutput
- 120. By Cris Dywan
-
Fix synchronize property/ signal/ methods
- 119. By Cris Dywan
-
Merge lp:u1db-qt
- 118. By Cris Dywan
-
Implement networkRequest signal to handle credentials correctly
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2014-01-22 13:14:22 +0000 |
3 | +++ debian/control 2014-02-20 14:57:43 +0000 |
4 | @@ -65,6 +65,8 @@ |
5 | Section: doc |
6 | Architecture: all |
7 | Depends: libu1db-qt5-3 (>= ${source:Version}), |
8 | + qtdeclarative5-ubuntuone1.0, |
9 | + qtdeclarative5-ubuntu-ui-toolkit-plugin, |
10 | ${misc:Depends}, |
11 | Description: Qt5 binding and QtQuick2 plugin for U1DB - examples |
12 | Simple Qt5 binding and QtQuick2 plugin for U1DB (https://launchpad.net/u1db). |
13 | |
14 | === renamed file 'examples/u1db-qt-example-6/u1db-qt-example-6.qdoc' => 'documentation/u1db-qt-example-6.qdoc' |
15 | === renamed directory 'examples/u1db-qt-example-6' => 'examples/notes-cloud' |
16 | === renamed file 'examples/u1db-qt-example-6/u1db-qt-example-6.qml' => 'examples/notes-cloud/notes-cloud.qml' |
17 | --- examples/u1db-qt-example-6/u1db-qt-example-6.qml 2013-08-09 10:42:18 +0000 |
18 | +++ examples/notes-cloud/notes-cloud.qml 2014-02-20 14:57:43 +0000 |
19 | @@ -1,8 +1,8 @@ |
20 | /* |
21 | - * Copyright (C) 2013 Canonical, Ltd. |
22 | + * Copyright (C) 2014 Canonical, Ltd. |
23 | * |
24 | * Authors: |
25 | - * Kevin Wright <kevin.wright@canonical.com> |
26 | + * Christian Dywan <christian.dywan@canonical.com> |
27 | * |
28 | * This program is free software; you can redistribute it and/or modify |
29 | * it under the terms of the GNU Lesser General Public License as published by |
30 | @@ -20,154 +20,273 @@ |
31 | import QtQuick 2.0 |
32 | import U1db 1.0 as U1db |
33 | import Ubuntu.Components 0.1 |
34 | - |
35 | - |
36 | -Item { |
37 | - |
38 | - width: units.gu(45) |
39 | - height: units.gu(80) |
40 | - |
41 | - U1db.Database { |
42 | - id: aDatabase |
43 | - path: "aDatabase6" |
44 | - } |
45 | - |
46 | - U1db.Document { |
47 | - id: aDocument1 |
48 | - database: aDatabase |
49 | - docId: 'helloworld' |
50 | - create: true |
51 | - contents:{"hello": { "world": [ { "message": "Hello World" } ] } } |
52 | - |
53 | - } |
54 | - |
55 | - U1db.Index{ |
56 | - database: aDatabase |
57 | - id: by_helloworld |
58 | - expression: ["hello.world.message"] |
59 | - } |
60 | - |
61 | - U1db.Query{ |
62 | - id: aQuery |
63 | - index: by_helloworld |
64 | - query: [{"message":"Hel*"}] |
65 | - } |
66 | - |
67 | - U1db.Synchronizer{ |
68 | - id: aSynchronizer |
69 | - source: aDatabase |
70 | - targets: [{remote:true}, |
71 | - {remote:true, |
72 | - ip:"127.0.0.1", |
73 | - port: 7777, |
74 | - name:"example1.u1db", |
75 | - resolve_to_source:true}, |
76 | - {remote:"OK"}] |
77 | - synchronize: false |
78 | - } |
79 | - |
80 | - MainView { |
81 | - |
82 | - id: u1dbView |
83 | - width: units.gu(45) |
84 | - height: units.gu(80) |
85 | - anchors.top: parent.top; |
86 | - |
87 | - Tabs { |
88 | - id: tabs |
89 | +import UbuntuOne 1.0 |
90 | +import Ubuntu.Components.Popups 0.1 |
91 | + |
92 | +MainView { |
93 | + applicationName: "com.ubuntu.developer.foobar.notes-cloud" |
94 | + |
95 | + width: units.gu(50) |
96 | + height: units.gu(75) |
97 | + |
98 | + /* |
99 | + Notes database for both local and remote data |
100 | + */ |
101 | + U1db.Database { |
102 | + id: db |
103 | + path: inMemory.checked ? ":memory:" : filename |
104 | + property string filename: "notes.db" |
105 | + } |
106 | + U1db.Document { |
107 | + id: doc |
108 | + database: db |
109 | + docId: 'text' |
110 | + create: true |
111 | + defaults: { "notes": "Lorem ipsum" } |
112 | + } |
113 | + |
114 | + /* |
115 | + Use this to reset switches and indicator to avoid "broken" appearance |
116 | + when error would be too fast to see anything happen at all |
117 | + */ |
118 | + Timer { |
119 | + id: smoothReset |
120 | + interval: 900 |
121 | + |
122 | + onRunningChanged: { |
123 | + testSyncActive.enabled = !liveSyncActive.checked |
124 | + liveSyncActive.enabled = !testSyncActive.checked |
125 | + syncIndicator.running = testSync.synchronize || liveSync.synchronize || running |
126 | + } |
127 | + |
128 | + onTriggered: { |
129 | + running = false |
130 | + testSyncActive.checked = testSync.synchronize |
131 | + liveSyncActive.checked = liveSync.synchronize |
132 | + } |
133 | + } |
134 | + |
135 | + function fillLabel(server, syncOutput) { |
136 | + var prettyOutput = '%1'.arg(server) |
137 | + for (var i in syncOutput) |
138 | + prettyOutput += '\n%1'.arg(syncOutput[i].message_value) |
139 | + syncLabel.text = prettyOutput |
140 | + } |
141 | + |
142 | + /* |
143 | + U1 Sync |
144 | + */ |
145 | + U1db.Synchronizer { |
146 | + id: testSync |
147 | + source: db |
148 | + targets: [ { |
149 | + uri: "http://127.0.0.1:7777/" + db.filename, |
150 | + resolve_to_source: true, |
151 | + // Legacy syntax |
152 | + ip: "127.0.0.1", |
153 | + port: 7777, |
154 | + name: db.filename, |
155 | + remote: true, |
156 | + }, ] |
157 | + synchronize: false |
158 | + onSynchronizeChanged: smoothReset.start() |
159 | + onSyncOutputChanged: fillLabel(targets[0].uri, syncOutput) |
160 | + } |
161 | + |
162 | + U1db.Server { |
163 | + port: 7777 |
164 | + onNetworkRequest: writeContents('HTTP/1.0 404 Not Found\r\nContent-Type: text/html\r\nContent-Length: 8\r\nNot found\r\n') |
165 | + } |
166 | + |
167 | + |
168 | + U1db.Synchronizer { |
169 | + id: liveSync |
170 | + source: db |
171 | + targets: [ { |
172 | + uri: "https://u1db.one.ubuntu.com/~/notes-cloud-" + db.path, |
173 | + resolve_to_source: true, |
174 | + }, ] |
175 | + synchronize: false |
176 | + onNetworkRequest: { |
177 | + request.handled = true |
178 | + u1credservice.onUrlSigned.connect(onUrlSigned.bind(request, request)) |
179 | + u1credservice.onUrlSigningError.connect(onUrlSigningError.bind(request, request)) |
180 | + u1credservice.signUrl(request.uri, request.method) |
181 | + } |
182 | + function onUrlSigned(request, signedUrl) { |
183 | + u1credservice.onUrlSigned.disconnect(onUrlSigned.bind(request, request)) |
184 | + u1credservice.onUrlSigningError.disconnect(onUrlSigningError.bind(request, request)) |
185 | + request.authorization = signedUrl |
186 | + request.send() |
187 | + } |
188 | + function onUrlSigningError(request, errorMessage) { |
189 | + u1credservice.onUrlSigned.disconnect(onUrlSigned.bind(request, request)) |
190 | + u1credservice.onUrlSigningError.disconnect(onUrlSigningError.bind(request, request)) |
191 | + request.cancel() |
192 | + } |
193 | + onSynchronizeChanged: smoothReset.start() |
194 | + onSyncOutputChanged: fillLabel(targets[0].uri, syncOutput) |
195 | + } |
196 | + |
197 | + UbuntuOneCredentialsService { |
198 | + id: u1credservice |
199 | + onCredentialsFound: { |
200 | + statusLabel.text = i18n.tr('Credentials found') |
201 | + } |
202 | + onCredentialsNotFound: { |
203 | + statusLabel.text = i18n.tr('No account credentials found') |
204 | + } |
205 | + onCredentialsDeleted: { |
206 | + statusLabel.text = i18n.tr('Clear credentials successfully') |
207 | + } |
208 | + onUrlSigned: { |
209 | + statusLabel.text = i18n.tr('Signed URL: %1').arg(signedUrl) |
210 | + } |
211 | + onUrlSigningError: { |
212 | + statusLabel.text = i18n.tr('Error signing: %1').arg(errorMessage) |
213 | + } |
214 | + |
215 | + onLoginOrRegisterSuccess: { |
216 | + statusLabel.text = i18n.tr('Login successful') |
217 | + } |
218 | + onLoginOrRegisterError: { |
219 | + statusLabel.text = i18n.tr('Login failed') |
220 | + } |
221 | + onTwoFactorAuthRequired: { |
222 | + statusLabel.text = i18n.tr('Two Factor Auth required') |
223 | + } |
224 | + } |
225 | + |
226 | + /* |
227 | + UI: Login credentials |
228 | + */ |
229 | + |
230 | + Component { |
231 | + id: loginPopCom |
232 | + Popover { |
233 | + id: loginPop |
234 | + Column { |
235 | + anchors.centerIn: parent |
236 | + spacing: units.gu(1) |
237 | + Label { |
238 | + text: i18n.tr('Please fill in your credentails') |
239 | + } |
240 | + TextField { |
241 | + id: email |
242 | + placeholderText: i18n.tr('email') |
243 | + } |
244 | + TextField { |
245 | + id: pass |
246 | + placeholderText: i18n.tr('password') |
247 | + } |
248 | + TextField { |
249 | + id: two |
250 | + inputMethodHints: Qt.ImhHiddenText |
251 | + placeholderText: i18n.tr('two-factor code') |
252 | + } |
253 | + Button { |
254 | + text: i18n.tr('Confirm') |
255 | + onClicked: u1credservice.login(email.text, pass.text, two.text) |
256 | + } |
257 | + } |
258 | + } |
259 | + } |
260 | + |
261 | + /* |
262 | + UI: Creds/ sync results, notes input |
263 | + */ |
264 | + Page { |
265 | + |
266 | + title: i18n.tr("Notes in the Cloud") |
267 | + |
268 | + Item { |
269 | + anchors.margins: units.gu(1) |
270 | anchors.fill: parent |
271 | |
272 | - Tab { |
273 | - objectName: "Tab1" |
274 | - |
275 | - title: i18n.tr("Hello U1Db!") |
276 | - |
277 | - page: Page { |
278 | - id: helloPage |
279 | - |
280 | - Rectangle { |
281 | - width: units.gu(45) |
282 | - height: units.gu(40) |
283 | - anchors.top: parent.top; |
284 | - border.width: 1 |
285 | - |
286 | - Text { |
287 | - id: sourceLabel |
288 | - anchors.top: parent.top; |
289 | - font.bold: true |
290 | - text: "aDatabase6 Contents" |
291 | - } |
292 | - |
293 | - ListView { |
294 | - id: sourceListView |
295 | - width: units.gu(45) |
296 | - height: units.gu(35) |
297 | - anchors.top: sourceLabel.bottom; |
298 | - model: aQuery |
299 | - |
300 | - delegate: Text { |
301 | - wrapMode: Text.WordWrap |
302 | - x: 6; y: 77 |
303 | - text: { |
304 | - text: "(" + index + ") '" + contents.message + "'" |
305 | - } |
306 | - } |
307 | - } |
308 | - |
309 | - |
310 | - } |
311 | - |
312 | - Rectangle { |
313 | - id: lowerRectangle |
314 | - width: units.gu(45) |
315 | - height: units.gu(35) |
316 | - anchors.bottom: parent.bottom; |
317 | - border.width: 1 |
318 | - |
319 | - Text { |
320 | - id: errorsLabel |
321 | - anchors.top: parent.top; |
322 | - font.bold: true |
323 | - text: "Log:" |
324 | - } |
325 | - |
326 | - ListView { |
327 | - |
328 | - parent: lowerRectangle |
329 | - width: units.gu(45) |
330 | - height: units.gu(30) |
331 | - anchors.top: errorsLabel.bottom; |
332 | - model: aSynchronizer |
333 | - delegate:Text { |
334 | - width: units.gu(40) |
335 | - anchors.left: parent.left |
336 | - anchors.right: parent.right |
337 | - wrapMode: Text.WordWrap |
338 | - text: { |
339 | - text: sync_output |
340 | - } |
341 | - } |
342 | - } |
343 | - |
344 | - Button{ |
345 | - parent: lowerRectangle |
346 | - anchors.bottom: parent.bottom; |
347 | - text: "Sync" |
348 | - onClicked: aSynchronizer.synchronize = true |
349 | - anchors.left: parent.left |
350 | - anchors.right: parent.right |
351 | - |
352 | - } |
353 | - |
354 | - } |
355 | - } |
356 | - |
357 | - } |
358 | - |
359 | + Column { |
360 | + id: col |
361 | + spacing: units.gu(1) |
362 | + anchors.top: parent.top |
363 | + anchors.left: parent.left |
364 | + anchors.right: parent.right |
365 | + |
366 | + TextArea { |
367 | + id: statusLabel |
368 | + text: i18n.tr('N/A') |
369 | + readOnly: true |
370 | + autoSize: true |
371 | + anchors.left: parent.left |
372 | + anchors.right: parent.right |
373 | + } |
374 | + |
375 | + Row { |
376 | + spacing: units.gu(1) |
377 | + Button { |
378 | + text: i18n.tr('Verify creds') |
379 | + onClicked: u1credservice.checkCredentials() |
380 | + } |
381 | + Button { |
382 | + text: i18n.tr('Clear creds') |
383 | + onClicked: { |
384 | + statusLabel.text = i18n.tr('N/A') |
385 | + u1credservice.invalidateCredentials() |
386 | + } |
387 | + } |
388 | + Button { |
389 | + id: loginButton |
390 | + text: i18n.tr('Login') |
391 | + onClicked: PopupUtils.open(loginPopCom, loginButton) |
392 | + } |
393 | + ActivityIndicator { |
394 | + id: syncIndicator |
395 | + } |
396 | + } |
397 | + |
398 | + TextArea { |
399 | + id: syncLabel |
400 | + text: i18n.tr('No sync target enabled') |
401 | + readOnly: true |
402 | + autoSize: true |
403 | + anchors.left: parent.left |
404 | + anchors.right: parent.right |
405 | + } |
406 | + |
407 | + Row { |
408 | + spacing: units.gu(1) |
409 | + Label { |
410 | + text: i18n.tr('Test') |
411 | + } |
412 | + |
413 | + Switch { |
414 | + id: testSyncActive |
415 | + onClicked: testSync.synchronize = true |
416 | + } |
417 | + Label { |
418 | + text: i18n.tr('U1') |
419 | + } |
420 | + Switch { |
421 | + id: liveSyncActive |
422 | + onClicked: liveSync.synchronize = true |
423 | + } |
424 | + Label { |
425 | + text: i18n.tr('Memory') |
426 | + } |
427 | + Switch { |
428 | + id: inMemory |
429 | + } |
430 | + } |
431 | + |
432 | + } |
433 | + |
434 | + TextArea { |
435 | + text: doc.contents.notes |
436 | + onFocusChanged: doc.contents = { "notes": text } |
437 | + anchors.margins: units.gu(1) |
438 | + anchors.top: col.bottom |
439 | + anchors.bottom: parent.bottom |
440 | + anchors.left: parent.left |
441 | + anchors.right: parent.right |
442 | + } |
443 | } |
444 | - |
445 | } |
446 | - |
447 | } |
448 | - |
449 | - |
450 | |
451 | === modified file 'modules/U1db/plugin.cpp' |
452 | --- modules/U1db/plugin.cpp 2013-05-02 19:52:10 +0000 |
453 | +++ modules/U1db/plugin.cpp 2014-02-20 14:57:43 +0000 |
454 | @@ -22,6 +22,7 @@ |
455 | #include "index.h" |
456 | #include "query.h" |
457 | #include "synchronizer.h" |
458 | +#include "server.h" |
459 | #include "plugin.h" |
460 | #include <qqml.h> |
461 | |
462 | @@ -34,5 +35,6 @@ |
463 | qmlRegisterType<Index>(uri, 1, 0, "Index"); |
464 | qmlRegisterType<Query>(uri, 1, 0, "Query"); |
465 | qmlRegisterType<Synchronizer>(uri, 1, 0, "Synchronizer"); |
466 | + qmlRegisterType<Server>(uri, 1, 0, "Server"); |
467 | } |
468 | |
469 | |
470 | === modified file 'src/CMakeLists.txt' |
471 | --- src/CMakeLists.txt 2013-08-08 10:11:39 +0000 |
472 | +++ src/CMakeLists.txt 2014-02-20 14:57:43 +0000 |
473 | @@ -7,6 +7,8 @@ |
474 | index.cpp |
475 | query.cpp |
476 | synchronizer.cpp |
477 | + request.cpp |
478 | + server.cpp |
479 | ) |
480 | |
481 | # Generated files |
482 | @@ -16,6 +18,8 @@ |
483 | moc_index.cpp |
484 | moc_query.cpp |
485 | moc_synchronizer.cpp |
486 | + moc_request.cpp |
487 | + moc_server.cpp |
488 | ) |
489 | set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${U1DB_QT_GENERATED}") |
490 | |
491 | @@ -54,7 +58,7 @@ |
492 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} |
493 | ) |
494 | |
495 | -install(FILES global.h database.h document.h index.h query.h synchronizer.h |
496 | +install(FILES global.h database.h document.h index.h query.h synchronizer.h request.h |
497 | DESTINATION ${INCLUDE_INSTALL_DIR} |
498 | ) |
499 | |
500 | |
501 | === modified file 'src/database.cpp' |
502 | --- src/database.cpp 2014-02-17 16:52:42 +0000 |
503 | +++ src/database.cpp 2014-02-20 14:57:43 +0000 |
504 | @@ -156,6 +156,16 @@ |
505 | return setError(QString("Failed to read internal schema: FileError %1").arg(file.error())); |
506 | } |
507 | } |
508 | + |
509 | + // index_storage is read by the python sqlite implementation |
510 | + // it wasn't set in in earlier u1db-qt versions so always check it |
511 | + QSqlQuery query(m_db.exec( |
512 | + "SELECT value FROM u1db_config WHERE name = 'index_storage'")); |
513 | + if (!query.next()) |
514 | + query.prepare("INSERT INTO u1db_config VALUES ('index_storage', 'expand referenced')"); |
515 | + if (!query.exec()) |
516 | + return setError(QString("Failed to set index storage: %1\n%2").arg(m_db.lastError().text()).arg(query.lastQuery())); |
517 | + |
518 | return true; |
519 | } |
520 | |
521 | |
522 | === modified file 'src/database.h' |
523 | --- src/database.h 2014-01-24 11:13:35 +0000 |
524 | +++ src/database.h 2014-02-20 14:57:43 +0000 |
525 | @@ -63,6 +63,7 @@ |
526 | void updateSyncLog(bool insert, QString uid, QString generation, QString transaction_id); |
527 | QList<QString> listTransactionsSince(int generation); |
528 | QMap<QString,QVariant> getSyncLogInfo(QMap<QString,QVariant> lastSyncInformation, QString uid, QString prefix); |
529 | + QString getReplicaUid(); |
530 | |
531 | Q_SIGNALS: |
532 | void pathChanged(const QString& path); |
533 | @@ -81,7 +82,6 @@ |
534 | QSqlDatabase m_db; |
535 | QString m_error; |
536 | |
537 | - QString getReplicaUid(); |
538 | bool isInitialized(); |
539 | bool initializeIfNeeded(const QString& path=":memory:"); |
540 | bool setError(const QString& error); |
541 | |
542 | === added file 'src/request.cpp' |
543 | --- src/request.cpp 1970-01-01 00:00:00 +0000 |
544 | +++ src/request.cpp 2014-02-20 14:57:43 +0000 |
545 | @@ -0,0 +1,87 @@ |
546 | +/* |
547 | + * Copyright (C) 2014 Canonical, Ltd. |
548 | + * |
549 | + * Authors: |
550 | + * Christian Dywan <christian.dywan@canonical.com> |
551 | + * |
552 | + * This program is free software; you can redistribute it and/or modify |
553 | + * it under the terms of the GNU Lesser General Public License as published by |
554 | + * the Free Software Foundation; version 3. |
555 | + * |
556 | + * This program is distributed in the hope that it will be useful, |
557 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
558 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
559 | + * GNU Lesser General Public License for more details. |
560 | + * |
561 | + * You should have received a copy of the GNU Lesser General Public License |
562 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
563 | + */ |
564 | + |
565 | +#include <QDebug> |
566 | + |
567 | +#include "request.h" |
568 | +#include "private.h" |
569 | + |
570 | +QT_BEGIN_NAMESPACE_U1DB |
571 | + |
572 | +Request::Request(const QString& uri, const QString& method, QObject *parent) : |
573 | + QObject(parent), m_uri(uri), m_method(method), m_handled(false) |
574 | +{ |
575 | + m_request = QNetworkRequest (QUrl (uri)); |
576 | + |
577 | + /* QNetworkManager doesn't time out in all cases */ |
578 | + m_timer.start(5 * 60000); // 5 * 60 seconds |
579 | + connect(&m_timer, &QTimer::timeout, this, &Request::timeout); |
580 | +} |
581 | + |
582 | +void Request::timeout() |
583 | +{ |
584 | + disconnect(&m_timer, &QTimer::timeout, this, &Request::timeout); |
585 | + cancel("Timeout after 5 minutes"); |
586 | +} |
587 | + |
588 | +QString Request::getUri() |
589 | +{ |
590 | + return m_uri; |
591 | +} |
592 | + |
593 | +void Request::setUri(const QString& uri) |
594 | +{ |
595 | + m_uri = uri; |
596 | + m_request.setUrl(uri); |
597 | +} |
598 | + |
599 | +QString Request::getMethod() |
600 | +{ |
601 | + return m_method; |
602 | +} |
603 | + |
604 | +void Request::setMethod(const QString& method) |
605 | +{ |
606 | + m_method = method; |
607 | +} |
608 | + |
609 | +bool Request::getHandled() |
610 | +{ |
611 | + return m_handled; |
612 | +} |
613 | + |
614 | +void Request::setHandled(bool handled) |
615 | +{ |
616 | + m_handled = handled; |
617 | +} |
618 | + |
619 | +QString Request::getAuthorization() |
620 | +{ |
621 | + return m_authorization; |
622 | +} |
623 | + |
624 | +void Request::setAuthorization(const QString& authorization) |
625 | +{ |
626 | + m_authorization = authorization; |
627 | +} |
628 | + |
629 | +QT_END_NAMESPACE_U1DB |
630 | + |
631 | +#include "moc_request.cpp" |
632 | + |
633 | |
634 | === added file 'src/request.h' |
635 | --- src/request.h 1970-01-01 00:00:00 +0000 |
636 | +++ src/request.h 2014-02-20 14:57:43 +0000 |
637 | @@ -0,0 +1,77 @@ |
638 | +/* |
639 | + * Copyright (C) 2014 Canonical, Ltd. |
640 | + * |
641 | + * Authors: |
642 | + * Christian Dywan <christian.dywan@canonical.com> |
643 | + * |
644 | + * This program is free software; you can redistribute it and/or modify |
645 | + * it under the terms of the GNU Lesser General Public License as published by |
646 | + * the Free Software Foundation; version 3. |
647 | + * |
648 | + * This program is distributed in the hope that it will be useful, |
649 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
650 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
651 | + * GNU Lesser General Public License for more details. |
652 | + * |
653 | + * You should have received a copy of the GNU Lesser General Public License |
654 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
655 | + */ |
656 | + |
657 | +#ifndef U1DB_REQUEST_H |
658 | +#define U1DB_REQUEST_H |
659 | + |
660 | +#include <QtCore/QObject> |
661 | +#include <QVariant> |
662 | +#include <QMetaType> |
663 | +#include <QtNetwork> |
664 | +#include <QNetworkAccessManager> |
665 | + |
666 | + |
667 | +#include "global.h" |
668 | + |
669 | +QT_BEGIN_NAMESPACE_U1DB |
670 | + |
671 | +class Q_DECL_EXPORT Request : public QObject { |
672 | + Q_OBJECT |
673 | + Q_PROPERTY(QString uri READ getUri WRITE setUri) |
674 | + Q_PROPERTY(QString method READ getMethod WRITE setMethod) |
675 | + Q_PROPERTY(bool handled READ getHandled WRITE setHandled) |
676 | + Q_PROPERTY(QString authorization READ getAuthorization WRITE setAuthorization) |
677 | + |
678 | +public: |
679 | + Request(const QString& uri, const QString& method, QObject* parent = 0); |
680 | + |
681 | + QString getUri(); |
682 | + void setUri(const QString& uri); |
683 | + QString getMethod(); |
684 | + void setMethod(const QString& method); |
685 | + bool getHandled(); |
686 | + void setHandled(bool handled); |
687 | + QString getAuthorization(); |
688 | + void setAuthorization(const QString& authorization); |
689 | + |
690 | + // FIXME private |
691 | + QNetworkRequest m_request; |
692 | + |
693 | +Q_SIGNALS: |
694 | + |
695 | + void send(); |
696 | + void cancel(const QString& reason=QString()); |
697 | + |
698 | +private: |
699 | + //Q_DISABLE_COPY(Request) |
700 | + QString m_uri; |
701 | + QString m_method; |
702 | + bool m_handled; |
703 | + QString m_authorization; |
704 | + QTimer m_timer; |
705 | + |
706 | + void timeout(); |
707 | +}; |
708 | + |
709 | +QT_END_NAMESPACE_U1DB |
710 | + |
711 | + |
712 | +#endif // U1DB_REQUEST_H |
713 | + |
714 | + |
715 | |
716 | === added file 'src/server.cpp' |
717 | --- src/server.cpp 1970-01-01 00:00:00 +0000 |
718 | +++ src/server.cpp 2014-02-20 14:57:43 +0000 |
719 | @@ -0,0 +1,94 @@ |
720 | +/* |
721 | + * Copyright (C) 2014 Canonical, Ltd. |
722 | + * |
723 | + * Authors: |
724 | + * Christian Dywan <christian.dywan@canonical.com> |
725 | + * |
726 | + * This program is free software; you can redistribute it and/or modify |
727 | + * it under the terms of the GNU Lesser General Public License as published by |
728 | + * the Free Software Foundation; version 3. |
729 | + * |
730 | + * This program is distributed in the hope that it will be useful, |
731 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
732 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
733 | + * GNU Lesser General Public License for more details. |
734 | + * |
735 | + * You should have received a copy of the GNU Lesser General Public License |
736 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
737 | + */ |
738 | + |
739 | +#include <QDebug> |
740 | + |
741 | +#include "server.h" |
742 | +#include "private.h" |
743 | + |
744 | +QT_BEGIN_NAMESPACE_U1DB |
745 | + |
746 | +Server::Server(QTcpServer *parent) : |
747 | + QTcpServer(parent), m_port(-1) |
748 | +{ |
749 | + connect(this, &QTcpServer::acceptError, this, &Server::acceptError); |
750 | +} |
751 | + |
752 | +void Server::acceptError(QAbstractSocket::SocketError socketError) |
753 | +{ |
754 | + qDebug() << socketError << m_socket->errorString(); |
755 | +} |
756 | + |
757 | +int Server::getPort() |
758 | +{ |
759 | + return m_port; |
760 | +} |
761 | + |
762 | +void Server::setPort(int port) |
763 | +{ |
764 | + if (port == m_port) |
765 | + return; |
766 | + |
767 | + if (isListening()) |
768 | + close (); |
769 | + |
770 | + // port 0 means random, that's why we take the value after the call |
771 | + listen(QHostAddress::Any, port); |
772 | + m_port = serverPort(); |
773 | +} |
774 | + |
775 | +void Server::incomingConnection(qintptr socket) |
776 | +{ |
777 | + m_socket = new QTcpSocket(this); |
778 | + connect(m_socket, &QIODevice::readyRead, this, &Server::readClient); |
779 | + connect(m_socket, &QAbstractSocket::disconnected, this, &Server::discardClient); |
780 | +} |
781 | + |
782 | +void Server::readClient() |
783 | +{ |
784 | + if (!m_socket->canReadLine()) |
785 | + return; |
786 | + QString line(m_socket->readLine()); |
787 | + // GET /foo/bar HTTP/1.1 |
788 | + QStringList tokens(line.split(" ")); |
789 | + QString method(tokens.at(0)); |
790 | + QString uri(tokens.at(1)); |
791 | + Request* request = new Request(uri, method); |
792 | + Q_EMIT networkRequest(request); |
793 | +} |
794 | + |
795 | +void Server::discardClient() |
796 | +{ |
797 | + m_socket->deleteLater(); |
798 | +} |
799 | + |
800 | +void Server::writeContents (const QString& output) |
801 | +{ |
802 | + QTextStream os(m_socket); |
803 | + os.setAutoDetectUnicode(true); |
804 | + os << output; |
805 | + m_socket->close(); |
806 | + if (m_socket->state() == QTcpSocket::UnconnectedState) |
807 | + delete m_socket; |
808 | +} |
809 | + |
810 | +QT_END_NAMESPACE_U1DB |
811 | + |
812 | +#include "moc_server.cpp" |
813 | + |
814 | |
815 | === added file 'src/server.h' |
816 | --- src/server.h 1970-01-01 00:00:00 +0000 |
817 | +++ src/server.h 2014-02-20 14:57:43 +0000 |
818 | @@ -0,0 +1,67 @@ |
819 | +/* |
820 | + * Copyright (C) 2014 Canonical, Ltd. |
821 | + * |
822 | + * Authors: |
823 | + * Christian Dywan <christian.dywan@canonical.com> |
824 | + * |
825 | + * This program is free software; you can redistribute it and/or modify |
826 | + * it under the terms of the GNU Lesser General Public License as published by |
827 | + * the Free Software Foundation; version 3. |
828 | + * |
829 | + * This program is distributed in the hope that it will be useful, |
830 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
831 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
832 | + * GNU Lesser General Public License for more details. |
833 | + * |
834 | + * You should have received a copy of the GNU Lesser General Public License |
835 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
836 | + */ |
837 | + |
838 | +#ifndef U1DB_SERVER_H |
839 | +#define U1DB_SERVER_H |
840 | + |
841 | +#include <QtCore/QObject> |
842 | +#include <QVariant> |
843 | +#include <QMetaType> |
844 | +#include <QtNetwork> |
845 | +#include <QNetworkAccessManager> |
846 | +#include <QTcpServer> |
847 | + |
848 | + |
849 | +#include "global.h" |
850 | +#include "request.h" |
851 | + |
852 | +QT_BEGIN_NAMESPACE_U1DB |
853 | + |
854 | +class Q_DECL_EXPORT Server : public QTcpServer { |
855 | + Q_OBJECT |
856 | + Q_PROPERTY(int port READ getPort WRITE setPort) |
857 | + |
858 | +public: |
859 | + Server(QTcpServer* parent = 0); |
860 | + |
861 | + int getPort(); |
862 | + void setPort(int port); |
863 | + |
864 | + Q_INVOKABLE void writeContents(const QString& output); |
865 | + |
866 | +Q_SIGNALS: |
867 | + void networkRequest(Request* request); |
868 | + |
869 | +private: |
870 | + //Q_DISABLE_COPY(Server) |
871 | + int m_port; |
872 | + QTcpSocket* m_socket; |
873 | + |
874 | + void acceptError(QAbstractSocket::SocketError socketError); |
875 | + void incomingConnection(qintptr socket); |
876 | + void readClient(); |
877 | + void discardClient(); |
878 | +}; |
879 | + |
880 | +QT_END_NAMESPACE_U1DB |
881 | + |
882 | + |
883 | +#endif // U1DB_SERVER_H |
884 | + |
885 | + |
886 | |
887 | === modified file 'src/synchronizer.cpp' |
888 | --- src/synchronizer.cpp 2013-11-25 13:22:07 +0000 |
889 | +++ src/synchronizer.cpp 2014-02-20 14:57:43 +0000 |
890 | @@ -1,8 +1,9 @@ |
891 | /* |
892 | - * Copyright (C) 2013 Canonical, Ltd. |
893 | + * Copyright (C) 2013-2014 Canonical, Ltd. |
894 | * |
895 | * Authors: |
896 | * Kevin Wright <kevin.wright@canonical.com> |
897 | + * Christian Dywan <christian.dywan@canonical.com> |
898 | * |
899 | * This program is free software; you can redistribute it and/or modify |
900 | * it under the terms of the GNU Lesser General Public License as published by |
901 | @@ -111,7 +112,8 @@ |
902 | Synchronizer::Synchronizer(QObject *parent) : |
903 | QAbstractListModel(parent), m_synchronize(false), m_source(NULL) |
904 | { |
905 | - QObject::connect(this, &Synchronizer::syncChanged, this, &Synchronizer::onSyncChanged); |
906 | + QObject::connect(this, &Synchronizer::synchronizeChanged, this, &Synchronizer::onSynchronizeChanged); |
907 | + QObject::connect(this, &Synchronizer::syncOutputChanged, this, &Synchronizer::onSyncOutputChanged); |
908 | } |
909 | |
910 | /*! |
911 | @@ -229,16 +231,73 @@ |
912 | * \property Synchronizer::synchronize |
913 | */ |
914 | |
915 | -void Synchronizer::setSync(bool synchronize) |
916 | +void Synchronizer::setSynchronize(bool synchronize) |
917 | { |
918 | |
919 | if (m_synchronize == synchronize) |
920 | return; |
921 | |
922 | m_synchronize = synchronize; |
923 | - Q_EMIT syncChanged(synchronize); |
924 | -} |
925 | - |
926 | + Q_EMIT synchronizeChanged(synchronize); |
927 | +} |
928 | + |
929 | +void Synchronizer::sendNetworkRequest(QNetworkAccessManager* manager, const QString& uri, QString method) |
930 | +{ |
931 | + Request* _request = new Request(uri, method); |
932 | + _request->m_request.setOriginatingObject(manager); |
933 | + |
934 | + connect(_request, &Request::send, this, &Synchronizer::networkRequestSent); |
935 | + connect(_request, &Request::cancel, this, &Synchronizer::networkRequestCancelled); |
936 | + connect(this, &Synchronizer::networkRequest, this, &Synchronizer::networkRequestEmitted); |
937 | + Q_EMIT networkRequest(_request); |
938 | +} |
939 | + |
940 | +void Synchronizer::networkRequestEmitted(Request* _request) |
941 | +{ |
942 | + if (!_request->getHandled()) |
943 | + Q_EMIT _request->send(); |
944 | +} |
945 | + |
946 | +void Synchronizer::networkRequestSent() |
947 | +{ |
948 | + Request* _request = qobject_cast<Request *>(QObject::sender()); |
949 | + QNetworkRequest request = _request->m_request; |
950 | + |
951 | + if (!_request->getAuthorization().isEmpty()) |
952 | + request.setRawHeader(QStringLiteral("Authorization").toUtf8(), |
953 | + _request->getAuthorization().toUtf8()); |
954 | + |
955 | + /* Validate here, otherwise QUrl is empty, finished won't be called */ |
956 | + QNetworkAccessManager* manager = qobject_cast<QNetworkAccessManager *>(request.originatingObject()); |
957 | + if (_request->getMethod() == "GET" && _request->m_request.url().isValid()) |
958 | + manager->get(request); |
959 | + else if (_request->getMethod() == "POST" && _request->m_request.url().isValid()) { |
960 | + request.setRawHeader("Content-Type", "application/x-u1db-sync-stream"); |
961 | + QByteArray postString(manager->property("postString").toByteArray()); |
962 | + QByteArray postDataSize = QByteArray::number(postString.size()); |
963 | + request.setRawHeader("Content-Length", postDataSize); |
964 | + manager->post(request, postString); |
965 | + } else { |
966 | + QVariantMap output_map; |
967 | + output_map.insert("message_type", "error"); |
968 | + output_map.insert("message_value", "Invalid Request.method: " + _request->getMethod()); |
969 | + m_sync_output.append(output_map); |
970 | + Q_EMIT syncOutputChanged(m_sync_output); |
971 | + setSynchronize(false); |
972 | + } |
973 | +} |
974 | + |
975 | +void Synchronizer::networkRequestCancelled(const QString& reason) |
976 | +{ |
977 | + disconnect(this, &Synchronizer::networkRequest, this, &Synchronizer::networkRequestEmitted); |
978 | + |
979 | + QVariantMap output_map; |
980 | + output_map.insert("message_type", "error"); |
981 | + output_map.insert("message_value", "Request was cancelled: " + reason); |
982 | + m_sync_output.append(output_map); |
983 | + Q_EMIT syncOutputChanged(m_sync_output); |
984 | + setSynchronize(false); |
985 | +} |
986 | |
987 | /*! |
988 | * \property Synchronizer::resolve_to_source |
989 | @@ -255,9 +314,9 @@ |
990 | |
991 | |
992 | /*! |
993 | - * \fn void Synchronizer::setSyncOutput(QList<QVariant> sync_output) |
994 | + * \fn void Synchronizer::setSyncOutput(QList<QVariant> syncOutput) |
995 | * |
996 | - * Sets the current value for the active session's \a sync_output. |
997 | + * Sets the current value for the active session's \a syncOutput. |
998 | * |
999 | */ |
1000 | |
1001 | @@ -296,7 +355,7 @@ |
1002 | } |
1003 | |
1004 | /*! |
1005 | - * \brief Synchronizer::getSync |
1006 | + * \brief Synchronizer::getSynchronize |
1007 | * |
1008 | * |
1009 | * Returns the current value of synchronize. If true then the synchronize |
1010 | @@ -308,7 +367,7 @@ |
1011 | * |
1012 | */ |
1013 | |
1014 | -bool Synchronizer::getSync() |
1015 | +bool Synchronizer::getSynchronize() |
1016 | { |
1017 | return m_synchronize; |
1018 | } |
1019 | @@ -329,7 +388,7 @@ |
1020 | } |
1021 | |
1022 | /*! |
1023 | - * \property Synchronizer::sync_output |
1024 | + * \property Synchronizer::syncOutput |
1025 | * \brief Synchronizer::getSyncOutput |
1026 | * |
1027 | * Returns the output from a sync session. The list should contain numerous |
1028 | @@ -358,6 +417,10 @@ |
1029 | |
1030 | */ |
1031 | |
1032 | +void Synchronizer::onSyncOutputChanged(QList<QVariant> syncOutput) { |
1033 | + beginResetModel(); |
1034 | + endResetModel(); |
1035 | +} |
1036 | |
1037 | /*! |
1038 | * \brief Synchronizer::onSyncChanged |
1039 | @@ -366,7 +429,7 @@ |
1040 | * |
1041 | */ |
1042 | |
1043 | -void Synchronizer::onSyncChanged(bool synchronize){ |
1044 | +void Synchronizer::onSynchronizeChanged(bool synchronize){ |
1045 | |
1046 | Database* source = getSource(); |
1047 | |
1048 | @@ -381,6 +444,7 @@ |
1049 | QMap<QString,QString>validator; |
1050 | |
1051 | validator.insert("remote","bool"); |
1052 | + validator.insert("uri","QString"); |
1053 | validator.insert("location","QString"); |
1054 | validator.insert("resolve_to_source","bool"); |
1055 | |
1056 | @@ -393,10 +457,11 @@ |
1057 | |
1058 | QList<QString>mandatory; |
1059 | |
1060 | - mandatory.append("remote"); |
1061 | + mandatory.append("uri"); |
1062 | mandatory.append("resolve_to_source"); |
1063 | |
1064 | if(synchronize == true){ |
1065 | + m_sync_output.clear(); |
1066 | |
1067 | /*! |
1068 | 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). |
1069 | @@ -410,34 +475,21 @@ |
1070 | */ |
1071 | |
1072 | synchronizeTargets(source, sync_targets); |
1073 | - |
1074 | - /*! |
1075 | - * After the synchronization is complete the model is reset so that |
1076 | - *log and error messages are available at the application level. |
1077 | - * |
1078 | - */ |
1079 | - |
1080 | - beginResetModel(); |
1081 | - endResetModel(); |
1082 | - |
1083 | - /*! |
1084 | - The convenience signals syncOutputChanged and syncCompleted are |
1085 | -emitted after the model has been reset. |
1086 | - */ |
1087 | - |
1088 | - Q_EMIT syncOutputChanged(m_sync_output); |
1089 | - Q_EMIT syncCompleted(); |
1090 | - |
1091 | - /*! |
1092 | - * The sync boolean value is reset to its default value (false) |
1093 | - *once all sync activity is complete. |
1094 | - */ |
1095 | - |
1096 | - setSync(false); |
1097 | - |
1098 | - } |
1099 | - else{ |
1100 | - |
1101 | + |
1102 | + if (sync_targets.isEmpty()) { |
1103 | + QVariantMap output_map; |
1104 | + output_map.insert("concerning_property", "targets"); |
1105 | + output_map.insert("message_type", "no-errors"); |
1106 | + output_map.insert("message_value", "No targets specified"); |
1107 | + m_sync_output.append(output_map); |
1108 | + Q_EMIT syncOutputChanged(m_sync_output); |
1109 | + } |
1110 | + |
1111 | + /* No sync started */ |
1112 | + if (m_sync_output.isEmpty() || sync_targets.isEmpty()) { |
1113 | + Q_EMIT syncCompleted(); |
1114 | + setSynchronize(false); |
1115 | + } |
1116 | } |
1117 | } |
1118 | |
1119 | @@ -486,6 +538,7 @@ |
1120 | output_map.insert("message_type","error"); |
1121 | output_map.insert("message_value",message_value); |
1122 | m_sync_output.append(output_map); |
1123 | + Q_EMIT syncOutputChanged(m_sync_output); |
1124 | |
1125 | target.insert("sync",false); |
1126 | |
1127 | @@ -542,7 +595,7 @@ |
1128 | target.insert("sync",true); |
1129 | sync_targets.append(target); |
1130 | |
1131 | - QString message_value = "Mandatory properties were included and their values are valid."; |
1132 | + QString message_value = "Mandatory properties included and valid."; |
1133 | |
1134 | QVariantMap output_map; |
1135 | output_map.insert("concerning_property","targets"); |
1136 | @@ -550,6 +603,7 @@ |
1137 | output_map.insert("message_type","no-errors"); |
1138 | output_map.insert("message_value",message_value); |
1139 | m_sync_output.append(output_map); |
1140 | + Q_EMIT syncOutputChanged(m_sync_output); |
1141 | |
1142 | } |
1143 | |
1144 | @@ -588,67 +642,53 @@ |
1145 | if(target.typeName()== QStringLiteral("QVariantMap")){ |
1146 | QMap<QString,QVariant> target_map = target.toMap(); |
1147 | |
1148 | - if(target_map.contains("remote")&&target_map["remote"]==false){ |
1149 | - if(target_map.contains("sync")&&target_map["sync"]==true){ |
1150 | + QVariantMap output_map; |
1151 | + output_map.insert("concerning_property", "targets"); |
1152 | + output_map.insert("concerning_index", target_index); |
1153 | |
1154 | + bool sync = target_map.contains("sync") && target_map["sync"].toBool(); |
1155 | + QUrl url(target_map["uri"].toString()); |
1156 | + if (url.isLocalFile()) { |
1157 | + if (sync) { |
1158 | QString message_value = "Valid local target."; |
1159 | - |
1160 | - QVariantMap output_map; |
1161 | - output_map.insert("concerning_property","targets"); |
1162 | - output_map.insert("concerning_index",target_index); |
1163 | output_map.insert("message_type","no-errors"); |
1164 | output_map.insert("message_value",message_value); |
1165 | m_sync_output.append(output_map); |
1166 | + Q_EMIT syncOutputChanged(m_sync_output); |
1167 | |
1168 | syncLocalToLocal(source, target_map); |
1169 | } |
1170 | } |
1171 | - else if(target_map.contains("remote")&&target_map["remote"]==true){ |
1172 | - if(target_map.contains("sync")&&target_map["sync"]==true){ |
1173 | - |
1174 | - //ip |
1175 | - //port |
1176 | - //name |
1177 | + else if (url.scheme() == "http" || url.scheme() == "https") { |
1178 | + if (sync) { |
1179 | //GET /thedb/sync-from/my_replica_uid |
1180 | |
1181 | QString source_uid = getUidFromLocalDb(source->getPath()); |
1182 | - QString get_string = target_map["name"].toString()+"/sync-from/"+source_uid; |
1183 | - QString url_string = "http://"+target_map["ip"].toString(); |
1184 | - QString full_get_request = url_string+"/"+get_string; |
1185 | - int port_number = target_map["port"].toInt(); |
1186 | + QString full_get_request = target_map["uri"].toString() + "/sync-from/" + source_uid; |
1187 | |
1188 | QNetworkAccessManager *manager = new QNetworkAccessManager(source); |
1189 | |
1190 | - QUrl url(full_get_request); |
1191 | - url.setPort(port_number); |
1192 | - |
1193 | - QNetworkRequest request(url); |
1194 | - |
1195 | connect(manager, &QNetworkAccessManager::finished, this, &Synchronizer::remoteGetSyncInfoFinished); |
1196 | |
1197 | QString message_value = "Valid remote target."; |
1198 | |
1199 | - QVariantMap output_map; |
1200 | - output_map.insert("concerning_property","targets"); |
1201 | - output_map.insert("concerning_index",target_index); |
1202 | output_map.insert("message_type","no-errors"); |
1203 | output_map.insert("message_value",message_value); |
1204 | m_sync_output.append(output_map); |
1205 | - |
1206 | - manager->get(QNetworkRequest(request)); |
1207 | - |
1208 | + Q_EMIT syncOutputChanged(m_sync_output); |
1209 | + |
1210 | + sendNetworkRequest(manager, full_get_request, "GET"); |
1211 | } |
1212 | } |
1213 | else{ |
1214 | |
1215 | - QString message_value = "Unknown error. Please check properties"; |
1216 | + QString message_value = "Invalid URI, must be file, http or https"; |
1217 | |
1218 | - QVariantMap output_map; |
1219 | - output_map.insert("concerning_property","targets"); |
1220 | - output_map.insert("concerning_index",target_index); |
1221 | output_map.insert("message_type","error"); |
1222 | output_map.insert("message_value",message_value); |
1223 | m_sync_output.append(output_map); |
1224 | + Q_EMIT syncOutputChanged(m_sync_output); |
1225 | + setSynchronize(false); |
1226 | |
1227 | } |
1228 | |
1229 | @@ -671,7 +711,7 @@ |
1230 | void Synchronizer::syncLocalToLocal(Database *sourceDb, QMap<QString,QVariant> target) |
1231 | { |
1232 | |
1233 | - QString target_db_name = target["location"].toString(); |
1234 | + QString target_db_name = QUrl(target["uri"].toString()).toLocalFile(); |
1235 | |
1236 | Database *targetDb; |
1237 | Index *targetIndex; |
1238 | @@ -705,6 +745,8 @@ |
1239 | output_map.insert("message_type","error"); |
1240 | output_map.insert("message_value",message_value); |
1241 | m_sync_output.append(output_map); |
1242 | + Q_EMIT syncOutputChanged(m_sync_output); |
1243 | + setSynchronize(false); |
1244 | |
1245 | return; |
1246 | } |
1247 | @@ -890,6 +932,8 @@ |
1248 | /* 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.*/ |
1249 | |
1250 | |
1251 | + Q_EMIT syncOutputChanged(m_sync_output); |
1252 | + setSynchronize(false); |
1253 | } |
1254 | |
1255 | /*! |
1256 | @@ -966,64 +1010,28 @@ |
1257 | |
1258 | QString Synchronizer::getUidFromLocalDb(QString dbFileName) |
1259 | { |
1260 | - |
1261 | QString dbUid; |
1262 | - |
1263 | - QSqlDatabase db; |
1264 | - |
1265 | - db = QSqlDatabase::addDatabase("QSQLITE",QUuid::createUuid().toString()); |
1266 | - |
1267 | - QFile db_file(dbFileName); |
1268 | - |
1269 | - if(!db_file.exists()) |
1270 | - { |
1271 | - |
1272 | - QString message_value = "Database does not exist."; |
1273 | - |
1274 | + Database localDb; |
1275 | + |
1276 | + localDb.setPath(dbFileName); |
1277 | + QString lastError(localDb.lastError()); |
1278 | + if (lastError.isEmpty()) { |
1279 | + dbUid = localDb.getReplicaUid(); |
1280 | + dbUid = dbUid.replace("{",""); |
1281 | + dbUid = dbUid.replace("}",""); |
1282 | + lastError = localDb.lastError(); |
1283 | + } |
1284 | + |
1285 | + if (!lastError.isEmpty()) { |
1286 | + QString message_value = localDb.lastError(); |
1287 | QVariantMap output_map; |
1288 | + |
1289 | output_map.insert("concerning_property","source|targets"); |
1290 | output_map.insert("concerning_database",dbFileName); |
1291 | output_map.insert("message_type","error"); |
1292 | output_map.insert("message_value",message_value); |
1293 | m_sync_output.append(output_map); |
1294 | - |
1295 | - return dbUid; |
1296 | - } |
1297 | - else |
1298 | - { |
1299 | - db.setDatabaseName(dbFileName); |
1300 | - if (!db.open()){ |
1301 | - |
1302 | - QString message_value = db.lastError().text(); |
1303 | - |
1304 | - QVariantMap output_map; |
1305 | - output_map.insert("concerning_property","source|targets"); |
1306 | - output_map.insert("concerning_database",dbFileName); |
1307 | - output_map.insert("message_type","error"); |
1308 | - output_map.insert("message_value",message_value); |
1309 | - m_sync_output.append(output_map); |
1310 | - |
1311 | - } |
1312 | - else{ |
1313 | - QSqlQuery query (db.exec("SELECT value FROM u1db_config WHERE name = 'replica_uid'")); |
1314 | - |
1315 | - if(!query.lastError().isValid() && query.next()){ |
1316 | - dbUid = query.value(0).toString(); |
1317 | - db.close(); |
1318 | - |
1319 | - dbUid = dbUid.replace("{",""); |
1320 | - |
1321 | - dbUid = dbUid.replace("}",""); |
1322 | - |
1323 | - return dbUid; |
1324 | - } |
1325 | - else{ |
1326 | - qDebug() << query.lastError().text(); |
1327 | - db.close(); |
1328 | - return dbUid; |
1329 | - } |
1330 | - } |
1331 | - |
1332 | + Q_EMIT syncOutputChanged(m_sync_output); |
1333 | } |
1334 | |
1335 | return dbUid; |
1336 | @@ -1047,6 +1055,21 @@ |
1337 | |
1338 | void Synchronizer::remoteGetSyncInfoFinished(QNetworkReply* reply) |
1339 | { |
1340 | + QVariant statusCodeVariant = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); |
1341 | + int statusCode(statusCodeVariant.isValid() ? statusCodeVariant.toInt() : -1); |
1342 | + |
1343 | + if(!(statusCode == 200 || statusCode == 401)) { |
1344 | + QVariant reasonVariant = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute); |
1345 | + QString reason(reasonVariant.isValid() ? reasonVariant.toString() : "N/A"); |
1346 | + QVariantMap output_map; |
1347 | + output_map.insert("concerning_property", "targets"); |
1348 | + output_map.insert("message_type", "error"); |
1349 | + output_map.insert("message_value", QString("Pre-sync bad HTTP code: %1 - %2").arg(statusCode).arg(reason)); |
1350 | + m_sync_output.append(output_map); |
1351 | + Q_EMIT syncOutputChanged(m_sync_output); |
1352 | + setSynchronize(false); |
1353 | + return; |
1354 | + } |
1355 | |
1356 | QNetworkAccessManager *manager = reply->manager(); |
1357 | |
1358 | @@ -1123,15 +1146,8 @@ |
1359 | |
1360 | postString.append("\r\n]"); |
1361 | |
1362 | - QByteArray postDataSize = QByteArray::number(postString.size()); |
1363 | - |
1364 | - QNetworkRequest request(postUrl); |
1365 | - request.setRawHeader("User-Agent", "U1Db-Qt v1.0"); |
1366 | - request.setRawHeader("X-Custom-User-Agent", "U1Db-Qt v1.0"); |
1367 | - request.setRawHeader("Content-Type", "application/x-u1db-sync-stream"); |
1368 | - request.setRawHeader("Content-Length", postDataSize); |
1369 | - |
1370 | - manager->post(QNetworkRequest(request),postString); |
1371 | + manager->setProperty("postString", postString); |
1372 | + sendNetworkRequest(manager, postUrl.toString(), "POST"); |
1373 | |
1374 | } |
1375 | |
1376 | @@ -1150,6 +1166,21 @@ |
1377 | |
1378 | void Synchronizer::remotePostSyncInfoFinished(QNetworkReply* reply) |
1379 | { |
1380 | + QVariant statusCodeVariant = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); |
1381 | + int statusCode(statusCodeVariant.isValid() ? statusCodeVariant.toInt() : -1); |
1382 | + |
1383 | + if(!(statusCode == 200 || statusCode == 409 || statusCode == 401)) { |
1384 | + QVariant reasonVariant = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute); |
1385 | + QString reason(reasonVariant.isValid() ? reasonVariant.toString() : "N/A"); |
1386 | + QVariantMap output_map; |
1387 | + output_map.insert("concerning_property", "targets"); |
1388 | + output_map.insert("message_type", "error"); |
1389 | + output_map.insert("message_value", QString("Post-sync bad HTTP code: %1 - %2").arg(statusCode).arg(reason)); |
1390 | + m_sync_output.append(output_map); |
1391 | + Q_EMIT syncOutputChanged(m_sync_output); |
1392 | + setSynchronize(false); |
1393 | + return; |
1394 | + } |
1395 | |
1396 | QNetworkAccessManager *manager = reply->manager(); |
1397 | |
1398 | @@ -1180,9 +1211,32 @@ |
1399 | |
1400 | replyData = replyData.replace("\r\n",""); |
1401 | |
1402 | - QJsonDocument replyJson = QJsonDocument::fromJson(replyData.toUtf8()); |
1403 | - |
1404 | - QVariant replyVariant = replyJson.toVariant(); |
1405 | + QJsonParseError parseError; |
1406 | + QJsonDocument replyJson = QJsonDocument::fromJson(replyData.toUtf8(), &parseError); |
1407 | + QString syncError; |
1408 | + QVariant replyVariant; |
1409 | + |
1410 | + if (parseError.error == QJsonParseError::NoError) { |
1411 | + // Valid JSON: errors may be returned as { "error": "Foo bar" } |
1412 | + replyVariant = replyJson.toVariant(); |
1413 | + QVariantMap replyMap(replyVariant.toMap()); |
1414 | + if (replyMap.contains("error")) |
1415 | + syncError = QString("Server - %1: %2").arg(replyMap["error"].toString()).arg(replyMap["message"].toString()); |
1416 | + } else { |
1417 | + // The response is likely an HTML page but can also be malformed JSON |
1418 | + syncError = QString("JSON - %1").arg(parseError.errorString()); |
1419 | + } |
1420 | + |
1421 | + if (!syncError.isEmpty()) { |
1422 | + QVariantMap output_map; |
1423 | + output_map.insert("concerning_property", "targets"); |
1424 | + output_map.insert("message_type", "error"); |
1425 | + output_map.insert("message_value", "Error: " + syncError); |
1426 | + m_sync_output.append(output_map); |
1427 | + Q_EMIT syncOutputChanged(m_sync_output); |
1428 | + setSynchronize(false); |
1429 | + return; |
1430 | + } |
1431 | |
1432 | QVariantList replyList = replyVariant.toList(); |
1433 | |
1434 | @@ -1190,6 +1244,8 @@ |
1435 | |
1436 | int index = -1; |
1437 | |
1438 | + QString changelog; |
1439 | + |
1440 | while(i.hasNext()){ |
1441 | |
1442 | index++; |
1443 | @@ -1240,6 +1296,7 @@ |
1444 | { |
1445 | source->putDoc(content,id); |
1446 | source->updateDocRevisionNumber(id,rev); |
1447 | + changelog += QString(" %1@%2").arg(id).arg(rev); |
1448 | } |
1449 | |
1450 | } |
1451 | @@ -1248,6 +1305,16 @@ |
1452 | |
1453 | } |
1454 | |
1455 | + if (changelog.isEmpty()) |
1456 | + changelog = "No changes"; |
1457 | + |
1458 | + QVariantMap output_map; |
1459 | + output_map.insert("concerning_property", "source|targets"); |
1460 | + output_map.insert("message_type", "no-errors"); |
1461 | + output_map.insert("message_value", "Sync done:" + changelog); |
1462 | + m_sync_output.append(output_map); |
1463 | + Q_EMIT syncOutputChanged(m_sync_output); |
1464 | + setSynchronize(false); |
1465 | } |
1466 | |
1467 | QT_END_NAMESPACE_U1DB |
1468 | |
1469 | === modified file 'src/synchronizer.h' |
1470 | --- src/synchronizer.h 2013-11-22 15:21:37 +0000 |
1471 | +++ src/synchronizer.h 2014-02-20 14:57:43 +0000 |
1472 | @@ -31,6 +31,7 @@ |
1473 | #include "database.h" |
1474 | #include "index.h" |
1475 | #include "query.h" |
1476 | +#include "request.h" |
1477 | |
1478 | QT_BEGIN_NAMESPACE_U1DB |
1479 | |
1480 | @@ -41,10 +42,10 @@ |
1481 | #else |
1482 | Q_PROPERTY(QT_PREPEND_NAMESPACE_U1DB(Database*) source READ getSource WRITE setSource NOTIFY sourceChanged) |
1483 | #endif |
1484 | - Q_PROPERTY(bool synchronize READ getSync WRITE setSync NOTIFY syncChanged) |
1485 | + Q_PROPERTY(bool synchronize READ getSynchronize WRITE setSynchronize NOTIFY synchronizeChanged) |
1486 | Q_PROPERTY(bool resolve_to_source READ getResolveToSource WRITE setResolveToSource NOTIFY resolveToSourceChanged) |
1487 | Q_PROPERTY(QVariant targets READ getTargets WRITE setTargets NOTIFY targetsChanged) |
1488 | - Q_PROPERTY(QList<QVariant> sync_output READ getSyncOutput NOTIFY syncOutputChanged) |
1489 | + Q_PROPERTY(QList<QVariant> syncOutput READ getSyncOutput NOTIFY syncOutputChanged) |
1490 | |
1491 | public: |
1492 | Synchronizer(QObject* parent = 0); |
1493 | @@ -60,13 +61,13 @@ |
1494 | |
1495 | Database* getSource(); |
1496 | QVariant getTargets(); |
1497 | - bool getSync(); |
1498 | + bool getSynchronize(); |
1499 | bool getResolveToSource(); |
1500 | QList<QVariant> getSyncOutput(); |
1501 | |
1502 | void setSource(Database* source); |
1503 | void setTargets(QVariant targets); |
1504 | - void setSync(bool synchronize); |
1505 | + void setSynchronize(bool synchronize); |
1506 | void setResolveToSource(bool resolve_to_source); |
1507 | void setSyncOutput(QList<QVariant> sync_output); |
1508 | |
1509 | @@ -97,11 +98,11 @@ |
1510 | void targetsChanged(QVariant targets); |
1511 | |
1512 | /*! |
1513 | - * \brief syncChanged |
1514 | + * \brief synchronizeChanged |
1515 | * \param synchronize |
1516 | */ |
1517 | |
1518 | - void syncChanged(bool synchronize); |
1519 | + void synchronizeChanged(bool synchronize); |
1520 | |
1521 | /*! |
1522 | * \brief resolveToSourceChanged |
1523 | @@ -112,7 +113,7 @@ |
1524 | |
1525 | /*! |
1526 | * \brief syncOutputChanged |
1527 | - * \param sync_output |
1528 | + * \param syncOutput |
1529 | */ |
1530 | |
1531 | void syncOutputChanged(QList<QVariant> sync_output); |
1532 | @@ -123,6 +124,12 @@ |
1533 | |
1534 | void syncCompleted(); |
1535 | |
1536 | + /*! |
1537 | + * \brief networkRequest |
1538 | + * \param request |
1539 | + */ |
1540 | + void networkRequest(Request* request); |
1541 | + |
1542 | private: |
1543 | //Q_DISABLE_COPY(Synchronizer) |
1544 | bool m_synchronize; |
1545 | @@ -131,10 +138,16 @@ |
1546 | QVariant m_targets; |
1547 | QList<QVariant> m_sync_output; |
1548 | |
1549 | - void onSyncChanged(bool synchronize); |
1550 | + void onSynchronizeChanged(bool synchronize); |
1551 | + void onSyncOutputChanged(QList<QVariant> syncOutput); |
1552 | void remoteGetSyncInfoFinished(QNetworkReply* reply); |
1553 | void remotePostSyncInfoFinished(QNetworkReply* reply); |
1554 | |
1555 | + void sendNetworkRequest(QNetworkAccessManager* manager, const QString& uri, QString method); |
1556 | + void networkRequestSent(); |
1557 | + void networkRequestCancelled(const QString& reason); |
1558 | + void networkRequestEmitted(Request* request); |
1559 | + |
1560 | }; |
1561 | |
1562 | QT_END_NAMESPACE_U1DB |
1563 | |
1564 | === added file 'tests/tst_sync.qml' |
1565 | --- tests/tst_sync.qml 1970-01-01 00:00:00 +0000 |
1566 | +++ tests/tst_sync.qml 2014-02-20 14:57:43 +0000 |
1567 | @@ -0,0 +1,97 @@ |
1568 | +/* |
1569 | + * Copyright (C) 2014 Canonical, Ltd. |
1570 | + * |
1571 | + * Authors: |
1572 | + * Christian Dywan <christian.dywan@canonical.com> |
1573 | + * |
1574 | + * This program is free software; you can redistribute it and/or modify |
1575 | + * it under the terms of the GNU Lesser General Public License as published by |
1576 | + * the Free Software Foundation; version 3. |
1577 | + * |
1578 | + * This program is distributed in the hope that it will be useful, |
1579 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1580 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1581 | + * GNU Lesser General Public License for more details. |
1582 | + * |
1583 | + * You should have received a copy of the GNU Lesser General Public License |
1584 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1585 | + */ |
1586 | + |
1587 | +import QtQuick 2.0 |
1588 | +import QtTest 1.0 |
1589 | +import U1db 1.0 as U1db |
1590 | + |
1591 | +Item { |
1592 | + width: 200; height: 200 |
1593 | + |
1594 | + U1db.Database { |
1595 | + id: stars |
1596 | + } |
1597 | + |
1598 | + U1db.Document { |
1599 | + database: stars |
1600 | + docId: 'earth' |
1601 | + contents: { 'habitable': true } |
1602 | + } |
1603 | + |
1604 | + U1db.Document { |
1605 | + database: stars |
1606 | + docId: 'jupiter' |
1607 | + contents: { 'moons': [ 'europa' ] } |
1608 | + } |
1609 | + |
1610 | + U1db.Synchronizer { |
1611 | + id: online |
1612 | + source: stars |
1613 | + } |
1614 | + |
1615 | + SignalSpy { |
1616 | + id: spySynchronizeChanged |
1617 | + target: online |
1618 | + signalName: "synchronizeChanged" |
1619 | + } |
1620 | + |
1621 | + U1db.Server { |
1622 | + id: mockServer |
1623 | + port: 0 |
1624 | + property bool unresponsive: false |
1625 | + onNetworkRequest: { |
1626 | + if (!unresponsive) |
1627 | + write ("HTTP/1.0 404 Ok\nContent-Type: text/html; charset=\"utf-8\"\nNot found!\n") |
1628 | + } |
1629 | + } |
1630 | + |
1631 | +TestCase { |
1632 | + name: "Sync" |
1633 | + |
1634 | + function test_0_incomplete () { |
1635 | + online.synchronize = true |
1636 | + spySynchronizeChanged.wait () |
1637 | + compare(online.syncOutput[0].message_value, "No targets specified") |
1638 | + |
1639 | + online.targets = [ { } ] |
1640 | + online.synchronize = true |
1641 | + spySynchronizeChanged.wait () |
1642 | + compare(0, online.syncOutput[1].message_value.indexOf("Invalid URI")) |
1643 | + |
1644 | + online.targets = [ { "uri": "foo://bar.com:9876/rien.db", |
1645 | + resolve_to_source: true } ] |
1646 | + online.synchronize = true |
1647 | + spySynchronizeChanged.wait () |
1648 | + compare(0, online.syncOutput[1].message_value.indexOf("Invalid URI")) |
1649 | + |
1650 | + online.targets = [ { "uri": "http://.invalid:1234/nada.db" } ] |
1651 | + online.synchronize = true |
1652 | + spySynchronizeChanged.wait () |
1653 | + compare(0, online.syncOutput[0].message_value.indexOf("Expected property `resolve_to_source")) |
1654 | + } |
1655 | + |
1656 | + function test_1_faultyServer () { |
1657 | + online.targets = [ { "uri": "http://.invalid:1234/nada.db", |
1658 | + resolve_to_source: true } ] |
1659 | + online.synchronize = true |
1660 | + spySynchronizeChanged.wait () |
1661 | + compare("no-errors", online.syncOutput[0].message_type) |
1662 | + } |
1663 | +} } |
1664 | + |
FAILED: Continuous integration, rev:118 jenkins. qa.ubuntu. com/job/ u1db-qt- ci/31/ jenkins. qa.ubuntu. com/job/ u1db-qt- precise- amd64-ci/ 31 jenkins. qa.ubuntu. com/job/ u1db-qt- quantal- amd64-ci/ 31/console jenkins. qa.ubuntu. com/job/ u1db-qt- saucy-amd64- ci/33 jenkins. qa.ubuntu. com/job/ u1db-qt- trusty- amd64-ci/ 11 jenkins. qa.ubuntu. com/job/ u1db-qt- trusty- armhf-ci/ 11
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/u1db- qt-ci/31/ rebuild
http://