\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.txt 0000644 0000152 0177776 00000003462 12310435504 022777 0 ustar pbuser nogroup 0000000 0000000 option(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.qdoc 0000644 0000152 0177776 00000021232 12310435504 022720 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.qdoc 0000644 0000152 0177776 00000002404 12310435504 022750 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.qdoc 0000644 0000152 0177776 00000017502 12310435504 023604 0 ustar pbuser nogroup 0000000 0000000 /*!
\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/ 0000755 0000152 0177776 00000000000 12310436007 021371 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/documentation/style/scratch.css 0000644 0000152 0177776 00000002610 12310435504 023532 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.css 0000644 0000152 0177776 00000001533 12310435504 023230 0 ustar pbuser nogroup 0000000 0000000 /*
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.css 0000644 0000152 0177776 00000001642 12310435504 023260 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.css 0000644 0000152 0177776 00000002154 12310435504 022676 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.css 0000644 0000152 0177776 00000033165 12310435504 023575 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.css 0000644 0000152 0177776 00000027067 12310435504 023032 0 ustar pbuser nogroup 0000000 0000000 /**
* 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.qdoc 0000644 0000152 0177776 00000000000 12310435504 024317 0 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/documentation/architecture.qdoc 0000644 0000152 0177776 00000000000 12310435504 023552 0 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/tests/ 0000755 0000152 0177776 00000000000 12310436007 016522 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/tests/tst_query.qml 0000644 0000152 0177776 00000025074 12310435522 021305 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.sh 0000755 0000152 0177776 00000001502 12310435504 023311 0 ustar pbuser nogroup 0000000 0000000 #!/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.cpp 0000644 0000152 0177776 00000003164 12310435504 021754 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.sh 0000755 0000152 0177776 00000002415 12310435504 021504 0 ustar pbuser nogroup 0000000 0000000 #!/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.txt 0000644 0000152 0177776 00000003707 12310435504 021272 0 ustar pbuser nogroup 0000000 0000000 add_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.py 0000755 0000152 0177776 00000006072 12310435504 021722 0 ustar pbuser nogroup 0000000 0000000 #!/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.sh 0000644 0000152 0177776 00000002015 12310435504 020022 0 ustar pbuser nogroup 0000000 0000000 #!/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.qml 0000644 0000152 0177776 00000012004 12310435504 021671 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.cpp 0000644 0000152 0177776 00000011630 12310435504 022717 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 017364 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/qtcreator/CMakeLists.txt 0000644 0000152 0177776 00000000327 12310435504 022127 0 ustar pbuser nogroup 0000000 0000000 set(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/ 0000755 0000152 0177776 00000000000 12310436007 021202 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/qtcreator/contacts/template.xml 0000644 0000152 0177776 00000001036 12310435504 023540 0 ustar pbuser nogroup 0000000 0000000
Showing a U1Db query in a ListViewUbuntu Components can be used to display a U1Db query.<br/>Any changes are updated automatically.<br/><br/>Requires <b>Qt 5.0</b> or newer.
u1db-qt-0.1.5+14.04.20140313/qtcreator/contacts/main.qml 0000644 0000152 0177776 00000004560 12310435504 022647 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 021224 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/qtcreator/settings/template.xml 0000644 0000152 0177776 00000001046 12310435504 023563 0 ustar pbuser nogroup 0000000 0000000
Storing app settings in U1DbA straightforward example of storing data in a U1Db document.<br/>The data can be displayed using standard QML items.<br/><br/>Requires <b>Qt 5.0</b> or newer.
u1db-qt-0.1.5+14.04.20140313/qtcreator/settings/main.qml 0000644 0000152 0177776 00000005755 12310435504 022700 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.txt 0000644 0000152 0177776 00000002500 12310435504 020116 0 ustar pbuser nogroup 0000000 0000000 project(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.in 0000644 0000152 0177776 00000000453 12310435504 020260 0 ustar pbuser nogroup 0000000 0000000 prefix=@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/ 0000755 0000152 0177776 00000000000 12310436007 017017 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/gallery/gallery.qml 0000644 0000152 0177776 00000002435 12310435504 021176 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.txt 0000644 0000152 0177776 00000000325 12310435504 021560 0 ustar pbuser nogroup 0000000 0000000 install(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.desktop 0000644 0000152 0177776 00000000364 12310435504 023330 0 ustar pbuser nogroup 0000000 0000000 [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.qml 0000644 0000152 0177776 00000003666 12310435504 021474 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 017030 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/modules/U1db/ 0000755 0000152 0177776 00000000000 12310436007 017623 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/modules/U1db/plugin.h 0000644 0000152 0177776 00000001755 12310435504 021303 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.txt 0000644 0000152 0177776 00000003110 12310435504 022357 0 ustar pbuser nogroup 0000000 0000000 find_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/qmldir 0000644 0000152 0177776 00000000036 12310435504 021036 0 ustar pbuser nogroup 0000000 0000000 module U1db
plugin U1DBPlugin
u1db-qt-0.1.5+14.04.20140313/modules/U1db/plugin.cpp 0000644 0000152 0177776 00000002270 12310435504 021627 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.txt 0000644 0000152 0177776 00000000027 12310435504 021570 0 ustar pbuser nogroup 0000000 0000000 add_subdirectory(U1db)
u1db-qt-0.1.5+14.04.20140313/examples/ 0000755 0000152 0177776 00000000000 12310436007 017176 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-5/ 0000755 0000152 0177776 00000000000 12310436007 022246 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-5/u1db-qt-example-5.qml 0000644 0000152 0177776 00000005433 12310435504 026037 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.qdoc 0000644 0000152 0177776 00000032671 12310435504 026200 0 ustar pbuser nogroup 0000000 0000000 /*!
\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/ 0000755 0000152 0177776 00000000000 12310436007 022244 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-3/u1db-qt-example-3.qml 0000644 0000152 0177776 00000034152 12310435504 026033 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.qdoc 0000644 0000152 0177776 00000001361 12310435504 026164 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.txt 0000644 0000152 0177776 00000000204 12310435504 021733 0 ustar pbuser nogroup 0000000 0000000 file(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/ 0000755 0000152 0177776 00000000000 12310436007 022243 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2/u1db-qt-example-2.qml 0000644 0000152 0177776 00000005451 12310435504 026031 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.qdoc 0000644 0000152 0177776 00000001361 12310435504 026162 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 022405 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-2b/u1db-qt-example-2b.qdoc 0000644 0000152 0177776 00000001362 12310435504 026467 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.qml 0000644 0000152 0177776 00000006537 12310435504 026343 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 022247 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-6/u1db-qt-example-6.qdoc 0000644 0000152 0177776 00000032745 12310435504 026204 0 ustar pbuser nogroup 0000000 0000000 /*!
\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.qml 0000644 0000152 0177776 00000011614 12310435504 026037 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 022242 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/u1db-qt-example-1/u1db-qt-example-1.qml 0000644 0000152 0177776 00000005377 12310435504 026036 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.qdoc 0000644 0000152 0177776 00000001361 12310435504 026160 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 021166 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/examples/bookmarks/bookmarks.qml 0000644 0000152 0177776 00000012415 12310435504 023675 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/ 0000755 0000152 0177776 00000000000 12310436007 016147 5 ustar pbuser nogroup 0000000 0000000 u1db-qt-0.1.5+14.04.20140313/src/sql.qrc 0000644 0000152 0177776 00000000165 12310435504 017460 0 ustar pbuser nogroup 0000000 0000000 dbschema.sql
u1db-qt-0.1.5+14.04.20140313/src/global.h 0000644 0000152 0177776 00000003251 12310435504 017562 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.h 0000644 0000152 0177776 00000004622 12310435504 020143 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.cpp 0000644 0000152 0177776 00000015752 12310435504 017775 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.h 0000644 0000152 0177776 00000001317 12310435504 017775 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.h 0000644 0000152 0177776 00000004722 12310435504 017435 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.txt 0000644 0000152 0177776 00000002372 12310435504 020714 0 ustar pbuser nogroup 0000000 0000000 set(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.cpp 0000644 0000152 0177776 00000011447 12310435504 020501 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.cpp 0000644 0000152 0177776 00000064525 12310435504 020434 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.sql 0000644 0000152 0177776 00000002054 12310435504 020440 0 ustar pbuser nogroup 0000000 0000000 -- 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.cpp 0000644 0000152 0177776 00000017676 12310435522 020042 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.h 0000644 0000152 0177776 00000006527 12310435504 020077 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.h 0000644 0000152 0177776 00000005053 12310435504 017471 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.cpp 0000644 0000152 0177776 00000111422 12310435504 021412 0 ustar pbuser nogroup 0000000 0000000 /*
* 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.h 0000644 0000152 0177776 00000007774 12310435504 021075 0 ustar pbuser nogroup 0000000 0000000 /*
* 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/COPYING 0000644 0000152 0177776 00000016743 12310435504 016427 0 ustar pbuser nogroup 0000000 0000000 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.