u1db-qt-0.1.5+14.04.20140313/0000755000015201777760000000000012310436007015360 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/HACKING0000644000015201777760000000123312310435504016347 0ustar pbusernogroup00000000000000*** Building and installing *** cmake .; make sudo make install *** Test from the build folder *** qmlscene -I ./modules examples/u1db-qt-example-1.qml qmltestrunner -import ./modules make check *** Run reference test cases *** make env PYTHONPATH=/path/to/u1db ./tests/test-upstream.py *** Use U1DB Python API to check sqlite compatiblity *** python -c "import u1db;db=u1db.open('pDb',create=False);print(db.get_all_docs());print(db.list_indexes())" python -c "import u1db;db=u1db.open('pDb',create=True);db.create_index('by-phone', 'gents.phone');doc3 = db.create_doc({'gents': [ {'name': 'Mary', 'phone': '12345'}, {'name': 'Peter', 'phone': '54321'} ]})" u1db-qt-0.1.5+14.04.20140313/documentation/0000755000015201777760000000000012310436007020231 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/documentation/bestpractices.qdoc0000644000015201777760000000000012310435504023723 0ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/documentation/u1db.qdocconf0000644000015201777760000000555612310435504022616 0ustar pbusernogroup00000000000000project = U1Db-Qt description = U1Db-Qt plugin documentation url = http://developer.ubuntu.com/api/devel/ubuntu-13.10/qml/u1db-qt5/ indexes = /usr/share/ubuntu-ui-toolkit/doc/html/ubuntuuserinterfacetoolkit.index sourcedirs = ../src \ ../documentation headers = ../src/database.h \ ../src/document.h \ ../src/index.h \ ../src/query.h \ ../src/synchronizer.h \ ../src/global.h Cpp.ignoretokens = Q_DECL_EXPORT \ Q_PROPERTY \ QT_BEGIN_NAMESPACE_U1DB \ Q_INVOKABLE \ defines = Q_QDOC Cpp.ignoredirectives = Q_ENUMS \ Q_FLAGS \ QT_PREPEND_NAMESPACE_U1DB \ Q_DISABLE_COPY exampledirs = ../examples/u1db-qt-example-1 \ ../examples/u1db-qt-example-2 \ ../examples/u1db-qt-example-2b \ ../examples/u1db-qt-example-3 \ ../examples/u1db-qt-example-4 \ ../examples/u1db-qt-example-5 \ ../examples/u1db-qt-example-6 imagedirs = images sources.fileextensions = "*.cpp *.qdoc *.mm *.qml" headers.fileextensions = "*.h *.ch *.h++ *.hh *.hpp *.hxx" examples.fileextensions = "*.cpp *.h *.js *.xq *.svg *.xml *.ui *.qhp *.qhcp" examples.imageextensions = "*.png *.jpeg *.jpg *.gif *.mng" outputdir = output/html outputformats = HTML syntaxhighlighting = true HTML.postheader = "
\n" \ "
\n" HTML.footer = "
\n" \ "
\n" HTML.headerstyles = "\n" \ "\n" \ "\n" \ "\n" \ "\n" \ "\n" HTML.stylesheets = style/base.css \ style/css.css \ style/css_002.css \ style/reset.css \ style/qtquick.css \ style/base.css \ style/scratch.css \ qhp.projects = U1DbQt qhp.U1DbQt.file = u1dbqt.qhp qhp.U1DbQt.namespace = net.launchpad.u1db-qt qhp.U1DbQt.virtualFolder = u1dbqt qhp.U1DbQt.indexTitle = U1DbQt Reference Documentation qhp.U1DbQt.indexRoot = qhp.U1DbQt.subprojects = overview tutorial concepts qhp.U1DbQt.subprojects.overview.title = U1DbQt Overview qhp.U1DbQt.subprojects.overview.indexTitle = All Modules qhp.U1DbQt.subprojects.overview.selectors = class qhp.U1DbQt.subprojects.tutorial.title = U1DbQt Tutorial qhp.U1DbQt.subprojects.tutorial.indexTitle = Tutorial qhp.U1DbQt.subprojects.tutorial.selectors = fake:page,group,module qhp.U1DbQt.subprojects.concepts.title = U1DbQt Design Concepts qhp.U1DbQt.subprojects.concepts.indexTitle = Design Concepts qhp.U1DbQt.subprojects.concepts.selectors = fake:example u1db-qt-0.1.5+14.04.20140313/documentation/CMakeLists.txt0000644000015201777760000000346212310435504022777 0ustar pbusernogroup00000000000000option(BUILD_DOCS "Build documentation" OFF) if (BUILD_DOCS) set(ALL "ALL") endif () set(U1DB_DOCS "${CMAKE_CURRENT_BINARY_DIR}/output") add_custom_command(OUTPUT overview.html COMMAND "mkdir" "-p" "${U1DB_DOCS}" COMMAND "qdoc" "-outputdir" "${U1DB_DOCS}" "${CMAKE_CURRENT_SOURCE_DIR}/u1db.qdocconf" "2>" "${CMAKE_CURRENT_BINARY_DIR}/qdoc.err" COMMAND "cat" "${CMAKE_CURRENT_BINARY_DIR}/qdoc.err" COMMAND "test" "!" "-s" "${CMAKE_CURRENT_BINARY_DIR}/qdoc.err" COMMAND "sed" "-r" "-i" "'s@()@\\1@'" "${U1DB_DOCS}/*.html" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS U1DBPlugin ) add_custom_command(OUTPUT u1dbqt.qhp COMMAND "qhelpgenerator" "${U1DB_DOCS}/u1dbqt.qhp" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS overview.html ) add_custom_target(doc ${ALL} DEPENDS u1dbqt.qhp overview.html) if (BUILD_DOCS) install(FILES "${U1DB_DOCS}/u1dbqt.qch" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/qt5/phrasebooks/" ) install(FILES "${U1DB_DOCS}/u1dbqt.qch" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/qt5/doc/qch/" ) install(DIRECTORY "${U1DB_DOCS}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/u1db-qt/doc/html" FILES_MATCHING PATTERN "*.html" ) install(DIRECTORY "${U1DB_DOCS}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/u1db-qt/doc/html" FILES_MATCHING PATTERN "*.index" ) install(DIRECTORY "${U1DB_DOCS}/style/" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/u1db-qt/doc/html/style" FILES_MATCHING PATTERN "*.css" ) install(DIRECTORY DESTINATION ${CMAKE_INSTALL_PREFIX}/share/u1db-qt/examples FILES_MATCHING PATTERN "*example-*.html" ) endif () u1db-qt-0.1.5+14.04.20140313/documentation/concepts.qdoc0000644000015201777760000002123212310435504022720 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \page concepts.html \title Design Concepts This concept guide will describe a wide variety of U1Db-Qt functionality and usage. It will cover: \list 1 \li Overview of U1Db Documents and Databases \li Creating Documents and Databases \li Database keys and Document contents \li Listing docId values in a Database \li Retrieving Documents \li Searching and retrieving Documents by docId \li Modifying Existing Documents \li Document Functions \li Index expressions \li Querying an index \li Index functions \li Blending the U1Db-Qt plugin with QML and Javascript \li U1Db-Qt with QML Elements and Components \li Using U1Db-Qt with elements and components that support models \li Using U1Db-Qt with elements and components that do not utilize models \li Using a Document without a Database \endlist \section1 Brief Description of U1DB U1DB is a database API for synchronised databases of JSON documents. It’s simple to use in applications, and allows apps to store documents and synchronise them between machines and devices. U1DB is the database designed to work everywhere, backed by the platform’s native data storage capabilities. This means that you can use u1db on different platforms, from different languages, and backed on to different databases, and sync between all of them. \section1 What is the difference between U1DB and U1Db-Qt U1Db-Qt is the QML implementation of U1DB. It is a QML plugin written in C++ and allows for creating and manipulating U1DB databases via a more declarative approach within a QML application. 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. \qml U1db.Database { id: aDatabase path: "aU1DbDSatabase2" } \endqml A Document can be declared at runtime. It requires at the very least a unique 'docId', but that alone won't do anything special. The snipet below snippet demonstrates the basic requirements. In addition to this, this example displays text from the database for a specific docId and id key in a text area called 'documentContent. To update the text area at startup with either the default value or a value from the database the onCompleted function is utilized, which is also demonstrated below. \qml U1db.Document { id: aDocument database: aDatabase docId: 'helloworld' create: true defaults: { "helloworld":"Hello World" } Component.onCompleted: { documentContent.text = aDocument.contents.helloworld } } \endqml It should be possible to use a document without a database, as demonstrated in this snippet. Additionally this document will use the concept of sub-keys, as exemplified by the "bookmarks" id key + contents. This example will attempt to use the bookmark document to store docId values from the database, which will be displayed in a ListView on the second tab of the application. The user will be able to select a value from the ListView and the first tab will be modified accordingly. \qml U1db.Document { id: aBookmarkDocument docId: 'bookmarks' create: true defaults: { "bookmarks": [{}] } } \endqml The listDocs method retrieves all the docId values from the current database. In this demonstration the values are put into an array, which is then checked to locate the docId for the current and previous documents within the database. \code var documentIds = documentObject.database.listDocs() for(var i = 0; i < documentIds.length; i++){ if(documentIds[i]===documentObject.docId && i > 0){ return documentIds[i-1] } else if(documentIds[i]===documentObject.docId && i==0){ return documentIds[documentIds.length-1] } } \endcode These steps demonstrate the creation of a temporary document, based on a copy of the global document. This will then be used to determine if there is already a document in the database with the same docId as the address bar, and additionally with a key id with the same name. \code var tempFieldName = addressBarText; var tempDocument = aDocument tempDocument.docId = addressBarText; var tempContents = tempDocument.contents \endcode \b{Note: For simplicity sake this example sometimes uses the same value for both the docId and the key id, as seen here. Real life implimentations can and will differ, and this will be demonstrated elsewhere in the example code.} Here the contents of the temporary document are modified, which then replaces the global document. \code documentContent.text = 'More Hello World...'; var tempContents = {} tempContents[tempFieldName] = documentContent.text tempDocument.contents = tempContents aDocument = tempDocument \endcode In this instance the current document's content is updated from the text view. The unique key and docId are not modified because the database already contains a record with those properties. \code var tempContents = {} tempFieldName = getCurrentDocumentKey(aDocument.contents) tempContents[tempFieldName] = documentContent.text aDocument.contents = tempContents \endcode Here a rectangle is defined that represents the lower portion of our application. It will contain all the main parts of the application. \qml Rectangle { width: units.gu(45) height: units.gu(70) anchors.bottom: parent.bottom color: "#00FFFFFF" // The remainder of the main part of the application goes here ... } \endqml The following TextArea is for displaying contents for the current state of the global document, as defined by the key / name in the address bar. \qml TextArea{ id: documentContent selectByMouse : false x: units.gu(1) y: units.gu(1) width: units.gu(43) height: units.gu(58) color: "#000000" } \endqml There is an object within in the 'aDocument' model defined earlier called 'contents', which contains a key called 'hello', which represents a search string. In this example the key will represent the name of a document in the database, which will be displayed in the address bar. Displaying the key is demonstrated here: \qml TextArea{ text: displayKey(aDocument.contents) function displayKey(documentObject){ var keys = Object.keys(documentObject); return keys[0] } } \endqml */ u1db-qt-0.1.5+14.04.20140313/documentation/overview.qdoc0000644000015201777760000000240412310435504022750 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \page overview.html overview \title All Modules \contentspage {All Modules} {Contents} \part General Topics \list \li \l {Tutorial} gives a quick start guide from storing a document to querying a database and displaying it in \l {Ubuntu User Interface Toolkit}{Ubuntu Components}. \li \l {Design Concepts} provides an overview of the design and terminology. \endlist \part Document Storage Available through: \code import U1db 1.0 as U1db \endcode \annotatedlist modules */ u1db-qt-0.1.5+14.04.20140313/documentation/u1db.tutorial.qdoc0000644000015201777760000001750212310435504023604 0ustar pbusernogroup00000000000000/*! \page tutorial.html \title Tutorial \chapter 1.0.0 Overview of U1Db-Qt U1Db-Qt is a QML plugin written in Qt C++. It provides declarative, easy to use, local data storage for QML applications, and in the future will also include remote sync capabilities. U1Db-Qt is based on a procedural implementation in Python, but has been designed and developed from the start with declarative programming in mind. While U1Db-Qt makes use of SQLite in the back end, and relies heavily on JSON, these are largely invisible to a QML developer who makes use of the plugin. However, because U1Db-Qt does rely on both technologies it is possible to easily debug applications using existing programs. For example, a developer could make use of SQLiteBrowser to read U1Db database files, which contain human readable JSON objects representing content derived from a QML application. \section1 1.0.1 How to Make U1Db-Qt Available to a QML Application Here is an example import statement: \code import U1db 1.0 as U1db \endcode \chapter 2.0.0 Database Element In U1Db-Qt, the Database element is the the central figure that works in conjunction with Document elements, and in the future indexing (currently under development), and querying (currently under development) elements. It is the Database element that defines the location of the data repository. A Database element is also a valuable tool for cases where specific data from a repository needs to be retrieved quickly, without indexing or querying ahead of time. The 'contents' object associated with the Database element can be used together with base items such as TextField or TextArea and model-based items like ListView and \l {Standard}{ListItems.Standard}. \section1 2.0.1 Database Properties \code QString path QString error \endcode \section1 2.1.0 Creating a Database A Database is very simple to create. It requires nothing more than an id and a path where the file will be created. \section2 2.1.1 Example of Creating a Database \code import QtQuick 2.0 import U1db 1.0 as U1db Item{ U1db.Database { id: aDatabase path: "aU1DbDatabase" } } \endcode \chapter 3.0.0 Document Element The Document element is the primary vehicle for entering data into a repository, and can be helpful in some cases for getting data out as well. Indexing and querying would normally provide more robust functionality for extracting data from a repository, but in the case of U1Db-Qt both are still under development at the time of writing (and therefore not available for developers to use). However, the Document element together with Database can still help developers in many common situations, and will continue to be very useful even when the indexing and querying functionality becomes available. When a developer wants unfiltered results from a database, or the cost of working with unfiltered results is reasonable, the Document+Database combination is fast and easy to use. In quite a number of use cases this may be exactly what a developer needs. \section1 3.0.1 Document Properties \code U1db.Database database QString docId bool create QVariant defaults QVariant contents \endcode \section1 3.1.0 Creating a Basic Document A Document declaration should contain at least a unique 'docId' and 'database', but these alone won't do anything by themselves. Additionally, although the 'id' property is not mandatory, this property will need to be set in order to more easily reference it from elsewhere in the program (e.g. within a function call). \section2 3.1.0.1 Example of Creating a Basic Document \code import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 Item{ width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aU1DbDatabase" } } \endcode \section1 3.1.1 Creating a Document At Runtime A Document can be declared at runtime, and default data entered into the repository. This requires the same properties to be set as in the basic example ('id', 'database' and 'docId'), plus setting 'create' (to true) and a 'default' string. \section2 3.1.1.1 Example of Creating a Document At Runtime \code import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 Item{ width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aU1DbDatabase" } U1db.Document { id: aDocument database: aDatabase docId: 'helloworld' create: true defaults: { "hello": "Hello World!" } } } \endcode \section1 3.1.2 Creating a Document Dynamically Creating a Document in a dynamic fashion is the most common way of putting data into a data repository based on UI activity (e.g. when a user presses a button). \section2 3.1.2.1 Example 1 of Creating a Document Dynamically Another way of creating a new Document is to copy an existing Document: \code import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 Item{ width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aU1DbDatabase" } U1db.Document { id: aDocument database: aDatabase docId: 'helloworld' } function someFunction() { var tempDocument = {} tempDocument = aDocument } } \endcode \section2 3.1.2.2 Example 2 of Creating a Document Dynamically One way of creating a new Document dynamically is to make use of Qt.createQmlObject: \code import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 Item{ width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aU1DbDatabase" Component.onCompleted: { newDocumentObject() } function newDocumentObject() { var qmlString = "import QtQuick 2.0; import U1db 1.0 as U1db; U1db.Document {id: aDcoument; database: aDatabase; docId: 'helloworld'; create: true; defaults: { 'hello': 'Hello New Document!' }}" Qt.createQmlObject(qmlString, aDatabase); } } } \endcode \chapter 4.0.0 U1Db-Qt and QML Elements and Models \section1 4.1.0 U1Db-Qt and Standard Elements \section2 4.1.1 U1Db-Qt and TextField & TextArea \section3 4.1.2 Example of Using U1Db-Qt with Standard Elements \code import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 Item{ width: units.gu(45) height: units.gu(80) function getContent(fieldName){ var tempContents = {}; tempContents = aDocument.contents return tempContents[fieldName] } U1db.Database { id: aDatabase path: "aU1DbDatabase" } U1db.Document { id: aDocument database: aDatabase docId: 'helloworld' create: true defaults: { "hello": "Hello World 1!" } } TextField { id: addressBar width: units.gu(45) text: getContent('hello') } } \endcode \section1 4.2.0 U1Db-Qt and Model-Based Elements \section2 4.2.1 U1Db-Qt and ListView \section3 4.2.2 Example of Using U1Db-Qt with Model-Based Elements \code import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 Item{ width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aU1DbDatabase" } U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld' create: true defaults: { "hello": "Hello World 1!" } } U1db.Document { id: aDocument2 database: aDatabase docId: 'helloworld' create: true defaults: { "hello": "Hello World 2!" } } ListView { model: aDatabase width: units.gu(45) height: units.gu(80) delegate: Text { x: 66; y: 77 text: contents.hello } } } \endcode \chapter 5.0.0 Resources \section1 5.0.1 Examples One can find several examples in the bzr branch of U1Db-Qt (bzr branch lp:u1db-qt) either in the subdirectory "examples" or from the following url: http://bazaar.launchpad.net/~uonedb-qt/u1db-qt/trunk/files/head:/examples/ These examples are currently under development (as is U1Db-Qt in general), but should still be able to demonstrate the fundamentals discussed within this document. */ u1db-qt-0.1.5+14.04.20140313/documentation/style/0000755000015201777760000000000012310436007021371 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/documentation/style/scratch.css0000644000015201777760000000261012310435504023532 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ body { margin: 0; } div.toc ul { padding: 0; } div.toc li { margin-bottom: 3px; } h1.title { font-size: 36px; line-height: 1.1; font-weight: normal; } h0, h2 { font-size: 24px; line-height: 1.2; margin: 14px 0; font-weight: normal; display: block; } a:hover { color: #dd4814; text-decoration: underline; outline: 0; } table, pre { border-radius: 0; } .annotated td { padding: 0.8em 1em 0.3em; } .wrapper { width: 940px; margin: 0 auto; } .main-content { width: 668px; position: relative; left: 270px; } .title { margin-left: -270px; margin-top: 30px; margin-bottom: 50px; } .toc { margin-left: -270px; font-size: 100%; margin-bottom: 40px; padding: 0; z-index: 2; position: absolute; top: 100px; width: 250px; } u1db-qt-0.1.5+14.04.20140313/documentation/style/reset.css0000644000015201777760000000153312310435504023230 0ustar pbusernogroup00000000000000/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}u1db-qt-0.1.5+14.04.20140313/documentation/style/css_002.css0000644000015201777760000000164212310435504023260 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ @font-face { font-family: 'Ubuntu Mono'; font-style: normal; font-weight: 400; src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(http://themes.googleusercontent.com/static/fonts/ubuntumono/v3/ViZhet7Ak-LRXZMXzuAfkYbN6UDyHWBl620a-IRfuBk.woff) format('woff'); } u1db-qt-0.1.5+14.04.20140313/documentation/style/css.css0000644000015201777760000000215412310435504022676 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ @font-face { font-family: 'Ubuntu'; font-style: normal; font-weight: 400; src: local('Ubuntu'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/_xyN3apAT_yRRDeqB3sPRg.woff) format('woff'); } @font-face { font-family: 'Ubuntu'; font-style: italic; font-weight: 400; src: local('Ubuntu Italic'), local('Ubuntu-Italic'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/kbP_6ONYVgE-bLa9ZRbvvvesZW2xOQ-xsNqO47m55DA.woff) format('woff'); } u1db-qt-0.1.5+14.04.20140313/documentation/style/qtquick.css0000644000015201777760000003316512310435504023575 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ @media screen { /* basic elements */ html { color: #000000; background: #FFFFFF; } table { border-collapse: collapse; border-spacing: 0; } fieldset, img { border: 0; max-width:100%; } address, caption, cite, code, dfn, em, strong, th, var, optgroup { font-style: inherit; font-weight: inherit; } del, ins { text-decoration: none; } ol li { list-style: decimal; } ul li { list-style: none; } caption, th { text-align: left; } h1.title { font-weight: bold; font-size: 150%; } h0 { font-weight: bold; font-size: 130%; } h1, h2, h3, h4, h5, h6 { font-size: 100%; } q:before, q:after { content: ''; } abbr, acronym { border: 0; font-variant: normal; } sup, sub { vertical-align: baseline; } tt, .qmlreadonly span, .qmldefault span { word-spacing:0.5em; } legend { color: #000000; } strong { font-weight: bold; } em { font-style: italic; } body { margin: 0 1.5em 0 1.5em; font-family: ubuntu; line-height: normal } a { color: #00732F; text-decoration: none; } hr { background-color: #E6E6E6; border: 1px solid #E6E6E6; height: 1px; width: 100%; text-align: left; margin: 1.5em 0 1.5em 0; } pre { border: 1px solid #DDDDDD; -moz-border-radius: 0.7em 0.7em 0.7em 0.7em; -webkit-border-radius: 0.7em 0.7em 0.7em 0.7em; border-radius: 0.7em 0.7em 0.7em 0.7em; padding: 1em 1em 1em 1em; overflow-x: auto; } table, pre { -moz-border-radius: 0.7em 0.7em 0.7em 0.7em; -webkit-border-radius: 0.7em 0.7em 0.7em 0.7em; border-radius: 0.7em 0.7em 0.7em 0.7em; background-color: #F6F6F6; border: 1px solid #E6E6E6; border-collapse: separate; margin-bottom: 2.5em; } pre { font-size: 90%; display: block; overflow:hidden; } thead { margin-top: 0.5em; font-weight: bold } th { padding: 0.5em 1.5em 0.5em 1em; background-color: #E1E1E1; border-left: 1px solid #E6E6E6; } td { padding: 0.25em 1.5em 0.25em 1em; } td.rightAlign { padding: 0.25em 0.5em 0.25em 1em; } table tr.odd { border-left: 1px solid #E6E6E6; background-color: #F6F6F6; color: black; } table tr.even { border-left: 1px solid #E6E6E6; background-color: #ffffff; color: #202020; } div.float-left { float: left; margin-right: 2em } div.float-right { float: right; margin-left: 2em } span.comment { color: #008B00; } span.string, span.char { color: #000084; } span.number { color: #a46200; } span.operator { color: #202020; } span.keyword { color: #840000; } span.name { color: black } span.type { font-weight: bold } span.type a:visited { color: #0F5300; } span.preprocessor { color: #404040 } /* end basic elements */ /* font style elements */ .heading { font-weight: bold; font-size: 125%; } .subtitle { font-size: 110% } .small-subtitle { font-size: 100% } .red { color:red; } /* end font style elements */ /* global settings*/ .header, .footer { display: block; clear: both; overflow: hidden; } /* end global settings*/ /* header elements */ .header .qtref { color: #00732F; font-weight: bold; font-size: 130%; } .header .content { margin-left: 5px; margin-top: 5px; margin-bottom: 0.5em; } .header .breadcrumb { font-size: 90%; padding: 0.5em 0 0.5em 1em; margin: 0; background-color: #fafafa; height: 1.35em; border-bottom: 1px solid #d1d1d1; } .header .breadcrumb ul { margin: 0; padding: 0; } .header .content { word-wrap: break-word; } .header .breadcrumb ul li { float: left; background: url(../images/breadcrumb.png) no-repeat 0 3px; padding-left: 1.5em; margin-left: 1.5em; } .header .breadcrumb ul li.last { font-weight: normal; } .header .breadcrumb ul li a { color: #00732F; } .header .breadcrumb ul li.first { background-image: none; padding-left: 0; margin-left: 0; } .header .content ol li { background: none; margin-bottom: 1.0em; margin-left: 1.2em; padding-left: 0 } .header .content li { background: url(../images/bullet_sq.png) no-repeat 0 5px; margin-bottom: 1em; padding-left: 1.2em; } /* end header elements */ /* content elements */ .content h1 { font-weight: bold; font-size: 130% } .content h2 { font-weight: bold; font-size: 120%; width: 100%; } .content h3 { font-weight: bold; font-size: 110%; width: 100%; } .content table p { margin: 0 } .content ul { padding-left: 2.5em; } .content li { padding-top: 0.25em; padding-bottom: 0.25em; } .content ul img { vertical-align: middle; } .content a:visited { color: #4c0033; text-decoration: none; } .content a:visited:hover { color: #4c0033; text-decoration: underline; } a:hover { color: #4c0033; text-decoration: underline; } descr p a { text-decoration: underline; } .descr p a:visited { text-decoration: underline; } .alphaChar{ width:95%; background-color:#F6F6F6; border:1px solid #E6E6E6; -moz-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; font-size:12pt; padding-left:10px; margin-top:10px; margin-bottom:10px; } .flowList{ /*vertical-align:top;*/ /*margin:20px auto;*/ column-count:3; -webkit-column-count:3; -moz-column-count:3; /* column-width:100%; -webkit-column-width:200px; -col-column-width:200px; */ column-gap:41px; -webkit-column-gap:41px; -moz-column-gap:41px; column-rule: 1px dashed #ccc; -webkit-column-rule: 1px dashed #ccc; -moz-column-rule: 1px dashed #ccc; } .flowList dl{ } .flowList dd{ /*display:inline-block;*/ margin-left:10px; min-width:250px; line-height: 1.5; min-width:100%; min-height:15px; } .flowList dd a{ } .mainContent { padding-left:5px; } .content .flowList p{ padding:0px; } .content .alignedsummary { margin: 15px; } .qmltype { text-align: center; font-size: 120%; } .qmlreadonly { padding-left: 5px; float: right; color: #254117; } .qmldefault { padding-left: 5px; float: right; color: red; } .qmldoc { } .generic .alphaChar{ margin-top:5px; } .generic .odd .alphaChar{ background-color: #F6F6F6; } .generic .even .alphaChar{ background-color: #FFFFFF; } .memItemRight{ padding: 0.25em 1.5em 0.25em 0; } .highlightedCode { margin: 1.0em; } .annotated td { padding: 0.25em 0.5em 0.25em 0.5em; } .toc { font-size: 80% } .header .content .toc ul { padding-left: 0px; } .content .toc h3 { border-bottom: 0px; margin-top: 0px; } .content .toc h3 a:hover { color: #00732F; text-decoration: none; } .content .toc .level2 { margin-left: 1.5em; } .content .toc .level3 { margin-left: 3.0em; } .content ul li { background: url(../images/bullet_sq.png) no-repeat 0 0.7em; padding-left: 1em } .content .toc li { background: url(../images/bullet_dn.png) no-repeat 0 5px; padding-left: 1em } .relpage { -moz-border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; border: 1px solid #DDDDDD; padding: 25px 25px; clear: both; } .relpage ul { float: none; padding: 1.5em; } h3.fn, span.fn { -moz-border-radius:7px 7px 7px 7px; -webkit-border-radius:7px 7px 7px 7px; border-radius:7px 7px 7px 7px; background-color: #F6F6F6; border-width: 1px; border-style: solid; border-color: #E6E6E6; font-weight: bold; word-spacing:3px; padding:3px 5px; } .functionIndex { font-size:12pt; word-spacing:10px; margin-bottom:10px; background-color: #F6F6F6; border-width: 1px; border-style: solid; border-color: #E6E6E6; -moz-border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; width:100%; } .centerAlign { text-align:center; } .rightAlign { text-align:right; } .leftAlign { text-align:left; } .topAlign{ vertical-align:top } .functionIndex a{ display:inline-block; } /* end content elements */ /* footer elements */ .footer { color: #393735; font-size: 0.75em; text-align: center; padding-top: 1.5em; padding-bottom: 1em; background-color: #E6E7E8; margin: 0; } .footer p { margin: 0.25em } .small { font-size: 0.5em; } /* end footer elements */ .item { float: left; position: relative; width: 100%; overflow: hidden; } .item .primary { margin-right: 220px; position: relative; } .item hr { margin-left: -220px; } .item .secondary { float: right; width: 200px; position: relative; } .item .cols { clear: both; display: block; } .item .cols .col { float: left; margin-left: 1.5%; } .item .cols .col.first { margin-left: 0; } .item .cols.two .col { width: 45%; } .item .box { margin: 0 0 10px 0; } .item .box h3 { margin: 0 0 10px 0; } .cols.unclear { clear:none; } } /* end of screen media */ /* start of print media */ @media print { input, textarea, .header, .footer, .toolbar, .feedback, .wrapper .hd, .wrapper .bd .sidebar, .wrapper .ft, #feedbackBox, #blurpage, .toc, .breadcrumb, .toolbar, .floatingResult { display: none; background: none; } .content { background: none; display: block; width: 100%; margin: 0; float: none; } } /* end of print media */ /* modify the TOC layouts */ div.toc ul { padding-left: 20px; } div.toc li { padding-left: 4px; } /* Remove the border around images*/ a img { border:none; } /*Add styling to the front pages*/ .threecolumn_area { padding-top: 20px; padding-bottom: 20px; } .threecolumn_piece { display: inline-block; margin-left: 78px; margin-top: 8px; padding: 0; vertical-align: top; width: 25.5%; } div.threecolumn_piece ul { list-style-type: none; padding-left: 0px; margin-top: 2px; } div.threecolumn_piece p { margin-bottom: 7px; color: #5C626E; text-decoration: none; font-weight: bold; } div.threecolumn_piece li { padding-left: 0px; margin-bottom: 5px; } div.threecolumn_piece a { font-weight: normal; } /* Add style to guide page*/ .fourcolumn_area { padding-top: 20px; padding-bottom: 20px; } .fourcolumn_piece { display: inline-block; margin-left: 35px; margin-top: 8px; padding: 0; vertical-align: top; width: 21.3%; } div.fourcolumn_piece ul { list-style-type: none; padding-left: 0px; margin-top: 2px; } div.fourcolumn_piece p { margin-bottom: 7px; color: #40444D; text-decoration: none; font-weight: bold; } div.fourcolumn_piece li { padding-left: 0px; margin-bottom: 5px; } div.fourcolumn_piece a { font-weight: normal; } u1db-qt-0.1.5+14.04.20140313/documentation/style/base.css0000644000015201777760000002706712310435504023032 0ustar pbusernogroup00000000000000/** * Ubuntu Developer base stylesheet * * A base stylesheet containing site-wide styles * * @project Ubuntu Developer * @version 1.0 * @author Canonical Web Team: Steve Edwards * @copyright 2011 Canonical Ltd. */ /** * @section Global */ body { font-family: 'Ubuntu', 'Ubuntu Beta', UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif; font-size: 13px; line-height: 1.4; color: #333; } a { color: #dd4814; text-decoration: none; outline: 0; } p, dl { margin-bottom: 10px; } strong { font-weight: bold; } em { font-style: italic; } code{ padding: 10px; font-family: 'Ubuntu Mono', 'Consolas', 'Monaco', 'DejaVu Sans Mono', Courier, monospace; background-color: #fdf6f2; display: block; margin-bottom: 10px; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } h1 { font-size: 36px; line-height: 1.1; margin-bottom: 20px; } article h1, h2 { font-size: 24px; line-height: 1.2; margin-bottom: 14px; } h3 { font-size: 16px; line-height: 1.3; margin-bottom: 8px; } h4 { font-weight: bold; } time { color:#999; } /** * @section Structure */ .header-login, .header-navigation div, .header-content div { margin: 0 auto; width: 940px; } .header-content h1{ background-color:#ffffff; display:inline-block; } .header-content h2{ background-color:#ffffff; display:table; } .header-login ul { margin: 4px 0; float: right; } .header-login li { margin-right: 10px; float: left; } .header-login a { color: #333; } .header-navigation { border-top: 2px solid #dd4814; border-bottom: 2px solid #dd4814; background-color: #fff; height: 54px; clear: right; overflow: hidden; } .header-navigation nav ul { border-right: 1px solid #dd4814; float: right; } .header-navigation nav li { border-left: 1px solid #dd4814; float: left; height: 54px; } .header-navigation nav a { padding: 18px 14px 0; font-size: 14px; display: block; height: 36px; } .header-navigation nav a:hover { background-color: #fcece7; } .header-navigation nav .current_page_item a, .header-navigation nav .current_page_parent a, .header-navigation nav .current_page_ancestor a { background-color: #dd4814; color: #fff; } .header-navigation input { margin: 12px 10px 0 10px; padding: 5px; border-top: 1px solid #a1a1a1; border-right: 1px solid #e0e0e0; border-bottom: 1px solid #fff; border-left: 1px solid #e0e0e0; width: 90px; font-style: italic; color: #ccc; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; -moz-box-shadow: inset 0 1px 1px #e0e0e0; -webkit-box-shadow: inset 0 1px 1px #e0e0e0; box-shadow: inset 0 1px 1px #e0e0e0; } .header-navigation h2 { margin: 18px 0 0 6px; text-transform: lowercase; font-size: 22px; color: #dd4814; float: left; } .header-navigation .logo-ubuntu { margin-top: 12px; float: left; } .header-content .header-navigation-secondary { margin-bottom: 40px; padding: 0; position: relative; z-index: 2; } .header-navigation-secondary div { padding: 0; border: 2px solid #dd4814; -moz-border-radius: 0px 0px 4px 4px; -webkit-border-radius: 0px 0px 4px 4px; border-radius: 0px 0px 4px 4px; background: #fff; border-top: 0px; width: 936px; } .header-navigation-secondary nav li { float: left; } .header-navigation-secondary nav li a { color: #333; display: block; height: 25px; padding: 8px 8px 0; } .header-navigation-secondary nav li:hover, .header-navigation-secondary nav .current_page_item a { background: url("../img/sec-nav-hover.gif"); } .header-content { padding-bottom: 30px; border-bottom: 1px solid #e0e0e0; -moz-box-shadow: 0 1px 3px #e0e0e0; -webkit-box-shadow: 0 1px 3px #e0e0e0; box-shadow: 0 1px 3px #e0e0e0; margin-bottom: 3px; position: relative; overflow: hidden; } footer { padding: 10px 10px 40px 10px; position: relative; -moz-border-radius: 0 0 4px 4px; -webkit-border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px; font-size: 12px; background: url("../img/background-footer.png") repeat scroll 0 0 #f7f6f5; } footer div { margin: 0 auto; padding: 0 10px; width: 940px; } footer a { color: #000; } footer nav ul { margin: 10px 17px 30px 0; width: 172px; display: inline-block; vertical-align: top; height: auto; zoom: 1; *display: inline; } footer nav ul.last { margin-right: 0; } footer nav li { margin-bottom: 8px; } footer nav li:first-child { font-weight: bold; } footer p { margin-bottom: 0; } #content { padding-top: 35px; } .arrow-nav { display: none; position: absolute; top: -1px; z-index: 3; } .shadow { margin: 30px 0 3px 0; border-bottom: 1px solid #e0e0e0; -moz-box-shadow: 0 2px 3px #e0e0e0; -webkit-box-shadow: 0 2px 3px #e0e0e0; box-shadow: 0 2px 3px #e0e0e0; height: 3px; } /** * @section Site-wide */ #content h2{ font-size:24px; } .box-orange { padding: 10px; border: 3px solid #dd4814; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } .box-orange .link-action-small { float: right; margin: 0 0 0 20px; } .link-bug { margin-left: 10px; color: #999; } .link-action { float: left; margin-bottom: 20px; padding: 8px 12px; display: block; background-color: #dd4814; color: #fff; -moz-border-radius: 20px; -webkit-border-radius: 20px; border-radius: 20px; font-size: 16px; line-height: 1.3; border-top: 3px solid #e6633a; border-bottom: 3px solid #c03d14; } .link-action2 { float: left; display: block; color: #fff; font-size: 16px; line-height: 1.3; } .link-action2 span{ display:block; float:left; } .link-action2 .cta-left{ background:url(../img/button-cta-left.png) no-repeat; width:22px; height:48px; } .link-action2 .cta-center{ background:url(../img/button-cta-slice.png) repeat-x; line-height:45px; height:48px; } .link-action2 .cta-right{ background:url(../img/button-cta-right.png) no-repeat; width:22px; height:48px; } .link-action-small { float: left; display: block; color: #fff; font-size: 16px; } .link-action-small span{ display:block; float:left; height:42px; } .link-action-small .cta-left{ background:url(../img/button-cta-left-small.png) no-repeat; width:19px; } .link-action-small .cta-center{ background:url(../img/button-cta-slice-small.png) repeat-x; line-height:42px; } .link-action-small .cta-right{ background:url(../img/button-cta-right-small.png) no-repeat; width:19px; } .link-action:active { position: relative; top: 1px; } .link-action2:active { position: relative; top: 1px; } .link-action-small:active { position: relative; top: 1px; } .list-bullets li { margin-bottom: 10px; list-style: disc; list-style-position: inside; } .box { margin-bottom: 30px; padding: 15px; border: 1px solid #aea79f; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } .box-padded { margin-bottom: 30px; padding: 5px; border: 2px solid #aea79f; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background: url("../img/pattern-featured.gif") repeat scroll 0 0 #ebe9e7; overflow: hidden; } .box-padded h3 { margin: 5px 0 10px 5px; } .box-padded div { padding: 10px; border: 1px solid #aea79f; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background-color: #fff; overflow: hidden; } .box-padded li { padding: 0 10px; float: left; width: 211px; border-right: 1px dotted #aea79f; } .box-padded li.first { padding: 0; margin-bottom: 0; } .box-padded li.last { border: 0; width: 217px; } .box-padded img { margin: 0 10px 50px 0; float: left; -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; } .box-clear { margin-bottom: 40px; } .box-clear .grid-4.first { margin-right: 15px; padding-right: 15px; } .box-clear .grid-4 { margin-left: 0; margin-right: 10px; padding-right: 10px; width: 298px; } .box-clear time { display: block; border-bottom: 1px dotted #aea79f; padding-bottom: 10px; margin-bottom: 10px; } .box-clear div.first { border-right: 1px dotted #aea79f; } .box-clear a { display: block; } .box-clear .rss { background: url("../img/rss.jpg") no-repeat scroll 0 center; padding-left: 20px; } .box-clear .location { display: block; margin-bottom: 1px; } .box-clear .last { margin: 0; padding-right: 0; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; width: 293px; } /* Widgets */ .ui-state-focus { outline: none; } .ui-accordion { border-bottom: 1px dotted #aea79f; } .ui-accordion a { display: block; } .ui-accordion h3 { margin-bottom: 0; border-top: 1px dotted #aea79f; position: relative; font-size: 13px; font-weight: bold; } .ui-accordion h3 a { padding: 10px 0; color: #333; } .ui-accordion h4 { margin-bottom: 5px; } .ui-accordion div fieldset { padding-bottom: 5px; } .ui-accordion div li, .ui-accordion div input { margin-bottom: 10px; } .ui-accordion .ui-icon { position: absolute; top: 15px; right: 0; display: block; width: 8px; height: 8px; background: url("../img/icon-accordion-inactive.png") 0 0 no-repeat transparent; } .ui-accordion .ui-state-active .ui-icon { background-image: url("../img/icon-accordion-active.png"); } .ui-accordion .current_page_item a { color: #333; } .container-tweet { -moz-border-radius: 4px 4px 4px 4px; -webkit-border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px; padding: 10px 10px 10px; background-color: #f7f7f7; } .container-tweet .tweet-follow { margin-top: 10px; margin-bottom: -10px; padding-left: 55px; padding-bottom: 6px; background: url("../img/tweet-follow.png") 0 5px no-repeat; display: block; } .container-tweet .tweet-follow span { font-size: 16px; font-weight: bold; line-height: 1.2; display: block; } .tweet a { display: inline; } .tweet .tweet_text { padding: 10px; background-color: #fff; -moz-border-radius: 4px 4px 4px 4px; -webkit-border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px; border: 1px solid #dd4814; font-size: 16px; display: block; clear: both; } .tweet.tweet-small .tweet_text { font-size: inherit; } .tweet .tweet_text a { color: #333; } .tweet .tweet_time, .tweet .tweet_user_and_time { padding: 15px 0 10px 0; position: relative; top: -2px; background: url("../img/tweet-arrow.png") no-repeat; display: block; } .tweet .tweet_odd .tweet_time, .tweet .tweet_odd .tweet_user_and_time { background-position: right 0; float: right; } .tweet .tweet_even .tweet_time, .tweet .tweet_even .tweet_user_and_time { background-position: left 0; float: left; } /* Search */ #content .list-search li { list-style-type:none; border:0px; margin-bottom: 15px; padding-top: 15px; } /* Blog */ .blog-article #nav-single { margin-top: 30px; margin-bottom: 30px; } .blog-article #nav-single .nav-next { float: right; } .blog-article article header .entry-meta { margin-bottom: 20px; } .blog-article article .entry-meta { color: #999; } .blog-article #respond form input[type="submit"] { float: left; cursor: pointer; margin-bottom: 20px; padding: 8px 12px; display: block; background-color: #dd4814; color: #fff; -moz-border-radius: 20px; -webkit-border-radius: 20px; border-radius: 20px; font-size: 16px; line-height: 1.3; border-top: 3px solid #e6633a; border-left: 3px solid #e6633a; border-right: 3px solid #e6633a; border-bottom: 3px solid #c03d14; } .blog-article #respond form input[type="submit"]:active { position: relative; top: 1px; } .alignnone{ float:left; margin:10px 20px 10px 0; } .alignleft{ float:left; margin:10px 20px 10px 0; } .alignright{ float:right; margin:10px 0 10px 20px; } .aligncenter{ float:left; margin:10px 20px 10px 0; } .entry-content h2, .entry-content h3{ margin-top:20px; } .entry-content ul li{ list-style-type: circle; margin-left:16px; } .entry-content hr{ border:none; border-top: 1px dotted #AEA79F; } u1db-qt-0.1.5+14.04.20140313/documentation/troubleshooting.qdoc0000644000015201777760000000000012310435504024317 0ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/documentation/architecture.qdoc0000644000015201777760000000000012310435504023552 0ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/tests/0000755000015201777760000000000012310436007016522 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/tests/tst_query.qml0000644000015201777760000002507412310435522021305 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import QtTest 1.0 import U1db 1.0 as U1db Item { width: 200; height: 200 U1db.Database { id: gents } U1db.Document { database: gents docId: '1' contents: { 'gents': [ { 'name': 'Mary', 'phone': 12345 }, { 'name': 'Rob', 'phone': 54321 }, ] } } U1db.Document { database: gents docId: 'a' contents: { 'gents': [ { 'name': 'George', 'phone': 'NA' }, { 'name': 'Ivanka', 'phone': 50243 }, ] } } U1db.Document { database: gents docId: '_' contents: { 'misc': { 'software': 'linux', 'sports': [ 'basketball', 'hockey' ] }, 'date': '2014-01-01' , 'gents': [ { 'name': 'Ivanka', 'phone': 00321 }, ] } } U1db.Document { database: gents docId: 'F' contents: { 'details': { 'name': 'spy', 'type': 'hide', 'colour': 'blue' } } } U1db.Document { database: gents docId: 'G' contents: { 'details': { 'name': 'kid', 'type': 'show', 'colour': 'green' } } } U1db.Index { id: byPhone database: gents name: 'by-phone' expression: ['gents.phone'] } U1db.Index { id: byNamePhone database: gents name: 'by-name-phone' expression: ['gents.name', 'gents.phone'] } U1db.Index { id: byDate database: gents name: 'by-date' expression: ['date', 'sports', 'software'] } U1db.Query { id: defaultPhone index: byPhone } U1db.Query { id: allPhone index: byPhone query: '*' } U1db.Query { id: allPhoneList index: byPhone query: ['*'] } U1db.Query { id: allPhoneKeywords index: byPhone query: [ { 'phone': '*' } ] } U1db.Query { id: s12345Phone index: byPhone query: '12345' } U1db.Query { id: i12345Phone index: byPhone query: [ { 'phone': 12345 } ] } U1db.Query { id: i12345PhoneSimple index: byPhone query: 12345 } U1db.Query { id: s1wildcardPhone index: byPhone query: '1*' } U1db.Query { id: ivankaAllNamePhone index: byNamePhone query: [ { name: 'Ivanka', phone: '*' } ] } U1db.Query { id: ivankaAllNamePhoneSimple index: byNamePhone query: ['Ivanka', '*'] } U1db.Query { id: ivankaAllNamePhoneKeywords index: byNamePhone query: [ { 'name': 'Ivanka', 'phone': '*' } ] } U1db.Query { id: toplevelQuery index: byDate query: [{ 'date': '2014*', 'sports': 'basketball', 'software': 'linux' }] } U1db.Query { id: toplevelQuerySimple index: byDate query: [ '2014*', 'basketball', 'linux' ] } U1db.Query { id: queryOne index: U1db.Index { database: gents name: 'one' expression: [ 'details.type' ] } query: [ 'show' ] } U1db.Query { id: queryBothSimple index: U1db.Index { database: gents name: 'bothSimple' expression: [ 'details.type', 'details.colour' ] } query: [ 'show', '*' ] } U1db.Query { id: queryBoth index: U1db.Index { database: gents name: 'both' expression: [ 'details.type', 'details.colour' ] } query: [ { type: 'show', colour: '*' } ] } U1db.Database { id: tokusatsu } U1db.Document { database: tokusatsu docId: 'ooo' contents: { 'series': 'ooo', 'type': 'rider' } } U1db.Document { database: tokusatsu docId: 'gokaiger' contents: { 'series': 'gokaiger', 'type': 'sentai' } } U1db.Document { id: tokusatsuDocumentWizard docId: 'wizard' contents: { 'series': 'wizard', 'type': 'rider', 'transformations': ['Flame Style','Water Style'] } } U1db.Document { id: tokusatsuDocumentDino docId: 'dino' contents: { 'series': 'zyuranger', 'scarf': false, 'type': 'sentai', 'beasts': ['T-Rex', 'Mastodon'] } } U1db.Index { id: bySeries database: tokusatsu name: 'by-series' expression: ['series', 'type'] } U1db.Query { id: allHeroesWithType index: bySeries query: [{ 'series': '*' }, { 'type': '*' }] } U1db.Query { id: allHeroesSeriesOnly index: bySeries query: [{ 'series': '*' }] } SignalSpy { id: spyDocumentsChanged target: defaultPhone signalName: "documentsChanged" } TestCase { name: "U1dbDatabase" when: windowShown function prettyJson(j) { var A = JSON.stringify(j) if (A['0'] && A != '{}') { var A = '[' for(var i in j) A += JSON.stringify(j[i]) + ',' A = A.substring(0, A.lastIndexOf(',')) + ']' } return A } function compareFail(a, b, msg) { compare(a, b, msg, true) } function compare(a, b, msg, willFail) { /* Override built-in compare to: Match different JSON for identical values (number hash versus list) Produce readable output for all JSON values */ if (a == b) return var A = prettyJson(a), B = prettyJson(b) if (A != B) { if (willFail) { console.log('Expected failure: %1%2 != %3'.arg(msg ? msg + ': ' : '').arg(A).arg(B)) return } fail('%5%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)).arg(msg ? msg + ': ' : '')) } if (willFail) fail('Expected to fail, but passed: %5%1 != %2 (%3 != %4)'.arg(A).arg(B).arg(JSON.stringify(a)).arg(JSON.stringify(b)).arg(msg ? msg + ': ' : '')) } function workaroundQueryAndWait (buggyQuery) { var realQuery = buggyQuery.query; spyDocumentsChanged.target = buggyQuery spyDocumentsChanged.wait(); } function test_1_defaults () { // We should get all documents workaroundQueryAndWait(defaultPhone) compare(defaultPhone.documents, ['1', '_', 'a'], 'uno') compare(defaultPhone.results.length, 3, 'dos') compare(defaultPhone.results.length, defaultPhone.documents.length, 'puntos') // These queries are functionally equivalent compare(defaultPhone.documents, allPhone.documents, 'tres') compare(defaultPhone.documents, allPhoneList.documents, 'quatro') workaroundQueryAndWait(allPhoneKeywords) compare(defaultPhone.documents, allPhoneKeywords.documents, 'cinco') // Results are also equivalent compare(defaultPhone.results.length, allPhoneKeywords.results.length , 'siete') compare(defaultPhone.results, allPhoneKeywords.results, 'seis') // Results are lists of matching index fields with their values var firstDocId = gents.listDocs()[0] compare(defaultPhone.documents[0], firstDocId) var firstContents = gents.getDoc(firstDocId) compare(defaultPhone.results[0], {"phone": firstContents.gents[0].phone}) } function test_2_numbers () { // We should get '1' compare(s12345Phone.documents, ['1'], 'uno') // It's okay to mix strings and numerical values compare(s12345Phone.documents, i12345Phone.documents, 'dos') compare(i12345PhoneSimple.documents, i12345Phone.documents, 'tres') } function test_3_wildcards () { // Trailing string wildcard compare(s1wildcardPhone.documents, ['1'], 'uno') // Last given field can use wildcards compare(ivankaAllNamePhoneSimple.documents, ['_', 'a'], 'dos') compare(ivankaAllNamePhone.documents, ['_', 'a'], 'tres') // These queries are functionally equivalent workaroundQueryAndWait(ivankaAllNamePhoneKeywords) workaroundQueryAndWait(ivankaAllNamePhone) compare(ivankaAllNamePhone.documents, ivankaAllNamePhoneKeywords.documents, 'tres') compare(toplevelQuery.documents, ['_']) compare(toplevelQuerySimple.documents, ['_'], 'cinco') } function test_4_delete () { compare(defaultPhone.documents, ['1', '_', 'a'], 'uno') // Deleted aka empty documents should not be returned gents.deleteDoc('_') compare(defaultPhone.documents, ['1', 'a'], 'dos') } function test_5_fields () { compare(queryOne.documents, ['G'], 'one field') compare(queryBoth.documents, ['G'], 'two fields') compare(queryBothSimple.documents, ['G'], 'two fields simple') } function test_6_definition () { workaroundQueryAndWait(allHeroesWithType) compare(allHeroesWithType.documents, ['gokaiger', 'ooo'], 'ichi') workaroundQueryAndWait(allHeroesSeriesOnly) compare(allHeroesSeriesOnly.documents, ['gokaiger', 'ooo'], 'ni') compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'doube-check') // Add a document with extra fields tokusatsu.putDoc(tokusatsuDocumentWizard.contents, tokusatsuDocumentWizard.docId) workaroundQueryAndWait(allHeroesWithType) compare(allHeroesWithType.documents, ['gokaiger', 'ooo', 'wizard'], 'san') workaroundQueryAndWait(allHeroesSeriesOnly) compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'chi') // Add a document with mixed custom fields tokusatsu.putDoc(tokusatsuDocumentDino.contents, tokusatsuDocumentDino.docId) workaroundQueryAndWait(allHeroesWithType) compare(allHeroesWithType.documents, ['dino', 'gokaiger', 'ooo', 'wizard'], 'go') compare(allHeroesWithType.documents, allHeroesSeriesOnly.documents, 'roku') } } } u1db-qt-0.1.5+14.04.20140313/tests/strict-qmltestrunner.sh0000755000015201777760000000150212310435504023311 0ustar pbusernogroup00000000000000#!/usr/bin/env sh #!/usr/bin/env sh # # Copyright 2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ################################################################################ qmltestrunner $* || exit 1 test 0 -eq $(grep -c qwarn plugintest-xunit.xml) || exit 1 u1db-qt-0.1.5+14.04.20140313/tests/test-database.cpp0000644000015201777760000000316412310435504021754 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include "database.h" #include "index.h" #include "query.h" QT_USE_NAMESPACE_U1DB class U1DBDatabaseTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { } void synchronizedTest() { while(false) qApp->processEvents(); Database db; QCOMPARE(db.getPath(), QString("")); QSignalSpy modelReset(&db, SIGNAL(pathChanged(const QString&))); QTemporaryFile file; db.setPath(file.fileName()); QCOMPARE(db.getPath(), file.fileName()); Index index; index.setDatabase(&db); index.setName("py-phone-number"); index.setExpression(QStringList("managers.phone_number")); Query query; query.setIndex(&index); query.setQuery("*"); } void cleanupTestCase() { } }; QTEST_MAIN(U1DBDatabaseTest) #include "test-database.moc" u1db-qt-0.1.5+14.04.20140313/tests/checklicense.sh0000755000015201777760000000241512310435504021504 0ustar pbusernogroup00000000000000#!/usr/bin/env sh # # Copyright 2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ################################################################################ PATTERN='\.(c(c|pp|xx)?|h(h|pp|xx)?|p(l|m)|php|py(|x)|java|js|css|vala|qml|sh)$' SKIP='(_build|CMakeFiles|Canonical|GENERATED FILE|Yahoo! Inc. All rights reserved)' COMMAND="licensecheck --noconf -r * --copyright -c $PATTERN" echo Executing $COMMAND RESULTS=$($COMMAND) test $? = 0 || exit 1 ERRORS=$(echo "$RESULTS" | egrep -v "$SKIP" | grep '*No copyright*') COUNT=$(echo "$ERRORS" | sed 's/^ *//g' | wc -l) if [ "$ERRORS" = "" ]; then echo No license problems found. exit 0 else echo Found $COUNT license problems: echo "$ERRORS" exit 1 fi u1db-qt-0.1.5+14.04.20140313/tests/CMakeLists.txt0000644000015201777760000000370712310435504021272 0ustar pbusernogroup00000000000000add_custom_target(check COMMAND "env" "CTEST_OUTPUT_ON_FAILURE=1" "${CMAKE_CTEST_COMMAND}") find_package(Qt5Test REQUIRED) add_test(NAME plugintest COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/strict-qmltestrunner.sh" "-import" "../modules" "-o" "plugintest-xunit.xml,xunitxml" "-o" "-,txt" "-input" "${CMAKE_CURRENT_SOURCE_DIR}") include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${Qt5Test_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src ${Qt5Sql_INCLUDE_DIRS} ${Qt5Quick_INCLUDE_DIRS} ) add_executable(test-database test-database.cpp) target_link_libraries(test-database ${Qt5Test_LIBRARIES} ${Qt5Quick_LIBRARIES} ${Qt5Sql_LIBRARIES} ${U1DB_QT_LIBNAME} ) set_target_properties(test-database PROPERTIES COMPILE_FLAGS -fPIC) add_test(NAME test-database COMMAND "dbus-test-runner" "--task" "${CMAKE_CURRENT_BINARY_DIR}/test-database" "-p" "-xunitxml" "-p" "-o" "-p" "test-database-xunit.xml") set_property(TEST test-database PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=.") set_property(TEST test-database PROPERTY ENVIRONMENT "QT_QPA_PLATFORM=minimal") add_dependencies(check test-database) option(BUILD_PYTHON "Build Python wrapper" OFF) if (BUILD_PYTHON) find_package(PythonLibs) if (PYTHONLIBS_FOUND) set(U1DB_QT_PY_SRCS qt-backend-wrapper.cpp) include_directories( ${PYTHON_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS} ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=default -Wall -Wundef -std=c++0x") add_library(u1dbqt SHARED ${U1DB_QT_PY_SRCS}) target_link_libraries(u1dbqt ${Qt5Core_LIBRARIES} ${Qt5Sql_LIBRARIES} ${U1DB_QT_LIBNAME} ${PYTHON_LDFLAGS} ) add_custom_command(TARGET u1dbqt POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink libu1dbqt.so u1dbqt.so) endif () endif () add_test(NAME licensetest COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/checklicense.sh" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") u1db-qt-0.1.5+14.04.20140313/tests/test-upstream.py0000755000015201777760000000607212310435504021722 0ustar pbusernogroup00000000000000#!/usr/bin/env python # Copyright 2013 Canonical Ltd. # # This file is part of u1db. # # u1db is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation. # # u1db is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with u1db. If not, see . import os, sys, unittest try: from testtools import run import testscenarios except: print('Required modules: python-testtools python-testscenarios') sys.exit(1) try: import u1dbqt except Exception: msg = sys.exc_info()[1] # Python 2/3 compatibility print(msg) print('u1dbqt Python wrapper not built? Did you run "make"?') sys.exit(1) try: import u1db # Database, SyncTarget, Document, errors import u1db.tests from u1db.tests import test_backends except: print('python-u1db required, with tests support') print('An easy way of doing that is getting lp:u1db and setting PYTHONPATH=/path/to/u1db') sys.exit(1) # u1db-qt specific test cases class TestQt(u1db.tests.DatabaseBaseTests): def setUp(self): super(TestQt, self).setUp() def test_sanity(self): self.assertTrue('Qt' in str(self)) # wrap Qt code in Python classes (via ui1dbqt Python module) # cf. http://bazaar.launchpad.net/~pedronis/u1db/u1db-js/view/head:/tests/bridged.py class QtDatabase(u1dbqt.Database): def __init__(self, replica_uid): u1dbqt.Database.__init__(self, replica_uid) def create_doc_from_json(self, json, doc_id=None): # FIXME create in db doc = u1db.Document(doc_id=doc_id) doc.set_json(json) return doc def put_doc (self, doc): u1dbqt.Database.put_doc(self, contents=doc.get_json(), doc_id=doc.doc_id) def delete_doc (self, doc): # Deleted means empty contents in the database self.put_doc(contents=None, doc_id=doc.doc_id) def get_docs (self): pass # TODO def get_all_docs (self): pass # TODO def close (self): pass # TODO # TODO complete wrappers def make_database(test, replica_uid): db = QtDatabase(replica_uid) def cleanup(): pass # FIXME delete old databases test.addCleanup(cleanup) return db # upstream Python test cases # cf http://bazaar.launchpad.net/~pedronis/u1db/u1db-js/view/head:/tests/test_bridged.py SCENARIOS = [("jsbridged", { "make_database_for_test": make_database, })] class BridgedAllDatabaseTests(test_backends.AllDatabaseTests): scenarios = SCENARIOS # TODO enable more cases load_tests = u1db.tests.load_with_scenarios if __name__ == '__main__': loader = unittest.TestLoader() suite = load_tests (loader, loader.loadTestsFromName(__name__), '*') unittest.TextTestRunner(verbosity=2).run(suite) u1db-qt-0.1.5+14.04.20140313/tests/xvfb.sh0000644000015201777760000000201512310435504020022 0ustar pbusernogroup00000000000000#!/bin/sh # # Copyright 2013-2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # # Author: Christian Dywan echo Running $@ in virtual frame buffer... xvfb-run -a -s "-screen 0 1280x1024x24" -e xvfb.err "$@" 2>test.err RETVAL=$? if [ $RETVAL -eq 0 ]; then echo $@ finished successfully... else echo $@ in virtual frame buffer failed... cat test.err >&2 echo Tail of xvfb-run output: tail xvfb.err >&2 exit $RETVAL fi u1db-qt-0.1.5+14.04.20140313/tests/tst_database.qml0000644000015201777760000001200412310435504021671 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import QtTest 1.0 import U1db 1.0 as U1db Item { width: 200; height: 200 U1db.Database { id: myDatabase path: "aDatabaseB" property bool first_row_loaded: false property bool last_row_loaded: false onDocLoaded: { if (path == 'aDatabaseC' && docId == 'dl0') first_row_loaded = true if (path == 'aDatabaseC' && docId == 'dl99') last_row_loaded = true } } U1db.Document { id: myDocument database: myDatabase docId: 'qwertzui' defaults: { "eggs": "spam" } } U1db.Document { id: otherDocument database: myDatabase docId: 'shallow' create: true defaults: { "eggs": "spam" } } ListView { id: myList model: myDatabase width: 200; height: 200 delegate: Text { x: 66; y: 77 text: "otherDelegate index:%1".arg(index) } } TestCase { name: "U1dbDatabase" when: windowShown function test_0_documentCreate () { compare(myDatabase.getDoc(otherDocument.docId), otherDocument.defaults) } function test_1_databasePopulated () { spyListCompleted.wait() compare(myDatabase.putDoc({"animals": ["cat", "dog", "hamster"]}) == '', false) var myPath = "aDatabaseA" myDatabase.path = myPath spyPathChanged.wait() compare(myDatabase.path, myPath) compare(myDatabase.putDoc({"spam": "eggs"}) == '', false) var json = {"foo": "bar"} compare(myDatabase.putDoc(json, "hijklmn") == '', false) compare(myDatabase.getDoc("hijklmn"), json) compare(myDatabase.getDoc("hijklmn"), json) } function test_2_databaseError () { /* FIXME: unicode in Qt console output doesn't work inside dpkg ignoreWarning('u1db: Invalid docID 日本語') myDatabase.putDoc({"": ""}, "日本語") spyErrorChanged.wait() compare(myDatabase.error.indexOf("Invalid docID") > -1, true) */ } function test_3_documentContents () { var json = {"content": {"notetext": "Lorem ipsum"}} myDatabase.putDoc(json, "qwertzui") myDocument.docId = '' compare(myDocument.contents, undefined) myDocument.docId = 'qwertzui' compare(myDocument.contents, json) compare(myDocument.contents.content.notetext, 'Lorem ipsum') var path = myDatabase.path myDatabase.path = ':memory:' myDatabase.path = path spyContentsChanged.wait() } function test_4_putIndex () { myDatabase.putIndex("by-phone-number", ["managers.phone_number"]) compare(myDatabase.getIndexExpressions('by-phone-number'), ["managers.phone_number"]) myDatabase.putDoc({ 'managers': [ { 'name': 'Mary', 'phone_number': '12345' }, { 'name': 'Rob', 'phone_number': '54321' }, ] }) // FIXME compare(myDatabase.getIndexKeys('by-phone-number'), ['12345', '54321']) } function test_6_fillDocument () { var path = "aDatabaseC" myDatabase.path = path spyPathChanged.wait() for (var i = 0; i < 100; i++) myDatabase.putDoc({'foo': 'bar'} ,'dl' + Number(i).toLocaleString()) myDatabase.path = ":memory:" spyPathChanged.wait() compare(myDatabase.listDocs(), []) compare(myList.count, 0) myDatabase.first_row_loaded = false myDatabase.last_row_loaded = false myDatabase.path = path spyPathChanged.wait() compare(myList.count, 100) spyDocLoaded.wait() // FIXME compare(myDatabase.first_row_loaded, true) // FIXME compare(myDatabase.last_row_loaded, false) } SignalSpy { id: spyPathChanged target: myDatabase signalName: "pathChanged" } SignalSpy { id: spyContentsChanged target: myDocument signalName: "contentsChanged" } SignalSpy { id: spyErrorChanged target: myDatabase signalName: "errorChanged" } SignalSpy { id: spyListCompleted target: myDatabase.Component signalName: "completed" } SignalSpy { id: spyDocLoaded target: myDatabase signalName: "docLoaded" } } } u1db-qt-0.1.5+14.04.20140313/tests/qt-backend-wrapper.cpp0000644000015201777760000001163012310435504022717 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include "database.h" #include QT_USE_NAMESPACE_U1DB static PyObject *Module_hello(PyObject *self, PyObject *args) { return PyString_FromString("world"); } static void u1dbqt_database_destructor(PyObject* capsule) { Database* db = static_cast(PyCapsule_GetPointer(capsule, NULL)); delete db; } static void* u1dbqt_get_ptr(PyObject *self, char* name) { PyObject* capsule = PyObject_GetAttrString(self, "qt_db"); assert(capsule != 0); void* ptr = PyCapsule_GetPointer(capsule, name); assert(ptr != 0); return ptr; } static void u1dbqt_set_ptr (PyObject *self, char* name, void* ptr, PyCapsule_Destructor destr) { // qDebug() << "set_ptr" << PyObject_Str(self) << name; PyObject* capsule = PyCapsule_New(ptr, name, destr); assert(capsule != 0); assert (PyObject_SetAttrString(self, "qt_db", capsule) != -1); } static PyObject *Database_init(PyObject *self, PyObject *args, PyObject *kwd) { char *path = ":memory:"; static char *kwlist[] = {"self", "path", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwd, "Os", kwlist, &self, &path)) return NULL; Database* db = new Database(); db->setPath(path); u1dbqt_set_ptr(self, "u1dbqt.db", db, u1dbqt_database_destructor); // qDebug() << "__init__" << db; Py_INCREF(Py_None); return Py_None; } static PyObject *Database_put_doc(PyObject *self, PyObject *args, PyObject* kwd) { char *contents = NULL; char *docId = NULL; static char *kwlist[] = {"self", "contents", "doc_id", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwd, "Ozz", kwlist, &self, &contents, &docId)) return NULL; Database* db = static_cast(u1dbqt_get_ptr(self, "u1dbqt.db")); if (db->putDoc(QVariant())) { char* lastError = (char*)(db->lastError().data()); if (strstr (lastError, "Invalid docID")) PyErr_SetString(PyErr_NewException("errors.InvalidDocId", NULL, NULL), lastError); else PyErr_SetString(PyErr_NewException("u1dbqt.QtDatabaseError", NULL, NULL), lastError); return NULL; } Py_INCREF(Py_None); return Py_None; } static PyObject *Database_get_doc(PyObject *self, PyObject *args, PyObject* kwd) { char *docId = NULL; static char *kwlist[] = {"self", "doc_id", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwd, "Oz", kwlist, &self, &docId)) return NULL; Database* db = static_cast(u1dbqt_get_ptr(self, "u1dbqt.db")); QVariant doc(db->getDoc(QString(docId))); /* if (!doc.isValid()) { const char* lastError = db->lastError().data(); PyErr_SetString(PyExc_ValueError, lastError); return NULL; } */ Py_INCREF(Py_None); return Py_None; } static PyObject *Database_repr(PyObject *self, PyObject *args) { return PyString_FromString("u1dbqt.Database"); } static PyMethodDef ModuleMethods[] = { {"hello", Module_hello, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} }; static PyMethodDef DatabaseMethods[] = { {"__init__", (PyCFunction)Database_init, METH_VARARGS | METH_KEYWORDS, ""}, {"put_doc", (PyCFunction)Database_put_doc, METH_VARARGS | METH_KEYWORDS, ""}, {"get_doc", (PyCFunction)Database_get_doc, METH_VARARGS | METH_KEYWORDS, ""}, {"__repr__", Database_repr, METH_VARARGS, ""}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initu1dbqt(void) { PyObject *module = Py_InitModule("u1dbqt", ModuleMethods); PyObject *moduleDict = PyModule_GetDict(module); PyObject *classDict = PyDict_New(); PyObject *className = PyString_FromString("Database"); PyObject *classInstance = PyClass_New(NULL, classDict, className); PyDict_SetItemString(moduleDict, "Database", classInstance); Py_DECREF(classDict); Py_DECREF(className); Py_DECREF(classInstance); PyMethodDef *def; for (def = DatabaseMethods; def->ml_name != NULL; def++) { PyObject *func = PyCFunction_New(def, NULL); PyObject *method = PyMethod_New(func, NULL, classInstance); PyDict_SetItemString(classDict, def->ml_name, method); Py_DECREF(func); Py_DECREF(method); } } u1db-qt-0.1.5+14.04.20140313/qtcreator/0000755000015201777760000000000012310436007017364 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/qtcreator/CMakeLists.txt0000644000015201777760000000032712310435504022127 0ustar pbusernogroup00000000000000set(U1DB_WIZARDS "${CMAKE_INSTALL_PREFIX}/share/qtcreator/templates/qml") install(DIRECTORY "settings" DESTINATION "${U1DB_WIZARDS}" ) install(DIRECTORY "contacts" DESTINATION "${U1DB_WIZARDS}" ) u1db-qt-0.1.5+14.04.20140313/qtcreator/contacts/0000755000015201777760000000000012310436007021202 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/qtcreator/contacts/template.xml0000644000015201777760000000103612310435504023540 0ustar pbusernogroup00000000000000 u1db-qt-0.1.5+14.04.20140313/qtcreator/contacts/main.qml0000644000015201777760000000456012310435504022647 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 import Ubuntu.Components.ListItems 0.1 as ListItem MainView { width: units.gu(45) height: units.gu(80) U1db.Database { id: contactsDatabase /* Uncomment to persistently store contacts on disk path: "contacts.db" */ } U1db.Document { database: contactsDatabase docId: 'person0' create: true defaults: { 'name': 'John', 'city': 'Dublin', 'phone': 65849 } } U1db.Document { database: contactsDatabase docId: 'person1' create: true defaults: { 'name': 'Ivanka', 'city': 'Dublin', 'phone': 98765 } } U1db.Document { database: contactsDatabase docId: 'person2' create: true defaults: { 'name': 'Leonardo', 'city': 'Rome', 'phone': 12345 } } U1db.Index { database: contactsDatabase id: byCityName expression: [ "city" ] } U1db.Query { id: numberOne index: byCityName query: [ "Dublin" ] } Page { Tabs { Tab { title: i18n.tr("People living in Dublin") page: Page { anchors.centerIn: parent ListView { width: units.gu(45) height: units.gu(80) model: numberOne delegate: ListItem.Subtitled { text: contents.name subText: contents.city } } } } } } } u1db-qt-0.1.5+14.04.20140313/qtcreator/settings/0000755000015201777760000000000012310436007021224 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/qtcreator/settings/template.xml0000644000015201777760000000104612310435504023563 0ustar pbusernogroup00000000000000 u1db-qt-0.1.5+14.04.20140313/qtcreator/settings/main.qml0000644000015201777760000000575512310435504022700 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 MainView { width: units.gu(45) height: units.gu(80) U1db.Database { id: settingsDatabase /* Uncomment to persistently store settings on disk path: "settings.db" */ } U1db.Document { id: settingsDocument database: settingsDatabase docId: 'settings' create: true defaults: { "sound": true, "music": false, "username": "Joe User" } } Page { Tabs { Tab { title: i18n.tr("Game Settings") page: Page { anchors.centerIn: parent Column { spacing: units.gu(2) Row { spacing: units.gu(2) Label { text: i18n.tr("Username") width: units.gu(19) anchors.verticalCenter: parent.verticalCenter } TextField { placeholderText: settingsDocument.contents.username } } Row { spacing: units.gu(2) Label { text: i18n.tr("Sound") width: units.gu(19) anchors.verticalCenter: parent.verticalCenter } Switch { checked: settingsDocument.contents.sound } } Row { spacing: units.gu(2) Label { text: i18n.tr("Music") width: units.gu(19) anchors.verticalCenter: parent.verticalCenter } Switch { checked: settingsDocument.contents.music } } } } } } } } u1db-qt-0.1.5+14.04.20140313/CMakeLists.txt0000644000015201777760000000250012310435504020116 0ustar pbusernogroup00000000000000project(u1db-qt) cmake_minimum_required(VERSION 2.8.6) # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) # Dependencies include(FindPkgConfig) include(GNUInstallDirs) find_package(Qt5Core REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5Sql REQUIRED) add_definitions(-DWITHQT5=1) set(U1DB_QT_LIBNAME u1db-qt5) set(QT_PKGCONFIG_DEPENDENCIES "Qt5Core Qt5Network Qt5Quick Qt5Sql") set(QT_U1DB_PKGCONFIG_FILE lib${U1DB_QT_LIBNAME}.pc) # Build flags set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -Wall -Wundef -Werror -std=c++0x") add_definitions(-DQT_NO_KEYWORDS) # Disable building during install to avoid file permission chaos set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY 1) add_subdirectory(src) enable_testing() add_subdirectory(tests) add_subdirectory(modules) add_subdirectory(examples) add_subdirectory(gallery) add_subdirectory(documentation) add_subdirectory(qtcreator) # PkgConfig file set (PREFIX "${CMAKE_INSTALL_PREFIX}") set (EXEC_PREFIX "${CMAKE_INSTALL_PREFIX}") set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}") configure_file (libu1db-qt.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${QT_U1DB_PKGCONFIG_FILE} @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${QT_U1DB_PKGCONFIG_FILE} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) u1db-qt-0.1.5+14.04.20140313/libu1db-qt.pc.in0000644000015201777760000000045312310435504020260 0ustar pbusernogroup00000000000000prefix=@PREFIX@ exec_prefix=@EXEC_PREFIX@ libdir=${exec_prefix}/lib includedir=${prefix}/@INCLUDE_INSTALL_DIR@ Name: @U1DB_QT_LIBNAME@ Description: Qt binding and QML plugin for U1DB. Version: 1.0 Requires: @QT_PKGCONFIG_DEPENDENCIES@ Libs: -L${libdir} -l@U1DB_QT_LIBNAME@ Cflags: -I${includedir} u1db-qt-0.1.5+14.04.20140313/gallery/0000755000015201777760000000000012310436007017017 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/gallery/gallery.qml0000644000015201777760000000243512310435504021176 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 0.1 MainView { width: units.gu(60) height: units.gu(80) Page { Tabs { /* FIXME: lp#1167568 Repeater { model: ["1", "2", "2b", "3", "4", "5"] SplitView { example: modelData } } */ SplitView { example: "1" } SplitView { example: "2" } SplitView { example: "2b" } SplitView { example: "3" } SplitView { example: "4" } SplitView { example: "5" } } } } u1db-qt-0.1.5+14.04.20140313/gallery/CMakeLists.txt0000644000015201777760000000032512310435504021560 0ustar pbusernogroup00000000000000install(FILES gallery.qml SplitView.qml DESTINATION ${CMAKE_INSTALL_PREFIX}/share/u1db-qt/gallery ) install(FILES u1db-qt-gallery.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications ) u1db-qt-0.1.5+14.04.20140313/gallery/u1db-qt-gallery.desktop0000644000015201777760000000036412310435504023330 0ustar pbusernogroup00000000000000[Desktop Entry] Encoding=UTF-8 Type=Application Exec=qmlscene /usr/share/u1db-qt/gallery/gallery.qml Path=/usr/share/u1db-qt/gallery Name=U1Db QML Example Gallery Icon=/usr/share/icons/unity-icon-theme/places/svg/service-u1.svg Terminal=false u1db-qt-0.1.5+14.04.20140313/gallery/SplitView.qml0000644000015201777760000000366612310435504021474 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 import QtWebKit 3.0 import QtWebKit.experimental 1.0 Tab { id: exampleView property string example title: "Example %1".arg(example) Row { id: splitView property string qml: Qt.resolvedUrl('../examples/u1db-qt-example-%1.qml'.arg(example)) property string html: 'file:////usr/share/u1db-qt/examples/u1db-qt-example-%1.html'.arg(example) anchors.fill: parent Loader { width: parent.width / 3 source: splitView.qml asynchronous: true } // TODO: syntax highlighting // FIXME: switching tabs with web views may crash lp#1124065 WebView { width: parent.width / 3 height: parent.height url: splitView.qml // FIXME: default font size is extremely small lp#1169989 experimental.preferences.minimumFontSize: units.dp(24) } WebView { width: parent.width / 3 height: parent.height url: splitView.html experimental.preferences.minimumFontSize: units.dp(24) // TODO: open help browser onNavigationRequested: { url } } } } u1db-qt-0.1.5+14.04.20140313/modules/0000755000015201777760000000000012310436007017030 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/modules/U1db/0000755000015201777760000000000012310436007017623 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/modules/U1db/plugin.h0000644000015201777760000000175512310435504021303 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef U1DB_PLUGIN_H #define U1DB_PLUGIN_H #include class U1DBPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: void registerTypes(const char *uri); }; #endif // U1DB_PLUGIN_H u1db-qt-0.1.5+14.04.20140313/modules/U1db/CMakeLists.txt0000644000015201777760000000311012310435504022357 0ustar pbusernogroup00000000000000find_package(Qt5Quick REQUIRED) find_package(Qt5Network REQUIRED) get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION) # See http://doc-snapshot.qt-project.org/5.0/qtcore/qlibraryinfo.html#LibraryLocation-enum # exec_program(${QMAKE_EXECUTABLE} ARGS "-query QT_INSTALL_QML" OUTPUT_VARIABLE QT_IMPORTS_DIR) exec_program(${QMAKE_EXECUTABLE} ARGS "-query QT_INSTALL_QML" OUTPUT_VARIABLE QT_IMPORTS_DIR) file(TO_CMAKE_PATH "${QT_IMPORTS_DIR}" QT_IMPORTS_DIR) set(U1DBPlugin_SRCS plugin.cpp ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${Qt5Sql_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ) add_library(U1DBPlugin SHARED ${U1DBPlugin_SRCS}) add_dependencies(check U1DBPlugin) target_link_libraries(U1DBPlugin ${U1DB_QT_LIBNAME} ${Qt5Quick_LIBRARIES} ${Qt5Network_LIBRARIES} ) include_directories( ${CMAKE_SOURCE_DIR}/src ${U1DB_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} ${Qt5Quick_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ) # copy qmldir file into build directory for shadow builds file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" DESTINATION ${CMAKE_CURRENT_BINARY_DIR} ) install(TARGETS U1DBPlugin LIBRARY DESTINATION ${QT_IMPORTS_DIR}/U1db ) install(FILES qmldir DESTINATION ${QT_IMPORTS_DIR}/U1db ) add_custom_command( TARGET U1DBPlugin POST_BUILD COMMAND "qmlplugindump" "U1db" "1.0" "${CMAKE_BINARY_DIR}/modules" ">" "plugins.qmltypes" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plugins.qmltypes DESTINATION ${QT_IMPORTS_DIR}/U1db ) u1db-qt-0.1.5+14.04.20140313/modules/U1db/qmldir0000644000015201777760000000003612310435504021036 0ustar pbusernogroup00000000000000module U1db plugin U1DBPlugin u1db-qt-0.1.5+14.04.20140313/modules/U1db/plugin.cpp0000644000015201777760000000227012310435504021627 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "database.h" #include "document.h" #include "index.h" #include "query.h" #include "synchronizer.h" #include "plugin.h" #include QT_USE_NAMESPACE_U1DB void U1DBPlugin::registerTypes(const char *uri) { qmlRegisterType(uri, 1, 0, "Database"); qmlRegisterType(uri, 1, 0, "Document"); qmlRegisterType(uri, 1, 0, "Index"); qmlRegisterType(uri, 1, 0, "Query"); qmlRegisterType(uri, 1, 0, "Synchronizer"); } u1db-qt-0.1.5+14.04.20140313/modules/CMakeLists.txt0000644000015201777760000000002712310435504021570 0ustar pbusernogroup00000000000000add_subdirectory(U1db) u1db-qt-0.1.5+14.04.20140313/examples/0000755000015201777760000000000012310436007017176 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-5/0000755000015201777760000000000012310436007022246 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-5/u1db-qt-example-5.qml0000644000015201777760000000543312310435504026037 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 MainView { width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aDatabase5" } U1db.Document { database: aDatabase docId: 'helloworld1' create: true defaults:{"hello": { "world": { "message":"Hello World", "id": 1 } } } } U1db.Document { database: aDatabase docId: 'helloworld2' create: true defaults:{"hello": { "world": [ { "message":"Hello World", "id": 2 }, { "message":"Hello World", "id": 2.5 } ] } } } U1db.Document { database: aDatabase docId: 'helloworld3' contents:{"hello": { "world": [ { "message":"Hello World", "id": 3 }, { "message":"Hello World", "id": 3.33 }, { "message":"Hello World", "id": 3.66 } ] } } } U1db.Document { database: aDatabase docId: 'helloworld4' defaults:{"hello": { "world": { "message":"Hello World", "id": 4 } } } } U1db.Index{ database: aDatabase id: by_helloworld expression: ["hello.world.id","hello.world.message"] } U1db.Query{ id: aQuery index: by_helloworld query: [{"id":"*"},{"message":"Hel*"}] } Tabs { id: tabs Tab { title: i18n.tr("Hello U1Db!") page: Page { id: helloPage ListView { width: units.gu(45) height: units.gu(80) model: aQuery delegate: Text { text: "(" + index + ") '" + contents.message + " " + contents.id + "'" } } } } } } u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-5/u1db-qt-example-5.qdoc0000644000015201777760000003267112310435504026200 0ustar pbusernogroup00000000000000/*! \page u1db-qt-tutorial-5.html \title U1Db-Qt Index Tutorial This tutorial is designed to demonstrate a variety of essential U1Db-Qt functionality and usage, including: \list 1 \li Utilizing the U1db-Qt Index element \li Various approaches to define U1db-Qt Document elements when using the Index element \li Partnering the U1db-Qt Index element and a QML ListView element \endlist \section1 Storing Data \section2 The Database Element \section3 Creating a Database 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. \code U1db.Database { id: aDatabase path: "aDatabase4" } \endcode \section1 The Document Element \section2 Declaring Documents (at Runtime) A Document can be instantiated at runtime, or generated dynamically. The examples below demonstrate the former. 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. This example of a very simple Document will not initially do anything, until more properties are added and defined: \code U1db.Document { id: aDocument1 docId: 'helloworld1' } \endcode 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. 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: \code U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld1' } \endcode 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: \code U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld1' create: true } \endcode 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: \code U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld1' create: true defaults:{"hello": { "world": { "message":"Hello World", "id": 1 } } } } \endcode 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. \code U1db.Document { id: aDocument2 database: aDatabase docId: 'helloworld2' create: true defaults:{"hello": { "world": [ { "message":"Hello World", "id": 2 }, { "message":"Hello World", "id": 2.5 } ] } } } \endcode 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): \code {"hello": { "world": [ { "message":"Hello World", "id": 2 }, { "message":"Hello World", "id": 2.5 } ] } } \endcode 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. 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. \code U1db.Document { id: aDocument3 database: aDatabase docId: 'helloworld3' contents:{"hello": { "world": [ { "message":"Hello World", "id": 3 }, { "message":"Hello World", "id": 3.33 }, { "message":"Hello World", "id": 3.66 } ] } } } \endcode 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: \code U1db.Document { id: aDocument3 database: aDatabase docId: 'helloworld3' create: true default:{"hello": { "world": [{ "message":"Hello World", "id": 3 }] } } contents:{"hello": { "world": [ { "message":"Hello World", "id": 3 }, { "message":"Hello World", "id": 3.33 }, { "message":"Hello World", "id": 3.66 } ] } } } \endcode 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. \code U1db.Document { id: aDocument4 database: aDatabase docId: 'helloworld4' defaults:{"hello": { "world": { "message":"Hello World", "id": 4 } } } } \endcode \section3 Samples of Stored Documents The data stored in the database after defining the above Document elements (and then running the application, will consist of the following: \table \header \li docId \li content \row \li 'helloworld1' \li \code { "hello": { "world": { "id": 1, "message": "Hello World" } } } \endcode \row \li 'helloworld2' \li \code { "hello": { "world": [ { "id": 2, "message": "Hello World" }, { "id": 2.5, "message": "Hello World" } ] } } \endcode \row \li 'helloworld3' \li \code { "hello": { "world": [ { "id": 3, "message": "Hello World" }, { "id": 3.33, "message": "Hello World" }, { "id": 3.66, "message": "Hello World" } ] } } \endcode \endtable \section1 Retrieving Data To retrieve the Documents that were declared earlier requires two additional elements: Index and Query. \section2 The Index Element \section3 Creating and Index Element The Index element requires both a unique 'id' and a pointer to a 'database' in order to begin becoming useful, as demonstrated here: \code U1db.Index{ database: aDatabase id: by_helloworld } \endcode 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: \code U1db.Index{ database: aDatabase id: by_helloworld //name: "by-helloworld" } \endcode 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): \code U1db.Index{ database: aDatabase id: by_helloworld //name: "by-helloworld" expression: ["hello.world.id","hello.world.message"] } \endcode \section2 The QueryElement \section3 Creating a Query Element 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). 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: \code U1db.Query{ id: aQuery index: by_helloworld } \endcode 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): \code U1db.Query{ id: aQuery index: by_helloworld //query: "*" } \endcode When the 'query' property becomes available, only wildcard search definitions for "starts with" will be suppoprted. Thus the following would be supported: \code U1db.Query{ id: aQuery index: by_helloworld //query: "Hello*" } \endcode But this would not: \code U1db.Query{ id: aQuery index: by_helloworld //query: "*World" } \endcode Note: again, the 'query' property is commented out in the above two snippets due to its current status \section1 Using Data \section2 Data and the Application UI \section3 Using Data With Models and Views 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: \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery } \endcode \section4 Data and Delegates 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: \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery delegate: Text { x: 66; y: 77 } } \endcode 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). 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). 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): \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery delegate: Text { x: 66; y: 77 text: index } } \endcode 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" \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery delegate: Text { x: 66; y: 77 text: "(" + index + ") '" + contents.message + " " + contents.id + "'" } } \endcode */ u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-3/0000755000015201777760000000000012310436007022244 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-3/u1db-qt-example-3.qml0000644000015201777760000003415212310435504026033 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 /*! This example and tutorial is designed to show a wide variety of U1Db-Qt functionality and usage. The example demonstrates: \list 1 \li Combining U1Db-Qt with elements and components that do not utilize models \li Blending the U1Db-Qt plugin with QML and Javascript \endlist */ MainView { width: units.gu(45) height: units.gu(80) /*! 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. U1db.Database { id: aDatabase path: "aDatabase3" } */ U1db.Database { id: aDatabase path: "aDatabase3" } /*! A Document can be declared at runtime. It requires at the very least a unique 'docId', but that alone won't do anything special. The snipet below snippet demonstrates the basic requirements. In addition to this, this example displays text from the database for a specific docId and id key in a text area called 'documentContent. To update the text area at startup with either the default value or a value from the database the onCompleted function is utilized, which is also demonstrated below. U1db.Document { id: aDocument database: aDatabase docId: 'helloworld' create: true defaults: { "helloworld":"Hello World" } Component.onCompleted: { documentContent.text = aDocument.contents.helloworld } } */ U1db.Document { id: aDocument database: aDatabase docId: 'helloworld' create: true defaults: { "helloworld":"Hello World" } Component.onCompleted: { documentContent.text = aDocument.contents.helloworld } } function switchToPreviousDocument(documentObject){ aDocument.docId = getPreviousDocumentId(documentObject) } function switchToNextDocument(){ aDocument.docId = getNextDocumentId(aDocument) } function getPreviousDocumentId(documentObject){ if(typeof documentObject!='undefined'){ /*! The listDocs method retrieves all the docId values from the current database. In this demonstration the values are put into an array, which is then checked to locate the docId for the current and previous documents within the database. var documentIds = {} documentIds = documentObject.database.listDocs() for(var i = 0; i < documentIds.length; i++){ if(documentIds[i]===documentObject.docId && i > 0){ return documentIds[i-1] } else if(documentIds[i]===documentObject.docId && i==0){ return documentIds[documentIds.length-1] } } */ var documentIds = {} documentIds = documentObject.database.listDocs() for(var i = 0; i < documentIds.length; i++){ if(documentIds[i]===documentObject.docId && i > 0){ return documentIds[i-1] } else if(documentIds[i]===documentObject.docId && i==0){ return documentIds[documentIds.length-1] } } return documentIds[0] } else{ print("Error!") return '' } } function getNextDocumentId(documentObject){ if(typeof documentObject!='undefined'){ var documentIds = documentObject.database.listDocs() for(var i = 0; i < documentIds.length; i++){ if(documentIds[i]===documentObject.docId && i < (documentIds.length-1)){ return documentIds[i+1] } else if(documentIds[i]===documentObject.docId && i==(documentIds.length-1)){ return documentIds[0] } } return documentIds[0] } else{ print("Error!") return '' } } function getCurrentDocumentKey(contentsObject){ if(typeof contentsObject!='undefined'){ var keys = Object.keys(contentsObject); return keys[0] } else{ return '' } } function updateContentWindow(documentText, addressBarText) { // Somewhere below need to check for things like invalid docId if(documentText!==addressBarText) { /*! These steps demonstrate the creation of a temporary document, based on a copy of the global document. This will then be used to determine if there is already a document in the database with the same docId as the address bar, and additionally with a key id with the same name. var tempDocument = {} var tempFieldName = addressBarText; var tempContents = {}; tempDocument = aDocument tempDocument.docId = addressBarText; tempContents = tempDocument.contents NOTE: For simplicity sake this example sometimes uses the same value for both the docId and the key id, as seen here. Real life implimentations can and will differ, and this will be demonstrated elsewhere in the example code. */ var tempDocument = {} var tempFieldName = addressBarText; var tempContents = {}; tempDocument = aDocument tempDocument.docId = addressBarText; tempContents = tempDocument.contents if(typeof tempContents !='undefined' && typeof tempContents[tempFieldName]!='undefined') { aDocument = tempDocument documentContent.text = tempContents[tempFieldName] } else { /*! Here the contents of the temporary document are modified, which then replaces the global document. documentContent.text = 'More Hello World...'; tempContents = {} tempContents[tempFieldName] = documentContent.text tempDocument.contents = tempContents aDocument = tempDocument */ documentContent.text = 'More Hello World...'; tempContents = {} tempContents[tempFieldName] = documentContent.text tempDocument.contents = tempContents aDocument = tempDocument } } else { /*! In this instance the current document's content is updated from the text view. The unique key and docId are not modified because the database already contains a record with those properties. tempContents = {} tempFieldName = getCurrentDocumentKey(aDocument.contents) tempContents[tempFieldName] = documentContent.text aDocument.contents = tempContents */ tempContents = {} tempFieldName = getCurrentDocumentKey(aDocument.contents) tempContents[tempFieldName] = documentContent.text aDocument.contents = tempContents } } Tabs { id: tabs Tab { title: i18n.tr("Hello U1Db!") page: Page { id: helloPage /*! Here a rectangle is defined that represents the lower portion of our application. It will contain all the main parts of the application. Rectangle { width: units.gu(45) height: units.gu(70) anchors.bottom: parent.bottom color: "#00FFFFFF" // The remainder of the main part of the application goes here ... } */ Rectangle { width: units.gu(45) height: units.gu(70) anchors.bottom: parent.bottom color: "#00FFFFFF" Rectangle { width: units.gu(45) height: units.gu(60) anchors.bottom: parent.bottom /*! The following TextArea is for displaying contents for the current state of the global document, as defined by the key / name in the address bar. TextArea{ id: documentContent selectByMouse : false x: units.gu(1) y: units.gu(1) width: units.gu(43) height: units.gu(58) color: "#000000" } */ TextArea{ id: documentContent selectByMouse : false x: units.gu(1) y: units.gu(1) width: units.gu(43) height: units.gu(58) color: "#000000" } } // This rectangle contains our navigation controls Rectangle { width: units.gu(43) height: units.gu(5) anchors.top: addressBarArea.bottom x: units.gu(1.5) color: "#00FFFFFF" Row{ width: units.gu(43) height: units.gu(5) anchors.verticalCenter: parent.verticalCenter spacing: units.gu(2) Button { text: "<" onClicked: updateContentWindow(switchToPreviousDocument(aDocument), addressBar.text) } Button { text: "Home" onClicked: updateContentWindow(getCurrentDocumentKey(aDocument.contents),'helloworld') } Button { text: "Save" onClicked: updateContentWindow(getCurrentDocumentKey(aDocument.contents),addressBar.text) } Button { text: ">" onClicked: updateContentWindow(switchToNextDocument(aDocument), addressBar.text) } } } Rectangle { id: addressBarArea width: units.gu(45) height: units.gu(5) anchors.top: parent.top TextField { id: addressBar width: units.gu(43) anchors.verticalCenter: parent.verticalCenter x: units.gu(1) hasClearButton: false /*! There is an object within in the 'aDocument' model defined earlier called 'contents', which contains a key called 'helloworld', which represents a search string. In our example the key will represent the name of a document in the database, which will be displayed in the address bar. Displaying the key is demonstrated here: text: displayKey(aDocument.contents) function displayKey(documentObject){ var keys = Object.keys(documentObject); return keys[0] } */ text: getCurrentDocumentKey(aDocument.contents) onAccepted: { onClicked: updateContentWindow(getCurrentDocumentKey(aDocument.contents),addressBar.text) } } } } } } } } u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-3/u1db-qt-example-3.qdoc0000644000015201777760000000136112310435504026164 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \page u1db-qt-tutorial-3.html */ u1db-qt-0.1.5+14.04.20140313/examples/CMakeLists.txt0000644000015201777760000000020412310435504021733 0ustar pbusernogroup00000000000000file(GLOB ALL_EXAMPLES */*.qml) install(FILES ${ALL_EXAMPLES} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/u1db-qt/examples ) u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2/0000755000015201777760000000000012310436007022243 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2/u1db-qt-example-2.qml0000644000015201777760000000545112310435504026031 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 MainView { id: u1dbView width: units.gu(45) height: units.gu(80) /*! 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. */ U1db.Database { id: aDatabase path: "aDatabase2" } Timer { property int i: 0; interval: 5000; running: true; repeat: true onTriggered: newDocumentObject() function newDocumentObject() { var qmlString = "import QtQuick 2.0; import U1db 1.0 as U1db; U1db.Document {id: aDcoument"+i+";database: aDatabase;docId: 'helloworld"+i+"';create: true; defaults: { 'hello': 'Hello New Document "+i+"!' }}" Qt.createQmlObject(qmlString, u1dbView, "dynamicNewDocument"+i); i = i+1 } } Tabs { id: tabs anchors.fill: parent Tab { title: i18n.tr("Hello U1Db!") page: Page { id: helloPage ListView { width: units.gu(45) height: units.gu(80) /* Here is the reference to the Database model mentioned earlier. */ model: aDatabase /* A delegate will be created for each Document retrieved from the Database */ delegate: Text { x: 66; y: 77 text: { /*! The object called 'contents' contains a string as demonstrated here. In this example 'hello' is our search string. text: contents.hello */ text: contents.hello } } } } } } } u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2/u1db-qt-example-2.qdoc0000644000015201777760000000136112310435504026162 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \page u1db-qt-tutorial-2.html */ u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2b/0000755000015201777760000000000012310436007022405 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2b/u1db-qt-example-2b.qdoc0000644000015201777760000000136212310435504026467 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \page u1db-qt-tutorial-2b.html */ u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2b/u1db-qt-example-2b.qml0000644000015201777760000000653712310435504026343 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 /*! This example demonstrates how to create and query one level of sub-fields in a document. */ MainView { id: u1dbView width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aDatabase2b" } /*! This snippet demonstrates how to create content that includes nested fields. The main field is 'hello', while the sub-field for each entry is 'value'. Later in the example it will be shown how to access each of these in the delegate of a ListView. U1db.Document { id: aDocument database: aDatabase docId: 'hello' create: true defaults: { "hello": [{"value":"99 Hello Worlds on the wall...!"},{"value":"98 Hello Worlds on the wall...!"},{"value":"97 Hello Worlds on the wall...!"},{"value":"...and so on..."}] } } */ U1db.Document { id: aDocument database: aDatabase docId: 'hello' create: true defaults: { "hello": [{"value":"99 Hello Worlds on the wall...!"},{"value":"98 Hello Worlds on the wall...!"},{"value":"97 Hello Worlds on the wall...!"},{"value":"...and so on..."}] } } Tabs { id: tabs anchors.fill: parent Tab { title: i18n.tr("Hello U1Db!") page: Page { id: helloPage ListView { width: units.gu(45) height: units.gu(80) anchors.fill: parent model: aDocument.contents.hello /*! Determining the current record is easy. All that is required is to access it using the delegate's own 'index' value, as shown here: delegate: Text { height: 30 text: aDocument.contents.hello[index].value } Remember that when the entries were created the sub-field was 'value'. So where index = 0, 'aDocument.contents.hello[0].value' will produce '99 Hello Worlds on the wall...!'. Each entry in the document will in turn create its own delegate with a new index number, which can then be used to extract the 'value' (or whatever other sub-field has been created). */ delegate: Text { height: 30 text: aDocument.contents.hello[index].value } } } } } } u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-6/0000755000015201777760000000000012310436007022247 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-6/u1db-qt-example-6.qdoc0000644000015201777760000003274512310435504026204 0ustar pbusernogroup00000000000000/*! \page u1db-qt-tutorial-6.html \title U1Db-Qt Synchronizing Tutorial This tutorial is designed to demonstrate a variety of essential U1Db-Qt functionality and usage, including: \list 1 \li Synchronizing two databases \li Utilizing the U1db-Qt Index element \li Various approaches to define U1db-Qt Document elements when using the Index element \li Partnering the U1db-Qt Index element and a QML ListView element \endlist \section1 Storing Data \section2 The Database Element \section3 Creating a Database 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. \code U1db.Database { id: aDatabase path: "aDatabase4" } \endcode \section1 The Document Element \section2 Declaring Documents (at Runtime) A Document can be instantiated at runtime, or generated dynamically. The examples below demonstrate the former. 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. This example of a very simple Document will not initially do anything, until more properties are added and defined: \code U1db.Document { id: aDocument1 docId: 'helloworld1' } \endcode 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. 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: \code U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld1' } \endcode 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: \code U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld1' create: true } \endcode 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: \code U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld1' create: true defaults:{"hello": { "world": { "message":"Hello World", "id": 1 } } } } \endcode 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. \code U1db.Document { id: aDocument2 database: aDatabase docId: 'helloworld2' create: true defaults:{"hello": { "world": [ { "message":"Hello World", "id": 2 }, { "message":"Hello World", "id": 2.5 } ] } } } \endcode 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): \code {"hello": { "world": [ { "message":"Hello World", "id": 2 }, { "message":"Hello World", "id": 2.5 } ] } } \endcode 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. 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. \code U1db.Document { id: aDocument3 database: aDatabase docId: 'helloworld3' contents:{"hello": { "world": [ { "message":"Hello World", "id": 3 }, { "message":"Hello World", "id": 3.33 }, { "message":"Hello World", "id": 3.66 } ] } } } \endcode 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: \code U1db.Document { id: aDocument3 database: aDatabase docId: 'helloworld3' create: true default:{"hello": { "world": [{ "message":"Hello World", "id": 3 }] } } contents:{"hello": { "world": [ { "message":"Hello World", "id": 3 }, { "message":"Hello World", "id": 3.33 }, { "message":"Hello World", "id": 3.66 } ] } } } \endcode 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. \code U1db.Document { id: aDocument4 database: aDatabase docId: 'helloworld4' defaults:{"hello": { "world": { "message":"Hello World", "id": 4 } } } } \endcode \section3 Samples of Stored Documents The data stored in the database after defining the above Document elements (and then running the application, will consist of the following: \table \header \li docId \li content \row \li 'helloworld1' \li \code { "hello": { "world": { "id": 1, "message": "Hello World" } } } \endcode \row \li 'helloworld2' \li \code { "hello": { "world": [ { "id": 2, "message": "Hello World" }, { "id": 2.5, "message": "Hello World" } ] } } \endcode \row \li 'helloworld3' \li \code { "hello": { "world": [ { "id": 3, "message": "Hello World" }, { "id": 3.33, "message": "Hello World" }, { "id": 3.66, "message": "Hello World" } ] } } \endcode \endtable \section1 Retrieving Data To retrieve the Documents that were declared earlier requires two additional elements: Index and Query. \section2 The Index Element \section3 Creating and Index Element The Index element requires both a unique 'id' and a pointer to a 'database' in order to begin becoming useful, as demonstrated here: \code U1db.Index{ database: aDatabase id: by_helloworld } \endcode 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: \code U1db.Index{ database: aDatabase id: by_helloworld //name: "by-helloworld" } \endcode 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): \code U1db.Index{ database: aDatabase id: by_helloworld //name: "by-helloworld" expression: ["hello.world.id","hello.world.message"] } \endcode \section2 The QueryElement \section3 Creating a Query Element 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). 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: \code U1db.Query{ id: aQuery index: by_helloworld } \endcode 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): \code U1db.Query{ id: aQuery index: by_helloworld //query: "*" } \endcode When the 'query' property becomes available, only wildcard search definitions for "starts with" will be suppoprted. Thus the following would be supported: \code U1db.Query{ id: aQuery index: by_helloworld //query: "Hello*" } \endcode But this would not: \code U1db.Query{ id: aQuery index: by_helloworld //query: "*World" } \endcode Note: again, the 'query' property is commented out in the above two snippets due to its current status \section1 Using Data \section2 Data and the Application UI \section3 Using Data With Models and Views 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: \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery } \endcode \section4 Data and Delegates 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: \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery delegate: Text { x: 66; y: 77 } } \endcode 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). 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). 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): \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery delegate: Text { x: 66; y: 77 text: index } } \endcode 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" \code ListView { width: units.gu(45) height: units.gu(80) model: aQuery delegate: Text { x: 66; y: 77 text: "(" + index + ") '" + contents.message + " " + contents.id + "'" } } \endcode */ u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-6/u1db-qt-example-6.qml0000644000015201777760000001161412310435504026037 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 Item { width: units.gu(45) height: units.gu(80) U1db.Database { id: aDatabase path: "aDatabase6" } U1db.Document { id: aDocument1 database: aDatabase docId: 'helloworld' create: true contents:{"hello": { "world": [ { "message": "Hello World" } ] } } } U1db.Index{ database: aDatabase id: by_helloworld expression: ["hello.world.message"] } U1db.Query{ id: aQuery index: by_helloworld query: [{"message":"Hel*"}] } U1db.Synchronizer{ id: aSynchronizer source: aDatabase targets: [{remote:true}, {remote:true, ip:"127.0.0.1", port: 7777, name:"example1.u1db", resolve_to_source:true}, {remote:"OK"}] synchronize: false } MainView { id: u1dbView width: units.gu(45) height: units.gu(80) anchors.top: parent.top; Tabs { id: tabs anchors.fill: parent Tab { objectName: "Tab1" title: i18n.tr("Hello U1Db!") page: Page { id: helloPage Rectangle { width: units.gu(45) height: units.gu(40) anchors.top: parent.top; border.width: 1 Text { id: sourceLabel anchors.top: parent.top; font.bold: true text: "aDatabase6 Contents" } ListView { id: sourceListView width: units.gu(45) height: units.gu(35) anchors.top: sourceLabel.bottom; model: aQuery delegate: Text { wrapMode: Text.WordWrap x: 6; y: 77 text: { text: "(" + index + ") '" + contents.message + "'" } } } } Rectangle { id: lowerRectangle width: units.gu(45) height: units.gu(35) anchors.bottom: parent.bottom; border.width: 1 Text { id: errorsLabel anchors.top: parent.top; font.bold: true text: "Log:" } ListView { parent: lowerRectangle width: units.gu(45) height: units.gu(30) anchors.top: errorsLabel.bottom; model: aSynchronizer delegate:Text { width: units.gu(40) anchors.left: parent.left anchors.right: parent.right wrapMode: Text.WordWrap text: { text: sync_output } } } Button{ parent: lowerRectangle anchors.bottom: parent.bottom; text: "Sync" onClicked: aSynchronizer.synchronize = true anchors.left: parent.left anchors.right: parent.right } } } } } } } u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-1/0000755000015201777760000000000012310436007022242 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-1/u1db-qt-example-1.qml0000644000015201777760000000537712310435504026036 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 MainView { id: u1dbView width: units.gu(45) height: units.gu(80) /*! 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. */ U1db.Database { id: aDatabase path: "aDatabase1" } /*! A Document can be declared at runtime. It requires at the very least a unique 'docId', but that alone won't do anything special. In order for a document to be entered into the database the below snippet demonstrates the basic requirements. The id might be optional. */ U1db.Document { id: aDocument database: aDatabase docId: 'helloworld' create: true defaults: { "hello": "Hello World!" } } Tabs { id: tabs anchors.fill: parent Tab { title: i18n.tr("Hello U1Db!") page: Page { id: helloPage ListView { width: units.gu(45) height: units.gu(80) /* Here is the reference to the Database model mentioned earlier. */ model: aDatabase /* A delegate will be created for each Document retrieved from the Database */ delegate: Text { x: 66; y: 77 text: { /*! The object called 'contents' contains a string as demonstrated here. In this example 'hello' is our search string. text: contents.hello */ text: contents.hello } } } } } } } u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-1/u1db-qt-example-1.qdoc0000644000015201777760000000136112310435504026160 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \page u1db-qt-tutorial-1.html */ u1db-qt-0.1.5+14.04.20140313/examples/bookmarks/0000755000015201777760000000000012310436007021166 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/examples/bookmarks/bookmarks.qml0000644000015201777760000001241512310435504023675 0ustar pbusernogroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import U1db 1.0 as U1db import Ubuntu.Components 0.1 import Ubuntu.Components.ListItems 0.1 as ListItem import Ubuntu.Components.Popups 0.1 /*! This example and tutorial is designed to show a wide variety of U1Db-Qt functionality and usage. The example demonstrates: \list 1 \li Combining U1Db-Qt with elements and components that do not utilize models \li Blending the U1Db-Qt plugin with QML and Javascript \endlist */ MainView { id: root applicationName: "com.ubuntu.developer.foobar.bookmarks" width: units.gu(45) height: units.gu(80) /* Bookmarks database */ U1db.Database { id: db // path: "bookmarks.db" } U1db.Document { database: db docId: 'defaultsDuckDuckGo' create: true defaults: { "uri": "https://www.duckduckgo.com", visited: false, "meta": { "title": "Search DuckDuckGo", visits: 0, tags: [ 'search', 'engine' ] } } } U1db.Document { database: db docId: 'defaultsUbuntu' create: true defaults: { "uri": "http://www.ubuntu.com", visited: true, "meta": { "title": "The world's most popular free OS", visits: 1001 } } } U1db.Query { id: allBookmarks index: U1db.Index { database: db } } /* UI: details */ Component { id: detailsPopCom Popover { id: detailsPop Column { anchors.centerIn: parent spacing: units.gu(1) Label { text: i18n.tr('JSON') } TextField { text: bookmarksList.detailsDocId } TextArea { text: bookmarksList.detailsContents.replace(',',',\n')+'\n\n' readOnly: true autoSize: true } Button { text: i18n.tr('Delete') onClicked: { PopupUtils.close(detailsPop) db.deleteDoc(bookmarksList.detailsDocId) } } } } } /* UI: list view, filters */ Page { id: page title: i18n.tr("Bookmarks") Item { id: container anchors.margins: units.gu(1) anchors.fill: parent ListView { id: bookmarksList anchors.fill: parent model: allBookmarks property string detailsDocId: "" property string detailsContents: "" delegate: ListItem.Subtitled { text: contents.title || '[title:%1]'.arg(docId) subText: contents.uri || '[uri:%1]'.arg(docId) // iconSource: contents.uri + "/favicon.ico" fallbackIconName: "favorite-unselected,text-html" iconFrame: false onClicked: { bookmarksList.detailsDocId = docId bookmarksList.detailsContents = JSON.stringify(contents) PopupUtils.open(detailsPopCom, bookmarksList) } } } OptionSelector { id: filterSelector StateSaver.properties: 'selectedIndex' anchors.bottom: parent.bottom text: i18n.tr('N/A') expanded: true model: ListModel { ListElement { label: 'Newly Added'; expression: "[ 'meta.visits' ]"; query: "[ { 'visits': 0 } ]" } ListElement { label: 'Ubuntu'; expression: "[ 'uri' ]"; query: "[ 'http://www.ubuntu*' ]" } ListElement { label: 'Search'; expression: "[ 'meta.title' ]"; query: "[ 'Search*' ]" } ListElement { label: 'Engine'; expression: "[ 'meta.tags' ]"; query: "[ 'engine' ]" } ListElement { label: 'All'; expression: "[ 'meta.visits', 'meta.title' ]"; query: "[ '*', '*' ]" } } delegate: OptionSelectorDelegate { text: i18n.tr(label) } selectedIndex: model.count - 1 onSelectedIndexChanged: { var d = model.get(selectedIndex) text = '%1 - %2'.arg(d.expression).arg(d.query) allBookmarks.index.expression = eval(d.expression) allBookmarks.query = eval(d.query) } } } } } u1db-qt-0.1.5+14.04.20140313/src/0000755000015201777760000000000012310436007016147 5ustar pbusernogroup00000000000000u1db-qt-0.1.5+14.04.20140313/src/sql.qrc0000644000015201777760000000016512310435504017460 0ustar pbusernogroup00000000000000 dbschema.sql u1db-qt-0.1.5+14.04.20140313/src/global.h0000644000015201777760000000325112310435504017562 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef U1DB_GLOBAL_H #define U1DB_GLOBAL_H #ifndef QT_STATIC # if defined(QT_BUILD_U1DB_LIB) # define Q_U1DB_EXPORT Q_DECL_EXPORT # else # define Q_U1DB_EXPORT Q_DECL_IMPORT # endif #else # define Q_U1DB_EXPORT #endif #if defined(QT_NAMESPACE) # define QT_BEGIN_NAMESPACE_U1DB namespace QT_NAMESPACE { namespace U1db { # define QT_END_NAMESPACE_U1DB } } # define QT_USE_NAMESPACE_U1DB using namespace QT_NAMESPACE::U1db; # define QT_PREPEND_NAMESPACE_U1DB(name) QT_NAMESPACE::U1db::name #else # define QT_BEGIN_NAMESPACE_U1DB namespace U1db { # define QT_END_NAMESPACE_U1DB } # define QT_USE_NAMESPACE_U1DB using namespace U1db; # define QT_PREPEND_NAMESPACE_U1DB(name) U1db::name #endif // a workaround for moc - if there is a header file that doesn't use u1db // namespace, we still force moc to do "using namespace" but the namespace have to // be defined, so let's define an empty namespace here QT_BEGIN_NAMESPACE_U1DB QT_END_NAMESPACE_U1DB #endif // U1DB_GLOBAL_H u1db-qt-0.1.5+14.04.20140313/src/document.h0000644000015201777760000000462212310435504020143 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef U1DB_DOCUMENT_H #define U1DB_DOCUMENT_H #include #include #include #include "database.h" QT_BEGIN_NAMESPACE_U1DB class Q_DECL_EXPORT Document : public QObject { Q_OBJECT #ifdef Q_QDOC Q_PROPERTY(Database* database READ getDatabase WRITE setDatabase NOTIFY databaseChanged) #else Q_PROPERTY(QT_PREPEND_NAMESPACE_U1DB(Database*) database READ getDatabase WRITE setDatabase NOTIFY databaseChanged) #endif Q_PROPERTY(QString docId READ getDocId WRITE setDocId NOTIFY docIdChanged) Q_PROPERTY(bool create READ getCreate WRITE setCreate NOTIFY createChanged) Q_PROPERTY(QVariant defaults READ getDefaults WRITE setDefaults NOTIFY defaultsChanged) Q_PROPERTY(QVariant contents READ getContents WRITE setContents NOTIFY contentsChanged) public: Document(QObject* parent = 0); Database* getDatabase(); void setDatabase(Database* database); QString getDocId(); void setDocId(const QString& docId); bool getCreate(); void setCreate(bool create); QVariant getDefaults(); void setDefaults(QVariant defaults); QVariant getContents(); void setContents(QVariant contents); Q_SIGNALS: void databaseChanged(Database* database); void docIdChanged(const QString& docId); void createChanged(bool create); void defaultsChanged(QVariant defaults); void contentsChanged(QVariant contents); private: Q_DISABLE_COPY(Document) Database* m_database; QString m_docId; bool m_create; QVariant m_defaults; QVariant m_contents; void onDocChanged(const QString& docID, QVariant content); void onPathChanged(const QString& path); }; QT_END_NAMESPACE_U1DB #endif // U1DB_DOCUMENT_H u1db-qt-0.1.5+14.04.20140313/src/index.cpp0000644000015201777760000001575212310435504017775 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "index.h" #include "private.h" QT_BEGIN_NAMESPACE_U1DB /*! \class Index \inmodule U1Db \ingroup modules \brief The Index class defines an index to be stored in the database and queried using Query. Changes in documents affected by the index also update the index in the database. This is the declarative API equivalent of Database::putIndex() and Database::getIndexExpressions(). */ /*! Instantiate a new Index with an optional \a parent, usually by declaring it as a QML item. */ Index::Index(QObject *parent) : QObject(parent), m_database(0) { } Database* Index::getDatabase() { return m_database; } void Index::onPathChanged(const QString& path) { Q_EMIT dataInvalidated(); } void Index::onDocChanged(const QString& docId, QVariant content) { Q_EMIT dataInvalidated(); } /*! \property Index::database Sets the Database to lookup documents from and store the index in. The dataInvalidated() signal will be emitted on all changes that could affect the index. */ void Index::setDatabase(Database* database) { if (m_database == database) return; if (m_database) QObject::disconnect(m_database, 0, this, 0); m_database = database; Q_EMIT databaseChanged(database); if (m_database) { m_database->putIndex(m_name, m_expression); QObject::connect(m_database, &Database::pathChanged, this, &Index::onPathChanged); QObject::connect(m_database, &Database::docChanged, this, &Index::onDocChanged); Q_EMIT dataInvalidated(); } } QString Index::getName() { return m_name; } /*! \property Index::name Sets the name used. Both an expression and a name must be specified for an index to be created. */ void Index::setName(const QString& name) { if (m_name == name) return; if (m_database) { m_database->putIndex(name, m_expression); Q_EMIT dataInvalidated(); } m_name = name; Q_EMIT nameChanged(name); } QStringList Index::getExpression() { return m_expression; } /*! \property Index::expression Sets the expression used. Both an expression and a name must be specified for an index to be created. Also starts the process of creating the Index result list, which can then be queried or populate the Query model as is. */ void Index::setExpression(QStringList expression) { if (m_expression == expression) return; m_expression = expression; if (m_database) { m_database->putIndex(m_name, m_expression); Q_EMIT dataInvalidated(); } Q_EMIT expressionChanged(expression); } /*! \internal * Iterates through the documents stored in the database and creates the list of results based on the Index expressions. */ void Index::generateIndexResults() { m_results.clear(); Database *db(getDatabase()); if(db){ QList documents = db->listDocs(); Q_FOREACH (QString docId, documents){ QVariant document = db->getDocUnchecked(docId); QStringList fieldsList; appendResultsFromMap(docId, fieldsList, document.toMap(),""); } } } /*! \internal */ QList Index::getAllResults(){ generateIndexResults(); return m_results; } /*! \internal * *This method is desinged to recursively iterate through a document, or section of a document, which represents a QVariantMap. As it iterates through the entire document, the method keeps track of the current index expression, and populates a local QVariantMap should the current expression be found in the Index's list of expressions. * *If that QVariantMap contains more than one entry it is added to the global results, which can then be utilized by a Query. This needs to be modified to ensure all expressions are found, whereas at the moment if more than one expressions are defined and any of them are found then the map is added to the results list. * */ QStringList Index::appendResultsFromMap(QString docId, QStringList fieldsList, QVariantMap current_section, QString current_field) { QMapIterator i(current_section); QString original_field = current_field; QVariantMap results_map; while (i.hasNext()) { i.next(); if(!original_field.isEmpty()){ current_field = original_field + "." + i.key(); } else{ current_field = i.key(); } fieldsList.append(current_field); QVariant value = i.value(); if(value.userType()==8) // QVariantMap { fieldsList = appendResultsFromMap(docId, fieldsList, value.toMap(),current_field); } else if(value.userType()==9) // QVariantList { fieldsList = getFieldsFromList(docId, fieldsList, value.toList(),current_field); } { if(m_expression.contains(current_field)==true){ results_map.insert(i.key(),value); } } } if(results_map.count()>0){ QVariantMap mapIdResult; mapIdResult.insert("docId", docId); mapIdResult.insert("result", results_map); m_results.append(mapIdResult); } return fieldsList; } /*! \internal * *This recursive method is used in conjuntion with Index::appendResultsFromMap, to aid in iterating through a document when an embedded list is found. * */ QStringList Index::getFieldsFromList(QString docId, QStringList fieldsList, QVariantList current_section, QString current_field) { QListIterator i(current_section); while (i.hasNext()) { QVariant value = i.next(); if(value.userType()==8) // QVariantMap { fieldsList = appendResultsFromMap(docId, fieldsList, value.toMap(),current_field); } else if(value.userType()==9) // QVariantList { fieldsList = getFieldsFromList(docId, fieldsList, value.toList(),current_field); } else if(value.userType()==10) // QString { fieldsList.append(current_field); } else { } } return fieldsList; } QT_END_NAMESPACE_U1DB #include "moc_index.cpp" u1db-qt-0.1.5+14.04.20140313/src/private.h0000644000015201777760000000131712310435504017775 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ u1db-qt-0.1.5+14.04.20140313/src/index.h0000644000015201777760000000472212310435504017435 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef U1DB_INDEX_H #define U1DB_INDEX_H #include #include #include #include "database.h" QT_BEGIN_NAMESPACE_U1DB class Q_DECL_EXPORT Index : public QObject { Q_OBJECT #ifdef Q_QDOC Q_PROPERTY(Database* database READ getDatabase WRITE setDatabase NOTIFY databaseChanged) #else Q_PROPERTY(QT_PREPEND_NAMESPACE_U1DB(Database*) database READ getDatabase WRITE setDatabase NOTIFY databaseChanged) #endif Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) Q_PROPERTY(QStringList expression READ getExpression WRITE setExpression NOTIFY expressionChanged) public: Index(QObject* parent = 0); Database* getDatabase(); void setDatabase(Database* database); QString getName(); void setName(const QString& name); QStringList getExpression(); void setExpression(QStringList expression); QList getAllResults(); Q_SIGNALS: void databaseChanged(Database* database); void nameChanged(const QString& name); void expressionChanged(QVariant expression); /*! The database, an indexed document or the expressions changed. */ void dataInvalidated(); private: Q_DISABLE_COPY(Index) Database* m_database; QString m_name; QStringList m_expression; QList m_results; void onPathChanged(const QString& path); void onDocChanged(const QString& docId, QVariant content); QStringList appendResultsFromMap(QString docId, QStringList fieldsList, QVariantMap current_section, QString current_field); QStringList getFieldsFromList(QString docId, QStringList fieldsList, QVariantList current_section, QString current_field); void generateIndexResults(); }; QT_END_NAMESPACE_U1DB #endif // U1DB_INDEX_H u1db-qt-0.1.5+14.04.20140313/src/CMakeLists.txt0000644000015201777760000000237212310435504020714 0ustar pbusernogroup00000000000000set(U1DB_QT_LIBNAME u1db-qt5) # Sources set(U1DB_QT_SRCS database.cpp document.cpp index.cpp query.cpp synchronizer.cpp ) # Generated files set(U1DB_QT_GENERATED moc_database.cpp moc_document.cpp moc_index.cpp moc_query.cpp moc_synchronizer.cpp ) set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${U1DB_QT_GENERATED}") # Resources set(U1DB_QT_RESOURCES sql.qrc ) QT5_ADD_RESOURCES(U1DB_QT_RCC ${U1DB_QT_RESOURCES}) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS} ${U1DB_INCLUDE_DIRS} ) add_library(${U1DB_QT_LIBNAME} SHARED ${U1DB_QT_SRCS} ${U1DB_QT_RCC}) target_link_libraries(${U1DB_QT_LIBNAME} ${Qt5Core_LIBRARIES} ${Qt5Sql_LIBRARIES} ${Qt5Network_LIBRARIES} ${U1DB_LDFLAGS} ) set_target_properties(${U1DB_QT_LIBNAME} PROPERTIES SOVERSION 3 VERSION 3.0.0 ) # Install set(INCLUDE_INSTALL_DIR include/lib${U1DB_QT_LIBNAME}) install(TARGETS ${U1DB_QT_LIBNAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install(FILES global.h database.h document.h index.h query.h synchronizer.h DESTINATION ${INCLUDE_INSTALL_DIR} ) u1db-qt-0.1.5+14.04.20140313/src/document.cpp0000644000015201777760000001144712310435504020501 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "document.h" #include "private.h" QT_BEGIN_NAMESPACE_U1DB /*! \class Document \inmodule U1db \ingroup modules \brief The Document class proxies a single document stored in the Database. This is the declarative API equivalent of Database::putDoc() and Database::getDoc(). */ /*! Instantiate a new Document with an optional \a parent, usually by declaring it as a QML item. */ Document::Document(QObject *parent) : QObject(parent), m_database(0), m_create(false) { } Database* Document::getDatabase() { return m_database; } void Document::onDocChanged(const QString& docId, QVariant content) { if (docId == m_docId) { m_contents = m_database->getDocUnchecked(m_docId); Q_EMIT contentsChanged(m_contents); } } void Document::onPathChanged(const QString& path) { if (!m_docId.isEmpty()) { m_contents = m_database->getDocUnchecked(m_docId); Q_EMIT contentsChanged(m_contents); } } /*! \property Document::database The database is used to lookup the contents of the document, reflecting changes done to it and conversely changes are saved to the database. */ void Document::setDatabase(Database* database) { if (m_database == database) return; if (m_database) QObject::disconnect(m_database, 0, this, 0); m_database = database; if (m_database) { if (!m_docId.isEmpty()) { m_contents = m_database->getDocUnchecked(m_docId); Q_EMIT contentsChanged(m_contents); } QObject::connect(m_database, &Database::pathChanged, this, &Document::onPathChanged); QObject::connect(m_database, &Database::docChanged, this, &Document::onDocChanged); } Q_EMIT databaseChanged(database); } QString Document::getDocId() { return m_docId; } /*! \property Document::docId The docId can be that of an existing document in the database and will determine what getContents() returns. If no such documents exists, setDefaults() can be used to supply a preset. */ void Document::setDocId(const QString& docId) { if (m_docId == docId) return; m_docId = docId; Q_EMIT docIdChanged(docId); if (m_database) { m_contents = m_database->getDocUnchecked(docId); Q_EMIT contentsChanged(m_contents); } } bool Document::getCreate() { return m_create; } /*! \property Document::create If create is true, docId is not empty and no document with the same docId exists, defaults will be used to store the document. */ void Document::setCreate(bool create) { if (m_create == create) return; m_create = create; Q_EMIT createChanged(create); if (m_create && m_database && m_defaults.isValid() && !m_database->getDocUnchecked(m_docId).isValid()) m_database->putDoc(m_defaults, m_docId); } QVariant Document::getDefaults() { return m_defaults; } /*! \property Document::defaults The default contents of the document, which are used only if create is true, docId is not empty and no document with the same docId exists in the database yet. If the defaults change, it's up to the API user to handle it. */ void Document::setDefaults(QVariant defaults) { if (m_defaults == defaults) return; m_defaults = defaults; Q_EMIT defaultsChanged(defaults); if (m_create && m_database && m_defaults.isValid() && !m_database->getDocUnchecked(m_docId).isValid()) m_database->putDoc(m_defaults, m_docId); } QVariant Document::getContents() { return m_contents; } /*! \property Document::contents Updates the contents of the document. A valid docId must be set. */ void Document::setContents(QVariant contents) { if (m_contents == contents) return; m_contents = contents; Q_EMIT contentsChanged(contents); if (m_database && !m_docId.isEmpty()) m_database->putDoc(m_contents, m_docId); } QT_END_NAMESPACE_U1DB #include "moc_document.cpp" u1db-qt-0.1.5+14.04.20140313/src/database.cpp0000644000015201777760000006452512310435504020434 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "database.h" #include "private.h" QT_BEGIN_NAMESPACE_U1DB /*! \class Database \inmodule U1Db \ingroup modules \brief The Database class implements the on-disk storage of an individual U1DB database. Database can be used as a QAbstractListModel, delegates will then have access to \a docId and \a contents analogous to the properties of Document. */ /*! A unique identifier for the state of synchronization */ QString Database::getReplicaUid() { QSqlQuery query (m_db.exec("SELECT value FROM u1db_config WHERE name = 'replica_uid'")); if (!query.lastError().isValid() && query.next()) return query.value(0).toString(); return setError(QString("Failed to get replica UID: %1\n%2").arg(query.lastError().text()).arg(query.lastQuery())) ? QString() : QString(); } /*! Checks if the underlying SQLite database is ready to be used Only to be used as a utility function by initializeIfNeeded() */ bool Database::isInitialized() { m_db.exec("PRAGMA case_sensitive_like=ON"); QSqlQuery query(m_db.exec( "SELECT value FROM u1db_config WHERE name = 'sql_schema'")); return query.next(); } /*! Describes the error as a string if the last operation failed. */ bool Database::setError(const QString& error) { qWarning("u1db: %s", qPrintable(error)); m_error = error; Q_EMIT errorChanged(error); return false; } /*! \property Database::error The last error as a string if the last operation failed. */ QString Database::lastError() { return m_error; } /*! Ensures that the underlying database works, or tries to set it up: The SQlite backend is loaded - it's an optional Qt5 module and can fail If @path is an existing database, it'll be opened For a new database, the default schema will be applied */ bool Database::initializeIfNeeded(const QString& path) { if (m_db.isOpen()) return true; /* A unique ID is used for the connection name to ensure that we aren't re-using or replacing other opend databases. */ if (!m_db.isValid()) m_db = QSqlDatabase::addDatabase("QSQLITE",QUuid::createUuid().toString()); if (!m_db.isValid()) return setError("QSqlDatabase error"); if (path != ":memory:" && QDir::isRelativePath(path)) { QString dataPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); QString absolutePath(QDir(dataPath).absoluteFilePath(path)); QString parent(QFileInfo(absolutePath).dir().path()); if (!QDir().mkpath(parent)) qWarning() << "Failed to make data folder" << parent; m_db.setDatabaseName(absolutePath); } else m_db.setDatabaseName(path); if (!m_db.open()) return setError(QString("Failed to open %1: %2").arg(path).arg(m_db.lastError().text())); if (!isInitialized()) { if (!isInitialized()) { QFile file(":/dbschema.sql"); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { while (!file.atEnd()) { QByteArray line = file.readLine(); while (!line.endsWith(";\n") && !file.atEnd()) line += file.readLine(); if (m_db.exec(line).lastError().isValid()) return setError(QString("Failed to apply internal schema: %1\n%2").arg(m_db.lastError().text()).arg(QString(line))); } QSqlQuery query(m_db.exec()); query.prepare("INSERT OR REPLACE INTO u1db_config VALUES ('replica_uid', :uuid)"); query.bindValue(":uuid", QUuid::createUuid().toString()); if (!query.exec()) return setError(QString("Failed to apply internal schema: %1\n%2").arg(m_db.lastError().text()).arg(query.lastQuery())); // Double-check if (query.boundValue(0).toString() != getReplicaUid()) return setError(QString("Invalid replica uid: %1").arg(query.boundValue(0).toString())); } else return setError(QString("Failed to read internal schema: FileError %1").arg(file.error())); } } return true; } /*! Instantiate a new Database with an optional \a parent, usually by declaring it as a QML item. */ Database::Database(QObject *parent) : QAbstractListModel(parent), m_path("") { initializeIfNeeded(); } /*! Used to implement QAbstractListModel Returns docId matching the given row index assuming all documents are ordered consistently */ QString Database::getDocIdByRow(int row) const { if (!m_db.isOpen()) return QString(); QSqlQuery query(m_db.exec()); query.prepare("SELECT doc_id FROM document LIMIT 1 OFFSET :row"); query.bindValue(":row", row); if (query.exec() && query.next()) return query.value("doc_id").toString(); return QString(); } /*! \internal Used to implement QAbstractListModel Implements the variables exposed to the Delegate in a model QVariant contents QString docId int index (built-in) */ QVariant Database::data(const QModelIndex & index, int role) const { QString docId(getDocIdByRow(index.row())); if (role == 0) // contents return getDocUnchecked(docId); if (role == 1) // docId return docId; return QVariant(); } /*! \internal Used to implement QAbstractListModel Defines \b{contents} and \b{docId} as variables exposed to the Delegate in a model \b{index} is supported out of the box. */ QHash Database::roleNames() const { QHash roles; roles.insert(0, "contents"); roles.insert(1, "docId"); return roles; } /*! \internal Used to implement QAbstractListModel The number of rows: the number of documents in the database. */ int Database::rowCount(const QModelIndex & parent) const { if (!m_db.isOpen()) return 0; QSqlQuery query(m_db.exec()); query.prepare("SELECT COUNT(*) AS count FROM document"); if (!(query.exec() && query.next())) return 0; return query.value("count").toInt(); } /*! Same functionality as Database::getDoc() except it won't set Database::lastError() and it doesn't implicitly try to initialize the underlying database. \a docId must be a valid unique ID string Use cases: model implementations, Document::getContents() */ QVariant Database::getDocUnchecked(const QString& docId) const { if (!m_db.isOpen()) return QVariant(); QSqlQuery query(m_db.exec()); query.prepare("SELECT doc_rev, content FROM document WHERE doc_id = :docId"); query.bindValue(":docId", docId); if (query.exec() && query.next()) { // Convert JSON string to the Variant that QML expects QJsonDocument json(QJsonDocument::fromJson(query.value("content").toByteArray())); Q_EMIT docLoaded(docId, json.object().toVariantMap()); return json.object().toVariantMap(); } return QVariant(); } /*! * \internal * \brief Database::getDocumentContents * * Returns the string representation of a document that has * been selected from the database using a document id. * */ QString Database::getDocumentContents(const QString& docId) { if (!initializeIfNeeded()) return QString(); QSqlQuery query(m_db.exec()); query.prepare("SELECT document.doc_rev, document.content, " "count(conflicts.doc_rev) AS conflicts FROM document LEFT OUTER JOIN " "conflicts ON conflicts.doc_id = document.doc_id WHERE " "document.doc_id = :docId GROUP BY document.doc_id, " "document.doc_rev, document.content"); query.bindValue(":docId", docId); if (query.exec()) { if (query.next()) { if (query.value("conflicts").toInt() > 0) setError(QString("Conflicts in %1").arg(docId)); return query.value("content").toString(); } return setError(QString("Failed to get document %1: No document").arg(docId)) ? QString() : QString(); } return setError(QString("Failed to get document %1: %2\n%3").arg(docId).arg(query.lastError().text()).arg(query.lastQuery())) ? QString() : QString(); } /*! Returns the contents of a document by \a docId in a form that QML recognizes as a Variant object, it's identical to Document::getContents() with the same \a docId. */ QVariant Database::getDoc(const QString& docId) { if (!initializeIfNeeded()) return QVariant(); QSqlQuery query(m_db.exec()); query.prepare("SELECT document.doc_rev, document.content, " "count(conflicts.doc_rev) AS conflicts FROM document LEFT OUTER JOIN " "conflicts ON conflicts.doc_id = document.doc_id WHERE " "document.doc_id = :docId GROUP BY document.doc_id, " "document.doc_rev, document.content"); query.bindValue(":docId", docId); if (query.exec()) { if (query.next()) { if (query.value("conflicts").toInt() > 0) setError(QString("Conflicts in %1").arg(docId)); // Convert JSON string to the Variant that QML expects QJsonDocument json(QJsonDocument::fromJson(query.value("content").toByteArray())); Q_EMIT docLoaded(docId, json.object().toVariantMap()); return json.object().toVariantMap(); } return setError(QString("Failed to get document %1: No document").arg(docId)) ? QVariant() : QVariant(); } return setError(QString("Failed to get document %1: %2\n%3").arg(docId).arg(query.lastError().text()).arg(query.lastQuery())) ? QVariant() : QVariant(); } /*! * \internal This function creates a new revision number. It returns a string for use in the document table's 'doc_rev' field. */ QString Database::getNextDocRevisionNumber(QString doc_id) { QString revision_number = getReplicaUid()+":1"; QString current_revision_number = getCurrentDocRevisionNumber(doc_id); /*! Some revisions contain information from previous conflicts/syncs. Revisions are delimited by '|'. */ QStringList current_revision_list = current_revision_number.split("|"); Q_FOREACH (QString current_revision, current_revision_list) { /*! Each revision contains two pieces of information, the uid of the database that made the revsion, and a counter for the revsion. This information is delimited by ':'. */ QStringList current_revision_number_list = current_revision.split(":"); if(current_revision_number_list[0]==getReplicaUid()) { /*! If the current revision uid is the same as this Database's uid the counter portion is increased by one. */ int revision_generation_number = current_revision_number_list[1].toInt()+1; revision_number = getReplicaUid()+":"+QString::number(revision_generation_number); } else { /*! 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. */ //revision_number+="|"+current_revision; /* Not sure if the above is necessary, *and did not appear to be working as intended either. * * Commented out, but maybe OK to delete. */ } } /*! The Database UID has curly brackets, but they are not required for the revision number and need to be removed. */ revision_number = revision_number.replace("{",""); revision_number = revision_number.replace("}",""); return revision_number; } /*! * \internal The getCurrentDocRevisionNumber(QString doc_id) function returns the current string value from the document table's doc_rev field. */ QString Database::getCurrentDocRevisionNumber(QString doc_id){ if (!initializeIfNeeded()) return QString(); QSqlQuery query(m_db.exec()); query.prepare("SELECT doc_rev from document WHERE doc_id = :docId"); query.bindValue(":docId", doc_id); if (query.exec()) { while (query.next()) { return query.value("doc_rev").toString(); } } else{ return setError(query.lastError().text()) ? QString() : QString(); } return QString(); } /*! * \internal * \brief Database::updateSyncLog * * This method is used at the end of a synchronization session, * to update the database with the latest information known about the peer * database that was synced against. */ void Database::updateSyncLog(bool insert, QString uid, QString generation, QString transaction_id) { if (!initializeIfNeeded()) return; QSqlQuery query(m_db.exec()); if(insert==true){ query.prepare("INSERT INTO sync_log(known_generation,known_transation_id,known_transation_id) VALUES(:knownGeneration, :knownTransactionId, :replicaUid)"); } else{ query.prepare("UPDATE sync_log SET known_generation = :knownGeneration, known_transation_id = :knownTransactionId WHERE replica_uid = :replicaUid"); } query.bindValue(":replicaUid", uid); query.bindValue(":knownGeneration", generation); query.bindValue(":knownTransactionId", transaction_id); if (!query.exec()) { setError(query.lastError().text()); } } /*! * \internal * \brief Database::updateDocRevisionNumber * * Whenever a document as added or modified it needs a new revision number. * * The revision number contains information about revisions made at the source, * but also revisions to the document by target databases (and then synced with the source). * */ void Database::updateDocRevisionNumber(QString doc_id,QString revision){ if (!initializeIfNeeded()) return; QSqlQuery query(m_db.exec()); query.prepare("UPDATE document SET doc_rev = :revisionId WHERE doc_id = :docId"); query.bindValue(":docId", doc_id); query.bindValue(":revisionId", revision); if (!query.exec()) { setError(query.lastError().text()); } } /*! The getCurrentGenerationNumber() function searches for the current generation number from the sqlite_sequence table. The return value can then be used during a synchronization session, amongst other things. */ int Database::getCurrentGenerationNumber(){ int sequence_number = -1; QSqlQuery query(m_db.exec()); query.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'transaction_log'"); if (query.exec()) { while (query.next()) { sequence_number = (query.value("seq").toInt()); } } else{ setError(query.lastError().text()); } return sequence_number; } /*! The generateNewTransactionId() function generates a random transaction id string, for use when creating new transations. */ QString Database::generateNewTransactionId(){ QString uid = "T-"+QUuid::createUuid().toString(); uid = uid.replace("}",""); uid = uid.replace("{",""); return uid; } /*! Each time a document in the Database is created or updated a new transaction is performed, and information about it inserted into the transation_log table using the createNewTransaction(QString doc_id) function. */ int Database::createNewTransaction(QString doc_id){ QString transaction_id = generateNewTransactionId(); QSqlQuery query(m_db.exec()); QString queryString = "INSERT INTO transaction_log(doc_id, transaction_id) VALUES('"+doc_id+"', '"+transaction_id+"')"; if (!query.exec(queryString)){ return -1; } else{ return 0; } return -1; } /*! Updates the existing \a contents of the document identified by \a docId if there's no error. If no \a docId is given or \a docId is an empty string the \a contents will be stored under an autogenerated name. Returns the new revision of the document, or -1 on failure. */ QString Database::putDoc(QVariant contents, const QString& docId) { if (!initializeIfNeeded()) return ""; QString newOrEmptyDocId(docId); QVariant oldDoc = newOrEmptyDocId.isEmpty() ? QVariant() : getDocUnchecked(newOrEmptyDocId); QString revision_number = getNextDocRevisionNumber(newOrEmptyDocId); QSqlQuery query(m_db.exec()); if (oldDoc.isValid()) { query.prepare("UPDATE document SET doc_rev=:docRev, content=:docJson WHERE doc_id = :docId"); query.bindValue(":docId", newOrEmptyDocId); query.bindValue(":docRev", revision_number); // Parse Variant from QML as JsonDocument, fallback to string QString json(QJsonDocument::fromVariant(contents).toJson()); query.bindValue(":docJson", json.isEmpty() ? contents : json); if (!query.exec()) return setError(QString("Failed to put/ update document %1: %2\n%3").arg(newOrEmptyDocId).arg(query.lastError().text()).arg(query.lastQuery())) ? "" : ""; query.prepare("DELETE FROM document_fields WHERE doc_id = :docId"); query.bindValue(":docId", newOrEmptyDocId); if (!query.exec()) return setError(QString("Failed to delete document field %1: %2\n%3").arg(newOrEmptyDocId).arg(query.lastError().text()).arg(query.lastQuery())) ? "" : ""; createNewTransaction(newOrEmptyDocId); } else { if (newOrEmptyDocId.isEmpty()) newOrEmptyDocId = QString("D-%1").arg(QUuid::createUuid().toString().mid(1).replace("}","")); if (!QRegExp("^[a-zA-Z0-9.%_-]+$").exactMatch(newOrEmptyDocId)) return setError(QString("Invalid docID %1").arg(newOrEmptyDocId)) ? "" : ""; query.prepare("INSERT INTO document (doc_id, doc_rev, content) VALUES (:docId, :docRev, :docJson)"); query.bindValue(":docId", newOrEmptyDocId); query.bindValue(":docRev", revision_number); // Parse Variant from QML as JsonDocument, fallback to string QJsonDocument json(QJsonDocument::fromVariant(contents)); query.bindValue(":docJson", json.isEmpty() ? contents : json.toJson()); if (!query.exec()) return setError(QString("Failed to put document %1: %2\n%3").arg(docId).arg(query.lastError().text()).arg(query.lastQuery())) ? "" : ""; createNewTransaction(newOrEmptyDocId); } beginResetModel(); endResetModel(); /* FIXME investigate correctly notifying about new rows beginInsertRows(QModelIndex(), rowCount(), 0); endInsertRows(); */ Q_EMIT docChanged(newOrEmptyDocId, contents); return revision_number; } /*! Deletes the document identified by \a docId. */ void Database::deleteDoc(const QString& docId) { putDoc(QString(), docId); } /*! * \brief Database::resetModel * * Resets the Database model. */ void Database::resetModel(){ beginResetModel(); endResetModel(); } /*! Returns a list of all stored documents by their docId. */ QList Database::listDocs() { QList list; if (!initializeIfNeeded()) return list; QSqlQuery query(m_db.exec()); query.prepare("SELECT document.doc_id, document.doc_rev, document.content, " "count(conflicts.doc_rev) FROM document LEFT OUTER JOIN conflicts " "ON conflicts.doc_id = document.doc_id GROUP BY document.doc_id, " "document.doc_rev, document.content"); if (query.exec()) { while (query.next()) { list.append(query.value("doc_id").toString()); } return list; } return setError(QString("Failed to list documents: %1\n%2").arg(query.lastError().text()).arg(query.lastQuery())) ? list : list; } /*! \property Database::path A relative filename can be given to store the database in an app-specific writable folder. This is recommended as it ensures to work with confinement. If more control is needed absolute paths can be used. By default everything is stored in memory. */ void Database::setPath(const QString& path) { if (m_path == path) return; beginResetModel(); m_db.close(); initializeIfNeeded(path); endResetModel(); m_path = path; Q_EMIT pathChanged(path); } /*! * \brief Database::getPath * * Simply returns the path of the database. * */ QString Database::getPath() { return m_path; } /*! Stores a new index under the given \a indexName, with \a expressions. An existing index won't be replaced implicitly, an error will be set in that case. */ QString Database::putIndex(const QString& indexName, QStringList expressions) { if (indexName.isEmpty() || expressions.isEmpty()) return QString("Either name or expressions is empty"); Q_FOREACH (QString expression, expressions) if (expression.isEmpty() || expression.isNull()) return QString("Empty expression in list"); if (!initializeIfNeeded()) return QString("Database isn't ready"); QStringList results = getIndexExpressions(indexName); bool changed = false; Q_FOREACH (QString expression, expressions) if (results.contains(expression)) changed = true; if (changed) return QString("Index conflicts with existing index"); QSqlQuery query(m_db.exec()); for (int i = 0; i < expressions.count(); ++i) { query.prepare("INSERT INTO index_definitions VALUES (:indexName, :offset, :field)"); query.bindValue(":indexName", indexName); query.bindValue(":offset", i); query.bindValue(":field", expressions.at(i)); if (!query.exec()) return QString("Failed to insert index definition: %1\n%2").arg(m_db.lastError().text()).arg(query.lastQuery()); } return QString(); } /*! Gets the expressions saved with putIndex(). \a indexName: the unique name of an existing index */ QStringList Database::getIndexExpressions(const QString& indexName) { QStringList expressions; if (!initializeIfNeeded()) return expressions; QSqlQuery query(m_db.exec()); query.prepare("SELECT field FROM index_definitions WHERE name = :indexName ORDER BY offset DESC"); query.bindValue(":indexName", indexName); if (!query.exec()) return setError(QString("Failed to lookup index definition: %1\n%2").arg(m_db.lastError().text()).arg(query.lastQuery())) ? expressions : expressions; while (query.next()) expressions.append(query.value("field").toString()); return expressions; } /*! Lists the index keys of an index created with putIndex(). \a indexName: the unique name of an existing index */ QStringList Database::getIndexKeys(const QString& indexName) { QStringList list; if (!initializeIfNeeded()) return list; QStringList expressions = getIndexExpressions(indexName); QString valueFields, tables, noValueWhere; int i = 0; Q_FOREACH (QString expression, expressions) { valueFields += QString("d%1.value,").arg(i); tables += QString("document_fields d%1,").arg(i); noValueWhere += QString("d.doc_id = d%1.doc_id AND d%1.field_name = \"%2\" AND ").arg( i).arg(expression); } if (valueFields.endsWith(",")) valueFields.chop(1); if (tables.endsWith(",")) tables.chop(1); if (noValueWhere.endsWith("AND ")) noValueWhere.chop(4); QString where; i = 0; Q_FOREACH (QString expression, expressions) { where += QString("%1 AND d%2.value NOT NULL AND ").arg(noValueWhere).arg(i); i++; } if (where.endsWith("AND ")) where.chop(4); QSqlQuery query(m_db.exec()); query.prepare(QString("SELECT %1 FROM document d, %2 WHERE %3 GROUP BY %1").arg( valueFields, tables, where)); if (!query.exec()) return setError(QString("Failed to get index keys: %1\n%2").arg(m_db.lastError().text()).arg(query.lastQuery())) ? list : list; while (query.next()) list.append(query.value("value").toString()); return list; } /* Handy functions for synchronization. */ /*! * \internal * \brief Database::listTransactionsSince * * This lists transactions for the database since a particular generation number. * */ QList Database::listTransactionsSince(int generation){ QList list; if (!initializeIfNeeded()) return list; QSqlQuery query(m_db.exec()); QString queryStmt = "SELECT generation, doc_id, transaction_id FROM transaction_log where generation > "+QString::number(generation); if (query.exec(queryStmt)) { while (query.next()) { list.append(query.value("generation").toString()+"|"+query.value("doc_id").toString()+"|"+query.value("transaction_id").toString()); } return list; } return list; } /*! * \internal * \brief Database::getSyncLogInfo * * Provides the information about previous synchronizations between the database and another (if any). * */ QMap Database::getSyncLogInfo(QMap lastSyncInformation, QString uid, QString prefix){ if (!initializeIfNeeded()) return lastSyncInformation; QString queryStmt = "SELECT known_transaction_id, known_generation FROM sync_log WHERE replica_uid = '"+uid +"'"; QSqlQuery query(m_db.exec()); if (query.exec(queryStmt)) { while (query.next()) { lastSyncInformation.insert(prefix + "_replica_generation", query.value(1).toInt()); lastSyncInformation.insert(prefix + "_replica_transaction_id",query.value(0).toString()); return lastSyncInformation; } } else{ setError(query.lastError().text()); } return lastSyncInformation; } QT_END_NAMESPACE_U1DB #include "moc_database.cpp" u1db-qt-0.1.5+14.04.20140313/src/dbschema.sql0000644000015201777760000000205412310435504020440 0ustar pbusernogroup00000000000000-- Database schema CREATE TABLE transaction_log ( generation INTEGER PRIMARY KEY AUTOINCREMENT, doc_id TEXT NOT NULL, transaction_id TEXT NOT NULL ); CREATE TABLE document ( doc_id TEXT PRIMARY KEY, doc_rev TEXT NOT NULL, content TEXT ); CREATE TABLE document_fields ( doc_id TEXT NOT NULL, field_name TEXT NOT NULL, value TEXT ); CREATE INDEX document_fields_field_value_doc_idx ON document_fields(field_name, value, doc_id); CREATE TABLE sync_log ( replica_uid TEXT PRIMARY KEY, known_generation INTEGER, known_transaction_id TEXT ); CREATE TABLE conflicts ( doc_id TEXT, doc_rev TEXT, content TEXT, CONSTRAINT conflicts_pkey PRIMARY KEY (doc_id, doc_rev) ); CREATE TABLE index_definitions ( name TEXT, offset INT, field TEXT, CONSTRAINT index_definitions_pkey PRIMARY KEY (name, offset) ); create index index_definitions_field on index_definitions(field); CREATE TABLE u1db_config ( name TEXT PRIMARY KEY, value TEXT ); INSERT INTO u1db_config VALUES ('sql_schema', '0'); u1db-qt-0.1.5+14.04.20140313/src/query.cpp0000644000015201777760000001767612310435522020042 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "query.h" #include "database.h" #include "private.h" QT_BEGIN_NAMESPACE_U1DB /*! \class Query \inmodule U1db \ingroup modules \brief The Query class generates a filtered list of documents based on a query using the given Index. Query can be used as a QAbstractListModel, delegates will then have access to \a docId and \a contents analogous to the properties of Document. */ /*! Instantiate a new Query with an optional \a parent, usually by declaring it as a QML item. */ Query::Query(QObject *parent) : QAbstractListModel(parent), m_index(0) { } /*! \internal *Used to implement QAbstractListModel *Implements the variables exposed to the Delegate in a model */ QVariant Query::data(const QModelIndex & index, int role) const { if (role == 0) // contents return m_results.at(index.row()); if (role == 1) // docId return m_documents.at(index.row()); return QVariant(); } /*! \internal Used to implement QAbstractListModel Defines \b{contents} and \b{docId} as variables exposed to the Delegate in a model \b{index} is supported out of the box. */ QHash Query::roleNames() const { QHash roles; roles.insert(0, "contents"); roles.insert(1, "docId"); return roles; } /*! \internal Used to implement QAbstractListModel The number of rows: the number of documents given by the query. */ int Query::rowCount(const QModelIndex & parent) const { return m_results.count(); } Index* Query::getIndex() { return m_index; } /*! Emitted whenever the index or documents change, and the results need to be updated. */ void Query::onDataInvalidated() { m_documents.clear(); m_results.clear(); if (!m_index) return; generateQueryResults(); } /*! \internal Manually triggers reloading of the query. */ void Query::generateQueryResults() { QList results(m_index->getAllResults()); /* Convert "*" or 123 or "aa" into a list */ /* Also convert ["aa", 123] into [{foo:"aa", bar:123}] */ QVariantList queryList(m_query.toList()); if (queryList.empty()) { // * is the default if query is empty if (!m_query.isValid()) queryList.append(QVariant(QString("*"))); else queryList.append(m_query); } if (queryList.at(0).type() != QVariant::Map) { QVariantList oldQueryList(queryList); QListIterator j(oldQueryList); QListIterator k(m_index->getExpression()); while(j.hasNext() && k.hasNext()) { QVariant j_value = j.next(); QString k_value = k.next(); QVariantMap valueMap; // Strip hierarchical components if (k_value.contains(".")) valueMap.insert(k_value.split(".").last(), j_value); else valueMap.insert(k_value, j_value); queryList.append(QVariant(valueMap)); } } Q_FOREACH (QVariantMap mapIdResult, results) { QString docId((mapIdResult["docId"]).toString()); QVariant result_variant(mapIdResult["result"]); QVariantMap result(result_variant.toMap()); QMapIterator j(result); bool match = true; while(j.hasNext()){ j.next(); if (!iterateQueryList(queryList, j.key(), j.value())) { match = false; break; } } if(match == true){ // Results must be unique and not empty aka deleted if (!m_documents.contains(docId) && result_variant.isValid()) { m_documents.append(docId); m_results.append(result); } } } resetModel(); Q_EMIT documentsChanged(m_documents); Q_EMIT resultsChanged(m_results); } /*! * \brief Query::resetModel * * Resets the model of the Query * */ void Query::resetModel(){ beginResetModel(); endResetModel(); } /*! \internal Loop through the query assuming it's a list. For example: queryList: { type: 'show' } field: 'type' value: 'show' */ bool Query::iterateQueryList(QVariantList queryList, QString field, QVariant value) { QListIterator j(queryList); while (j.hasNext()) { QVariant j_value = j.next(); QVariantMap valueMap(j_value.toMap()); if (!queryMap(valueMap, value.toString(), field)) return false; } return true; } /*! \internal Verify that query is an identical or wild card match. */ bool Query::queryMatchesValue(QString query, QString value) { if (query == "*") return true; if (query == value) return true; if (!query.contains ("*")) return false; QString prefix(query.split("*")[0]); return value.startsWith(prefix, Qt::CaseSensitive); } /*! \internal Handle different types of string values including wildcards. */ bool Query::queryString(QString query, QVariant value) { QString typeName = value.typeName(); if (typeName == "QVariantList") { Q_FOREACH (QVariant value_string, value.toList()) { if (queryString(query, value_string.toString())) return true; } return false; } return queryMatchesValue(query, value.toString()); } /*! \internal Loop through the given map of keys and queries. For example: map: { type: 'show' } value: { 'show' } field: 'type' */ bool Query::queryMap(QVariantMap map, QString value, QString field) { QMapIterator k(map); while(k.hasNext()){ k.next(); QString k_key = k.key(); QVariant k_variant = k.value(); QString query = k_variant.toString(); if(field == k_key){ if (!queryMatchesValue(query, value)) return false; } } return true; } /*! \property Query::index Sets the Index to use. The index must have a valid name and index expressions. If no query is set, the default is all results of the index. */ void Query::setIndex(Index* index) { if (m_index == index) return; if (m_index) QObject::disconnect(m_index, 0, this, 0); m_index = index; if (m_index){ QObject::connect(m_index, &Index::dataInvalidated, this, &Query::onDataInvalidated); } Q_EMIT indexChanged(index); onDataInvalidated(); } QVariant Query::getQuery() { return m_query; } /*! \property Query::query A query in one of the allowed forms: 'value', ['value'] or [{'sub-field': 'value'}]. The default is equivalent to '*'. */ void Query::setQuery(QVariant query) { if (m_query == query) return; m_query = query; Q_EMIT queryChanged(query); onDataInvalidated(); } /*! \property Query::documents The docId's of all matched documents. */ QStringList Query::getDocuments() { return m_documents; } /*! \property Query::results The results of the query as a list. */ QList Query::getResults() { return m_results; } QT_END_NAMESPACE_U1DB #include "moc_query.cpp" u1db-qt-0.1.5+14.04.20140313/src/database.h0000644000015201777760000000652712310435504020077 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef U1DB_DATABASE_H #define U1DB_DATABASE_H #include "global.h" #include #include #include #include QT_BEGIN_NAMESPACE_U1DB class Q_DECL_EXPORT Database : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QString path READ getPath WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString error READ lastError NOTIFY errorChanged) public: Database(QObject* parent = 0); // QAbstractListModel QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; QHashroleNames() const; int rowCount(const QModelIndex & parent = QModelIndex()) const; void resetModel(); QString getPath(); void setPath(const QString& path); Q_INVOKABLE QVariant getDoc(const QString& docId); QString getDocumentContents(const QString& docId); QVariant getDocUnchecked(const QString& docId) const; Q_INVOKABLE QString putDoc(QVariant newDoc, const QString& docID=QString()); Q_INVOKABLE void deleteDoc(const QString& docID); Q_INVOKABLE QList listDocs(); Q_INVOKABLE QString lastError(); Q_INVOKABLE QString putIndex(const QString& index_name, QStringList expressions); Q_INVOKABLE QStringList getIndexExpressions(const QString& indexName); Q_INVOKABLE QStringList getIndexKeys(const QString& indexName); /* Functions handy for Synchronization */ QString getNextDocRevisionNumber(QString doc_id); QString getCurrentDocRevisionNumber(QString doc_id); void updateDocRevisionNumber(QString doc_id,QString revision); void updateSyncLog(bool insert, QString uid, QString generation, QString transaction_id); QList listTransactionsSince(int generation); QMap getSyncLogInfo(QMap lastSyncInformation, QString uid, QString prefix); Q_SIGNALS: void pathChanged(const QString& path); void errorChanged(const QString& error); /*! A document's contents were modified. */ void docChanged(const QString& docId, QVariant content); /*! A document was loaded via its docID. */ void docLoaded(const QString& docId, QVariant content) const; private: //Q_DISABLE_COPY(Database) QString m_path; QSqlDatabase m_db; QString m_error; QString getReplicaUid(); bool isInitialized(); bool initializeIfNeeded(const QString& path=":memory:"); bool setError(const QString& error); QString getDocIdByRow(int row) const; int createNewTransaction(QString doc_id); QString generateNewTransactionId(); int getCurrentGenerationNumber(); }; QT_END_NAMESPACE_U1DB #endif // U1DB_DATABASE_H u1db-qt-0.1.5+14.04.20140313/src/query.h0000644000015201777760000000505312310435504017471 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Christian Dywan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef U1DB_QUERY_H #define U1DB_QUERY_H #include #include #include "index.h" QT_BEGIN_NAMESPACE_U1DB class Q_DECL_EXPORT Query : public QAbstractListModel { Q_OBJECT #ifdef Q_QDOC Q_PROPERTY(Index* index READ getIndex WRITE setIndex NOTIFY indexChanged) #else Q_PROPERTY(QT_PREPEND_NAMESPACE_U1DB(Index*) index READ getIndex WRITE setIndex NOTIFY indexChanged) #endif Q_PROPERTY(QVariant query READ getQuery WRITE setQuery NOTIFY queryChanged) Q_PROPERTY(QStringList documents READ getDocuments NOTIFY documentsChanged) Q_PROPERTY(QList results READ getResults NOTIFY resultsChanged) public: Query(QObject* parent = 0); // QAbstractListModel QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; QHashroleNames() const; int rowCount(const QModelIndex & parent = QModelIndex()) const; Index* getIndex(); void setIndex(Index* index); QVariant getQuery(); void setQuery(QVariant query); QStringList getDocuments(); QList getResults(); void resetModel(); Q_SIGNALS: void indexChanged(Index* index); void queryChanged(QVariant query); void documentsChanged(QStringList documents); void resultsChanged(QList results); private: Q_DISABLE_COPY(Query) Index* m_index; QStringList m_documents; QList m_results; QVariant m_query; void onDataInvalidated(); bool debug(); void generateQueryResults(); bool iterateQueryList(QVariantList list, QString field, QVariant value); bool queryMatchesValue(QString query, QString value); bool queryString(QString query, QVariant value); bool queryMap(QVariantMap map, QString value, QString field); bool queryField(QString field, QVariant value); }; QT_END_NAMESPACE_U1DB #endif // U1DB_QUERY_H u1db-qt-0.1.5+14.04.20140313/src/synchronizer.cpp0000644000015201777760000011142212310435504021412 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "synchronizer.h" #include "private.h" QT_BEGIN_NAMESPACE_U1DB /*! \class Synchronizer \inmodule U1Db \ingroup modules \brief The Synchronizer class handles synchronizing between two databases. */ /* Below this line are general methods for this class, such as setting/getting values for various properties. */ /*! Create a new Synchronizer element, with an optional \a parent, usually by declaring it as a QML item. Synchronizer elements sync two databases together, a 'source' database and a remote or local 'target' database. Example use in a QML application: U1db.Synchronizer{ id: aSynchronizer synchronize: false source: aDatabase targets: [{remote:true, ip:"127.0.0.1", port: 7777, name:"example1.u1db", resolve_to_source:true}] } Short description of properties: id: The element's identification. bool synchronize: Is the element actively synching or not. Should be set to false. U1DB::Database source: The id of a local database that will be used for synchronization. QVariant targets: One or more target databases that will be synched with the local database. bool targets.remote: Is the target database a remote or local database. QString targets.ip: The ip address of a remote database (if applicable). int targets.port: Port number of the remote server. QString targets.name: The name of the database. bool targets.resolve_to_source: In case of conflict should the sync resolve to the source's data (if true). Example use with u1db-serve: 1. In a terminal cd into a directory where the u1db Python reference implemented has been downloaded from lp:u1db. 2. Using Python create a database called 'example1.u1db' using u1db, and a document 'helloworld': # python >>> import u1db >>> db = u1db.open("example1.u1db",create=True) >>> content = {"hello": { "world": { "message":"Hello World Updated" } } } >>> db.create_doc(content, doc_id="helloworld") ctrl+d 3. From the u1db directory above type './u1db-serve --port=7777' and hit enter. 4. Open another terminal tab. 5. Change into a directory containing u1db-qt (assuming this class is included in that directory and the installed version on the host computer). 6. Change into the directory where u1db-qt-example-6.qml is located. 7. Type 'qmlscene u1db-qt-example-6.qml' and hit enter. 8. Click the button labelled 'Sync'. 9. Check the terminal windows for output from either the client or server. */ Synchronizer::Synchronizer(QObject *parent) : QAbstractListModel(parent), m_synchronize(false), m_source(NULL) { QObject::connect(this, &Synchronizer::syncChanged, this, &Synchronizer::onSyncChanged); } /*! \internal *Used to implement QAbstractListModel *Implements the variables exposed to the Delegate in a model */ QVariant Synchronizer::data(const QModelIndex & index, int role) const { if (role == 0) return m_sync_output.at(index.row()); return QVariant(); } /*! \internal Used to implement QAbstractListModel The number of rows: the number of documents given by the query. */ int Synchronizer::rowCount(const QModelIndex & parent) const { return m_sync_output.count(); } /*! \internal Used to implement QAbstractListModel */ QHash Synchronizer::roleNames() const { QHash roles; roles.insert(0, "sync_output"); return roles; } /*! Sets the source database. */ void Synchronizer::setSource(Database* source) { if (m_source == source) return; if (m_source) QObject::disconnect(m_source, 0, this, 0); m_source = source; Q_EMIT sourceChanged(source); } /*! * \property Synchronizer::targets * * Sets meta-data for databases to be used during a synchronization session. * * The QVariant is a list that can contain definitions for more than one database * to be used as a target. For example: * * targets: [{remote:true}, * {remote:true, * ip:"127.0.0.1", * port: 7777, * name:"example1.u1db", * resolve_to_source:true}, * {remote:"OK"}] * * The above example defines three databases. Two of the three definitions in the * example are invalid, the first ({remote:true}) and the third ({remote:"OK"}), * because they are incomplete. * * The second definition is a fully defined and valid definition for a local to * remote synchronization of two databases: * * {remote:true, * ip:"127.0.0.1", * port: 7777, * name:"example1.u1db", * resolve_to_source:true} * * 'remote' determines whether the database is on disk or located on a server. * 'ip' and 'port' for a server are used only when 'remote' is set to true * 'name' is the name of the local (on disk) or remote database. * Note: If 'remote' is false this is the relative/absolute file location. * 'resolve_to_source' determines whether to resolve conflicts automatically * in favor of the source (aka local) database's values or the target's. * */ void Synchronizer::setTargets(QVariant targets) { if (m_targets == targets) return; //if (m_targets) // QObject::disconnect(m_targets, 0, this, 0); m_targets = targets; Q_EMIT targetsChanged(targets); } /*! * \property Synchronizer::synchronize */ void Synchronizer::setSync(bool synchronize) { if (m_synchronize == synchronize) return; m_synchronize = synchronize; Q_EMIT syncChanged(synchronize); } /*! * \property Synchronizer::resolve_to_source */ void Synchronizer::setResolveToSource(bool resolve_to_source) { if (m_resolve_to_source == resolve_to_source) return; m_resolve_to_source = resolve_to_source; Q_EMIT resolveToSourceChanged(resolve_to_source); } /*! * \fn void Synchronizer::setSyncOutput(QList sync_output) * * Sets the current value for the active session's \a sync_output. * */ void Synchronizer::setSyncOutput(QList sync_output) { if (m_sync_output == sync_output) return; m_sync_output = sync_output; Q_EMIT syncOutputChanged(sync_output); } /*! * \property Synchronizer::source * * * Returns a source Database. * */ Database* Synchronizer::getSource() { return m_source; } /*! * \brief Synchronizer::getTargets * * * Returns meta-data for all target databases. * */ QVariant Synchronizer::getTargets() { return m_targets; } /*! * \brief Synchronizer::getSync * * * Returns the current value of synchronize. If true then the synchronize * session is initiated. * * This should probaby always be set to false on application start up. * The application developer should use some trigger to switch it to true * when needed (e.g. button click). * */ bool Synchronizer::getSync() { return m_synchronize; } /*! * \brief Synchronizer::getResolveToSource * * * If set to true, any document conflicts created during a sync session * will be resolved in favor of the content from the source database. If false * the content from the target database will replace the document content in * the source database. * */ bool Synchronizer::getResolveToSource(){ return m_resolve_to_source; } /*! * \property Synchronizer::sync_output * \brief Synchronizer::getSyncOutput * * Returns the output from a sync session. The list should contain numerous * QVariantMaps, each of which will have various meta-data with informative * information about what happened in the background of the session. * * In some cases the information will be about errors or warnings, and in * other cases simple log messages. Also included would noramlly be associated * properties, elements and other data. * * The information can be used in any number of ways, such as on screen within an app, * testing, console output, logs and more. This is designed to be flexible enough that * the app developer can decide themselves how to best use the data. * */ QList Synchronizer::getSyncOutput(){ return m_sync_output; } /* Below this line represents the class' more unique functionality. In other words, methods that do more than simply modify/retrieve an element's property values. */ /*! * \brief Synchronizer::onSyncChanged * * The synchroization process begins here. * */ void Synchronizer::onSyncChanged(bool synchronize){ Database* source = getSource(); QList sync_targets; /*! * The validator map contains key and value pair definitions, *that are used to confirm that the values provided for each *database target are of the expected type for a particular key. */ QMapvalidator; validator.insert("remote","bool"); validator.insert("location","QString"); validator.insert("resolve_to_source","bool"); /*! * The mandatory map contains the keys that are used to confirm *that a database target definition contains all the mandatory keys *necessary for synchronizing. */ QListmandatory; mandatory.append("remote"); mandatory.append("resolve_to_source"); if(synchronize == true){ /*! 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). */ sync_targets=getValidTargets(validator, mandatory); /*! * Once the list of sync targets has been generated the sync activity *can be initiated via the synchronizeTargets function. */ synchronizeTargets(source, sync_targets); /*! * After the synchronization is complete the model is reset so that *log and error messages are available at the application level. * */ beginResetModel(); endResetModel(); /*! The convenience signals syncOutputChanged and syncCompleted are emitted after the model has been reset. */ Q_EMIT syncOutputChanged(m_sync_output); Q_EMIT syncCompleted(); /*! * The sync boolean value is reset to its default value (false) *once all sync activity is complete. */ setSync(false); } else{ } } /*! * \internal * \brief Synchronizer::getValidTargets * * * This method confirms that each sync target definition is valid, based * on predefined criteria contained in the validator and mandatory lists. * */ QList Synchronizer::getValidTargets(QMapvalidator, QListmandatory){ QList sync_targets; int index = 0; QList targets = getTargets().toList(); Q_FOREACH (QVariant target_variant, targets) { index++; QString index_number = QString::number(index); QMap target = target_variant.toMap(); bool valid = true; bool complete = true; QMapIterator i(target); while (i.hasNext()) { i.next(); if(validator.contains(i.key())&&validator[i.key()]!=i.value().typeName()){ valid = false; QString message_value = "For property `" + i.key() + "` Expecting type `" + validator[i.key()] + "`, but received type `" + i.value().typeName()+"`"; QVariantMap output_map; output_map.insert("concerning_property","targets"); output_map.insert("concerning_index",index_number); output_map.insert("message_type","error"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); target.insert("sync",false); break; } if(valid==false){ targets.removeOne(target); break; } else{ QListIterator j(mandatory); while(j.hasNext()){ QString value = j.next(); if(!target.contains(value)){ QString message_value = "Expected property `" + value + "`, but it is not present."; QVariantMap output_map; output_map.insert("concerning_property","targets"); output_map.insert("concerning_index",index_number); output_map.insert("message_type","error"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); target.insert("sync",false); targets.removeOne(target); complete = false; break; } } if(complete==false){ break; } } } if(target.contains("sync")&&target["sync"]==false){ QString message_value = "Not synced due to errors with properties."; QVariantMap output_map; output_map.insert("concerning_property","targets"); output_map.insert("concerning_index",index_number); output_map.insert("message_type","error"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); } else { target.insert("sync",true); sync_targets.append(target); QString message_value = "Mandatory properties were included and their values are valid."; QVariantMap output_map; output_map.insert("concerning_property","targets"); output_map.insert("concerning_index",index_number); output_map.insert("message_type","no-errors"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); } } return sync_targets; } /*! * \internal * \brief Synchronizer::synchronizeTargets * * The source database is synchronized with the target databases contained * in the 'targets' list. That list should only contain valid targets, as * determined by Synchronizer::getValidTargets. * */ void Synchronizer::synchronizeTargets(Database *source, QVariant targets){ if(targets.typeName()== QStringLiteral("QVariantList")){ QList target_list = targets.toList(); QListIterator i(target_list); int target_index = -1; while(i.hasNext()){ target_index++; QVariant target = i.next(); if(target.typeName()== QStringLiteral("QVariantMap")){ QMap target_map = target.toMap(); if(target_map.contains("remote")&&target_map["remote"]==false){ if(target_map.contains("sync")&&target_map["sync"]==true){ QString message_value = "Valid local target."; QVariantMap output_map; output_map.insert("concerning_property","targets"); output_map.insert("concerning_index",target_index); output_map.insert("message_type","no-errors"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); syncLocalToLocal(source, target_map); } } else if(target_map.contains("remote")&&target_map["remote"]==true){ if(target_map.contains("sync")&&target_map["sync"]==true){ //ip //port //name //GET /thedb/sync-from/my_replica_uid QString source_uid = getUidFromLocalDb(source->getPath()); QString get_string = target_map["name"].toString()+"/sync-from/"+source_uid; QString url_string = "http://"+target_map["ip"].toString(); QString full_get_request = url_string+"/"+get_string; int port_number = target_map["port"].toInt(); QNetworkAccessManager *manager = new QNetworkAccessManager(source); QUrl url(full_get_request); url.setPort(port_number); QNetworkRequest request(url); connect(manager, &QNetworkAccessManager::finished, this, &Synchronizer::remoteGetSyncInfoFinished); QString message_value = "Valid remote target."; QVariantMap output_map; output_map.insert("concerning_property","targets"); output_map.insert("concerning_index",target_index); output_map.insert("message_type","no-errors"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); manager->get(QNetworkRequest(request)); } } else{ QString message_value = "Unknown error. Please check properties"; QVariantMap output_map; output_map.insert("concerning_property","targets"); output_map.insert("concerning_index",target_index); output_map.insert("message_type","error"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); } } } } } /*! * \internal * \brief Synchronizer::syncLocalToLocal * * This function synchronizes two local databases, a source database and a target database. * */ void Synchronizer::syncLocalToLocal(Database *sourceDb, QMap target) { QString target_db_name = target["location"].toString(); Database *targetDb; Index *targetIndex; Query *targetQuery; Index *sourceIndex; Query *sourceQuery; if(target.contains("id")){ targetDb = (Database*)target["id"].value(); } else if(target.contains("target_query")){ targetQuery = (Query*)target["target_query"].value(); targetIndex = targetQuery->getIndex(); targetDb = targetIndex->getDatabase(); } else targetDb = NULL; if(target.contains("source_query")){ sourceQuery = (Query*)target["source_query"].value(); sourceIndex = sourceQuery->getIndex(); sourceDb = sourceIndex->getDatabase(); } if(sourceDb == NULL || targetDb == NULL){ QString message_value = "Either source or target does not exist or is not active."; QVariantMap output_map; output_map.insert("concerning_property","source|targets"); //output_map.insert("concerning_index",index_number); // no access to targets index? output_map.insert("message_type","error"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); return; } QMap lastSyncInformation; lastSyncInformation.insert("target_replica_uid",getUidFromLocalDb(target_db_name)); lastSyncInformation.insert("target_replica_generation",""); lastSyncInformation.insert("target_replica_transaction_id",-1); lastSyncInformation.insert("source_replica_uid",getUidFromLocalDb(sourceDb->getPath())); lastSyncInformation.insert("source_replica_generation",""); lastSyncInformation.insert("source_replica_transaction_id",-1); lastSyncInformation = getLastSyncInformation(sourceDb, targetDb, false, lastSyncInformation); QList transactionsFromSource; QList transactionsFromTarget; // Check if target and source have ever been synced before 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) { QString message_value = "Source and local database have previously synced."; QVariantMap output_map; output_map.insert("concerning_property","source|targets"); output_map.insert("concerning_source",sourceDb->getPath()); output_map.insert("concerning_target",target_db_name); output_map.insert("message_type","no-errors"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); //Do some syncing transactionsFromSource = sourceDb->listTransactionsSince(lastSyncInformation["source_replica_generation"].toInt()); transactionsFromTarget = targetDb->listTransactionsSince(lastSyncInformation["target_replica_generation"].toInt()); } else{ QString message_value = "Source and local database have not previously synced."; QVariantMap output_map; output_map.insert("concerning_property","source|targets"); output_map.insert("concerning_source",sourceDb->getPath()); output_map.insert("concerning_target",target_db_name); output_map.insert("message_type","no-errors"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); //There is a first time for everything, let's sync! transactionsFromSource = sourceDb->listTransactionsSince(0); transactionsFromTarget = targetDb->listTransactionsSince(0); } /*! * With two distinct lists present, it is now possible to check what * updates should be made, or new documents created in one or the other * database, depending on conditions. * * However, two additional lists containing transactions IDs are required * because the information is contained within delimited strings (see * below for details). * */ QList transactionIdsFromSource; QList transactionIdsFromTarget; Q_FOREACH(QString sourceTransaction, transactionsFromSource){ /*! * Each sourceTransaction is a pipe delimited string containing * generation number, document ID, and transaction ID details * in that order. * * Splitting the string into its component pieces provides a * document ID (the second key in the list). */ QStringList transactionDetails = sourceTransaction.split("|"); /*! * It is only necessary to have unique instances of the * document ID. */ if(!transactionIdsFromSource.contains(transactionDetails[1])) transactionIdsFromSource.append(transactionDetails[1]); } Q_FOREACH(QString targetTransaction, transactionsFromTarget){ /*! * Each targetTransaction is a pipe delimited string containing * generation number, document ID, and transaction ID details * in that order. * * Splitting the string into its component pieces provides a * document ID (the second key in the list). */ QStringList transactionDetails = targetTransaction.split("|"); /*! * It is only necessary to have unique instances of the * document ID. */ if(!transactionIdsFromTarget.contains(transactionDetails[1])) transactionIdsFromTarget.append(transactionDetails[1]); } /* The source replica asks the target replica for the information it has stored about the last time these two replicas were synchronised (if ever).*/ /* The application wishing to synchronise sends the following GET request to the server: GET /thedb/sync-from/my_replica_uid 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 */ /* The target responds with a JSON document that looks like this: { "target_replica_uid": "other_replica_uid", "target_replica_generation": 12, "target_replica_transaction_id": "T-sdkfj92292j", "source_replica_uid": "my_replica_uid", "source_replica_generation": 23, "source_transaction_id": "T-39299sdsfla8" } 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. */ /* 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.) */ /* The source replica generates a list of changes since the last change the target replica knows of. */ /* The source replica checks what the last change is it knows about on the target replica. */ /* If there have been no changes on either replica that the other side has not seen, the synchronisation stops here. */ /* 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. */ /* The target processes the changed documents, and records the source replica’s latest change. */ /* The target responds with the documents that have changes that the source does not yet know about. */ /* The source processes the changed documents, and records the target replica’s latest change. */ /* 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.*/ } /*! * \internal * \brief Synchronizer::syncDocument * * * This method is used to synchronize documents from one local database * to another local database. * */ QVariant Synchronizer::syncDocument(Database *from, Database *to, QString docId) { QVariant document = from->getDoc(docId); to->putDoc(document, docId); QString revision = from->getCurrentDocRevisionNumber(docId); to->updateDocRevisionNumber(docId,revision); return document; } /*! * \internal * \brief Synchronizer::getLastSyncInformation * * * If the source and target database have ever been synced before the information * from that previous session is returned. This is only used for local to local * databases. The local to remote procedure is handled elsewhere in a different manner. * */ QMap Synchronizer::getLastSyncInformation(Database *sourceDb, Database *targetDb, bool remote, QMap lastSyncInformation){ if(remote == true){ QString message_value = "Sync information from remote target not available at this time."; QVariantMap output_map; output_map.insert("concerning_property","source|targets"); output_map.insert("concerning_source",sourceDb->getPath()); output_map.insert("message_type","warning"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); return lastSyncInformation; } else{ lastSyncInformation["source_replica_uid"].toString(); lastSyncInformation = targetDb->getSyncLogInfo(lastSyncInformation, lastSyncInformation["source_replica_uid"].toString(),"target"); lastSyncInformation = sourceDb->getSyncLogInfo(lastSyncInformation, lastSyncInformation["target_replica_uid"].toString(),"source"); } return lastSyncInformation; } /*! * \internal * \brief Synchronizer::getUidFromLocalDb * * * The unique id of a database is needed in certain situations of a * synchronize session. This method retrieves that id from a local database. * */ QString Synchronizer::getUidFromLocalDb(QString dbFileName) { QString dbUid; QSqlDatabase db; db = QSqlDatabase::addDatabase("QSQLITE",QUuid::createUuid().toString()); QFile db_file(dbFileName); if(!db_file.exists()) { QString message_value = "Database does not exist."; QVariantMap output_map; output_map.insert("concerning_property","source|targets"); output_map.insert("concerning_database",dbFileName); output_map.insert("message_type","error"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); return dbUid; } else { db.setDatabaseName(dbFileName); if (!db.open()){ QString message_value = db.lastError().text(); QVariantMap output_map; output_map.insert("concerning_property","source|targets"); output_map.insert("concerning_database",dbFileName); output_map.insert("message_type","error"); output_map.insert("message_value",message_value); m_sync_output.append(output_map); } else{ QSqlQuery query (db.exec("SELECT value FROM u1db_config WHERE name = 'replica_uid'")); if(!query.lastError().isValid() && query.next()){ dbUid = query.value(0).toString(); db.close(); dbUid = dbUid.replace("{",""); dbUid = dbUid.replace("}",""); return dbUid; } else{ qDebug() << query.lastError().text(); db.close(); return dbUid; } } } return dbUid; } /*! * \internal * \brief Synchronizer::remoteGetSyncInfoFinished * * Once the initial exchange between the client application * and the remote server is complete, this method retrieves * necessary information from the reply that came from the * server, and is required for posting data from the client * in the steps that will follow. * * After the data is saved to a string and the network reply * is closed, the appropriate method for posting * (postDataFromClientToRemoteServer) is then called. * */ void Synchronizer::remoteGetSyncInfoFinished(QNetworkReply* reply) { QNetworkAccessManager *manager = reply->manager(); Database *source = qobject_cast(manager->parent()); QUrl postUrl = reply->request().url(); QByteArray data = reply->readAll(); QString replyData = QString(data); reply->close(); postDataFromClientToRemoteServer(source, postUrl, replyData); } /*! * \internal * \brief Synchronizer::postDataFromClientToRemoteServer * * This method builds a string for posting from the client * application to the remote server, using information previously * gathered from the source and target databases, * and then initiates the post. * */ void Synchronizer::postDataFromClientToRemoteServer(Database *source, QUrl postUrl, QString replyData) { QVariantMap replyMap; QJsonDocument replyJson = QJsonDocument::fromJson(replyData.toUtf8()); QVariant replyVariant = replyJson.toVariant(); replyMap = replyVariant.toMap(); double source_replica_generation = replyMap["source_replica_generation"].toDouble(); QString source_replica_uid = replyMap["source_replica_uid"].toString(); QString source_replica_transaction_id = replyMap["source_transaction_id"].toString(); //double target_replica_generation = replyMap["target_replica_generation"].toDouble(); QString target_replica_transaction_id = replyMap["target_replica_transaction_id"].toString(); QString target_replica_uid = replyMap["target_replica_uid"].toString(); QNetworkAccessManager *manager = new QNetworkAccessManager(source); connect(manager, &QNetworkAccessManager::finished, this, &Synchronizer::remotePostSyncInfoFinished); QByteArray postString; postString = "[\r\n"; postString.append("{\"last_known_generation\": "); postString.append(QByteArray::number(source_replica_generation)); postString.append(", \"last_known_trans_id\": \""); postString.append(source_replica_transaction_id); postString.append("\"}"); QList transactions = m_source->listTransactionsSince(source_replica_generation); Q_FOREACH(QString transaction,transactions){ QStringList transactionData = transaction.split("|"); QString content = source->getDocumentContents(transactionData[1]); content = content.replace("\r\n",""); content = content.replace("\r",""); content = content.replace("\n",""); content = content.replace("\"","\\\""); postString.append(",\r\n{\"content\": \""+content+"\",\"rev\": \""+m_source->getCurrentDocRevisionNumber(transactionData[1])+"\", \"id\": \""+transactionData[1]+"\",\"trans_id\": \""+transactionData[2]+"\",\"gen\": "+transactionData[0]+"}"); } postString.append("\r\n]"); QByteArray postDataSize = QByteArray::number(postString.size()); QNetworkRequest request(postUrl); request.setRawHeader("User-Agent", "U1Db-Qt v1.0"); request.setRawHeader("X-Custom-User-Agent", "U1Db-Qt v1.0"); request.setRawHeader("Content-Type", "application/x-u1db-sync-stream"); request.setRawHeader("Content-Length", postDataSize); manager->post(QNetworkRequest(request),postString); } /*! * \internal * \brief Synchronizer::remotePostSyncInfoFinished * * This method is a slot, which is called once the data * from the client application has been posted to the remote * server. * * This is where any new data from the * remote server is gathered, and then the further steps * for processing on the client side are begun. */ void Synchronizer::remotePostSyncInfoFinished(QNetworkReply* reply) { QNetworkAccessManager *manager = reply->manager(); Database *source = qobject_cast(manager->parent()); QByteArray data = reply->readAll(); QString replyData = QString(data); reply->close(); processDataFromRemoteServer(source, replyData); } /*! * \internal * \brief Synchronizer::processDataFromRemoteServer * * After the remote target database has replied back, the data it has sent * is processed to determine what action to take on any relevant documents * that may have been included in the data. * */ void Synchronizer::processDataFromRemoteServer(Database *source, QString replyData) { replyData = replyData.replace("\r\n",""); QJsonDocument replyJson = QJsonDocument::fromJson(replyData.toUtf8()); QVariant replyVariant = replyJson.toVariant(); QVariantList replyList = replyVariant.toList(); QListIterator i(replyList); int index = -1; while(i.hasNext()){ index++; QVariant current = i.next(); QString type_name = QString::fromUtf8(current.typeName()); if(type_name == "QVariantMap") { QVariantMap map = current.toMap(); if(index == 0) { // Meta data } else { // Document to update QString id(""); QVariant content(""); QString rev(""); QMapIterator i(map); while (i.hasNext()) { i.next(); if(i.key()=="content") { content = i.value(); } else if(i.key()=="id") { id = i.value().toString(); } else if(i.key()=="rev") { rev = i.value().toString(); } } if(content!=""&&id!=""&&rev!="") { source->putDoc(content,id); source->updateDocRevisionNumber(id,rev); } } } } } QT_END_NAMESPACE_U1DB #include "moc_synchronizer.cpp" u1db-qt-0.1.5+14.04.20140313/src/synchronizer.h0000644000015201777760000000777412310435504021075 0ustar pbusernogroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Kevin Wright * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef U1DB_SYNCHRONIZER_H #define U1DB_SYNCHRONIZER_H #include #include #include #include #include #include #include "database.h" #include "index.h" #include "query.h" QT_BEGIN_NAMESPACE_U1DB class Q_DECL_EXPORT Synchronizer : public QAbstractListModel { Q_OBJECT #ifdef Q_QDOC Q_PROPERTY(Database* source READ getSource WRITE setSource NOTIFY sourceChanged) #else Q_PROPERTY(QT_PREPEND_NAMESPACE_U1DB(Database*) source READ getSource WRITE setSource NOTIFY sourceChanged) #endif Q_PROPERTY(bool synchronize READ getSync WRITE setSync NOTIFY syncChanged) Q_PROPERTY(bool resolve_to_source READ getResolveToSource WRITE setResolveToSource NOTIFY resolveToSourceChanged) Q_PROPERTY(QVariant targets READ getTargets WRITE setTargets NOTIFY targetsChanged) Q_PROPERTY(QList sync_output READ getSyncOutput NOTIFY syncOutputChanged) public: Synchronizer(QObject* parent = 0); // QAbstractListModel QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; QHashroleNames() const; int rowCount(const QModelIndex & parent = QModelIndex()) const; QList getValidTargets(QMapvalidator, QListmandatory); QMap getLastSyncInformation(Database *sourceDb, Database *targetDb, bool remote, QMap lastSyncInformation); Database* getSource(); QVariant getTargets(); bool getSync(); bool getResolveToSource(); QList getSyncOutput(); void setSource(Database* source); void setTargets(QVariant targets); void setSync(bool synchronize); void setResolveToSource(bool resolve_to_source); void setSyncOutput(QList sync_output); void syncLocalToLocal(Database *sourceDb, QMap target); void synchronizeTargets(Database *source, QVariant targets); QVariant syncDocument(Database *from, Database *to, QString docId); QString getUidFromLocalDb(QString dbFileName); void postDataFromClientToRemoteServer(Database *source, QUrl postUrl, QString replyData); void processDataFromRemoteServer(Database *source, QString replyData); Q_SIGNALS: /*! * \brief sourceChanged * \param source */ void sourceChanged(Database* source); /*! * \brief targetsChanged * \param targets */ void targetsChanged(QVariant targets); /*! * \brief syncChanged * \param synchronize */ void syncChanged(bool synchronize); /*! * \brief resolveToSourceChanged * \param resolve_to_source */ void resolveToSourceChanged(bool resolve_to_source); /*! * \brief syncOutputChanged * \param sync_output */ void syncOutputChanged(QList sync_output); /*! * \brief syncCompleted */ void syncCompleted(); private: //Q_DISABLE_COPY(Synchronizer) bool m_synchronize; Database* m_source; bool m_resolve_to_source; QVariant m_targets; QList m_sync_output; void onSyncChanged(bool synchronize); void remoteGetSyncInfoFinished(QNetworkReply* reply); void remotePostSyncInfoFinished(QNetworkReply* reply); }; QT_END_NAMESPACE_U1DB #endif // U1DB_SYNCHRONIZER_H u1db-qt-0.1.5+14.04.20140313/COPYING0000644000015201777760000001674312310435504016427 0ustar pbusernogroup00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.