summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt22
-rw-r--r--README.md (renamed from README)11
-rwxr-xr-xbuilddocs.sh4
-rw-r--r--common/CMakeLists.txt37
-rw-r--r--common/SinkConfig.cmake.in (renamed from common/SinkCommonConfig.cmake.in)2
-rw-r--r--common/clientapi.h140
-rw-r--r--common/commands.cpp2
-rw-r--r--common/commands.h11
-rw-r--r--common/commands/notification.fbs2
-rw-r--r--common/definitions.h5
-rw-r--r--common/domain/applicationdomaintype.cpp45
-rw-r--r--common/domain/applicationdomaintype.h60
-rw-r--r--common/domain/event.cpp2
-rw-r--r--common/domainadaptor.h3
-rw-r--r--common/domaintypeadaptorfactoryinterface.h6
-rw-r--r--common/entitybuffer.cpp7
-rw-r--r--common/entitybuffer.h5
-rw-r--r--common/facade.cpp9
-rw-r--r--common/facade.h6
-rw-r--r--common/facadefactory.h3
-rw-r--r--common/genericresource.cpp77
-rw-r--r--common/genericresource.h6
-rw-r--r--common/index.h3
-rw-r--r--common/inspection.h2
-rw-r--r--common/listener.cpp17
-rw-r--r--common/listener.h3
-rw-r--r--common/listmodelresult.h125
-rw-r--r--common/log.cpp165
-rw-r--r--common/log.h75
-rw-r--r--common/messagequeue.cpp4
-rw-r--r--common/messagequeue.h3
-rw-r--r--common/modelresult.cpp3
-rw-r--r--common/notification.h4
-rw-r--r--common/notifier.cpp69
-rw-r--r--common/notifier.h (renamed from common/listmodelresult.cpp)29
-rw-r--r--common/pipeline.cpp174
-rw-r--r--common/pipeline.h8
-rw-r--r--common/propertymapper.h13
-rw-r--r--common/queryrunner.cpp49
-rw-r--r--common/queryrunner.h11
-rw-r--r--common/resource.cpp15
-rw-r--r--common/resource.h11
-rw-r--r--common/resourceaccess.cpp78
-rw-r--r--common/resourceaccess.h6
-rw-r--r--common/resourceconfig.h3
-rw-r--r--common/resourcecontrol.cpp125
-rw-r--r--common/resourcecontrol.h62
-rw-r--r--common/resultprovider.h13
-rw-r--r--common/storage.h13
-rw-r--r--common/storage_common.cpp5
-rw-r--r--common/storage_lmdb.cpp14
-rw-r--r--common/store.cpp (renamed from common/clientapi.cpp)140
-rw-r--r--common/store.h101
-rw-r--r--common/threadboundary.h4
-rw-r--r--common/typeindex.cpp7
-rw-r--r--docs/building.md12
-rw-r--r--docs/clientapi.md129
-rw-r--r--docs/design.md104
-rw-r--r--docs/designgoals.md39
-rw-r--r--docs/index.md25
-rw-r--r--docs/logging.md27
-rw-r--r--docs/queries.md104
-rw-r--r--docs/resource.md59
-rw-r--r--docs/sinksh.md (renamed from docs/akonadish.md)0
-rw-r--r--docs/storage.md23
-rw-r--r--docs/terminology.md2
-rw-r--r--docs/tradeoffs.md36
-rw-r--r--examples/client/CMakeLists.txt2
-rw-r--r--examples/client/main.cpp3
-rw-r--r--examples/dummyresource/CMakeLists.txt2
-rw-r--r--examples/dummyresource/resourcefactory.cpp10
-rw-r--r--examples/dummyresource/resourcefactory.h1
-rw-r--r--examples/maildirresource/CMakeLists.txt2
-rw-r--r--examples/maildirresource/facade.cpp30
-rw-r--r--examples/maildirresource/facade.h1
-rw-r--r--examples/maildirresource/libmaildir/maildir.cpp193
-rw-r--r--examples/maildirresource/libmaildir/maildir.h30
-rw-r--r--examples/maildirresource/maildirresource.cpp82
-rw-r--r--examples/maildirresource/maildirresource.h5
-rw-r--r--hawd_defs/facade_query8
-rw-r--r--mkdocs.yml23
-rw-r--r--sinksh/CMakeLists.txt3
-rw-r--r--sinksh/repl/replStates.cpp4
-rw-r--r--sinksh/sinksh_utils.cpp2
-rw-r--r--sinksh/sinksh_utils.h3
-rw-r--r--sinksh/syntax_modules/core_syntax.cpp55
-rw-r--r--sinksh/syntax_modules/sink_clear.cpp2
-rw-r--r--synchronizer/CMakeLists.txt2
-rw-r--r--synchronizer/main.cpp4
-rw-r--r--tests/CMakeLists.txt4
-rw-r--r--tests/clientapitest.cpp2
-rw-r--r--tests/databasepopulationandfacadequerybenchmark.cpp15
-rw-r--r--tests/domainadaptortest.cpp2
-rw-r--r--tests/dummyresourcebenchmark.cpp47
-rw-r--r--tests/dummyresourcetest.cpp38
-rw-r--r--tests/dummyresourcewritebenchmark.cpp3
-rw-r--r--tests/hawd/CMakeLists.txt2
-rw-r--r--tests/hawd/dataset.cpp4
-rw-r--r--tests/indextest.cpp2
-rw-r--r--tests/inspectiontest.cpp18
-rw-r--r--tests/maildirresourcetest.cpp157
-rw-r--r--tests/messagequeuetest.cpp2
-rw-r--r--tests/modelinteractivitytest.cpp11
-rw-r--r--tests/pipelinetest.cpp14
-rw-r--r--tests/querytest.cpp23
105 files changed, 1994 insertions, 1196 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6fa2c09..0bbd9f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 2.8.12)
2 2
3option(BUILD_MAILDIR "BUILD_MAILDIR" ON) 3option(BUILD_MAILDIR "BUILD_MAILDIR" ON)
4option(AVOID_BINDING_REBUILD "AVOID_BINDING_REBUILD" OFF) 4option(AVOID_BINDING_REBUILD "AVOID_BINDING_REBUILD" OFF)
5option(CATCH_ERRORS "CATCH_ERRORS" OFF)
5 6
6# ECM setup 7# ECM setup
7find_package(ECM 0.0.10 REQUIRED NO_MODULE) 8find_package(ECM 0.0.10 REQUIRED NO_MODULE)
@@ -41,8 +42,27 @@ function(generate_flatbuffers _target)
41 endforeach(fbs) 42 endforeach(fbs)
42endfunction(generate_flatbuffers) 43endfunction(generate_flatbuffers)
43 44
45add_custom_target(analyze)
46function(add_clang_static_analysis target)
47 get_target_property(SRCs ${target} SOURCES)
48 get_target_property(INCLUDEs ${target} INCLUDE_DIRECTORIES)
49 add_library(${target}_analyze OBJECT EXCLUDE_FROM_ALL ${SRCs})
50 set_target_properties(${target}_analyze PROPERTIES
51 COMPILE_OPTIONS "--analyze"
52 EXCLUDE_FROM_DEFAULT_BUILD true
53 INCLUDE_DIRECTORIES "${INCLUDEs};${KDE_INSTALL_FULL_INCLUDEDIR}/KF5/" # Had to hardcode include directory to find KAsync includes
54 #COMPILE_FLAGS is deprecated, but the only way that -Xanalyzer isn't erronously deduplicated
55 COMPILE_FLAGS "-Xanalyzer -analyzer-eagerly-assume -Xanalyzer -analyzer-opt-analyze-nested-blocks"
56 )
57 target_compile_options(${target}_analyze PRIVATE ${Qt5Core_EXECUTABLE_COMPILE_FLAGS})# Necessary to get options such as fPIC
58 add_dependencies(analyze ${target}_analyze)
59endfunction()
60
44set(CMAKE_AUTOMOC ON) 61set(CMAKE_AUTOMOC ON)
45add_definitions("-Wall -std=c++0x -g") 62if (${CATCH_ERRORS})
63 add_definitions("-Werror -Wall -Weverything -Wno-unused-function -Wno-cast-align -Wno-used-but-marked-unused -Wno-shadow -Wno-weak-vtables -Wno-global-constructors -Wno-deprecated -Wno-weak-template-vtables -Wno-exit-time-destructors -Wno-covered-switch-default -Wno-shorten-64-to-32 -Wno-documentation -Wno-old-style-cast -Wno-extra-semi -Wno-unused-parameter -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-missing-prototypes -Wno-documentation-unknown-command -Wno-sign-conversion")
64endif()
65add_definitions("-std=c++0x -g")
46include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${FLATBUFFERS_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/common) 66include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${FLATBUFFERS_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/common)
47include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/common/domain) 67include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/common/domain)
48 68
diff --git a/README b/README.md
index 4ab63c6..e4a94a0 100644
--- a/README
+++ b/README.md
@@ -1,7 +1,8 @@
1This repository contains a draft of a possible successor to Akonadi 1. 1# Sink
2 2
3It is currently entirely experimental and should not be considered a definite 3Sink is a data access layer handling synchronization, caching and indexing.
4replacement for Akonadi 1 at this point in time. 4
5To build it's documentation please run the builddocs.sh script or see the docs/ subdirectory.
5 6
6Discussion of the code should be done on the kde-pim at kde.org mailing list 7Discussion of the code should be done on the kde-pim at kde.org mailing list
7or in #kontact on IRC. 8or in #kontact on IRC.
@@ -13,7 +14,7 @@ development easy (and easy for others to coordinate with):
13 14
14 https://github.com/nvie/gitflow 15 https://github.com/nvie/gitflow
15 16
16You can find information about the overall design here: 17For further information see:
17 18
18 https://community.kde.org/KDE_PIM/Akonadi_Next 19 https://phabricator.kde.org/project/view/5/
19 20
diff --git a/builddocs.sh b/builddocs.sh
new file mode 100755
index 0000000..41e48a4
--- /dev/null
+++ b/builddocs.sh
@@ -0,0 +1,4 @@
1#!/bin/bash
2mkdocs build
3echo "The HTML files for the documentation can be found in the site/ subdirectory."
4echo "To view the docs in a browser run 'mkdocs serve'"
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index a80ef95..fe72605 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -1,41 +1,45 @@
1include_directories(${CMAKE_CURRENT_BINARY_DIR}) 1include_directories(${CMAKE_CURRENT_BINARY_DIR})
2include_directories(domain) 2include_directories(domain)
3 3
4project(sinkcommon) 4project(sink)
5 5
6ecm_setup_version("0.1" VARIABLE_PREFIX SinkCommon 6ecm_setup_version("0.1" VARIABLE_PREFIX Sink
7 VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/sinkcommon_version.h" 7 VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/sink_version.h"
8 PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/SinkCommonConfigVersion.cmake" 8 PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/SinkConfigVersion.cmake"
9 SOVERSION 0 9 SOVERSION 0
10) 10)
11 11
12########### CMake Config Files ########### 12########### CMake Config Files ###########
13set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/SinkCommon") 13set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/Sink")
14 14
15ecm_configure_package_config_file( 15ecm_configure_package_config_file(
16 "${CMAKE_CURRENT_SOURCE_DIR}/SinkCommonConfig.cmake.in" 16 "${CMAKE_CURRENT_SOURCE_DIR}/SinkConfig.cmake.in"
17 "${CMAKE_CURRENT_BINARY_DIR}/SinkCommonConfig.cmake" 17 "${CMAKE_CURRENT_BINARY_DIR}/SinkConfig.cmake"
18 INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} 18 INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
19) 19)
20 20
21install(FILES 21install(FILES
22 "${CMAKE_CURRENT_BINARY_DIR}/SinkCommonConfig.cmake" 22 "${CMAKE_CURRENT_BINARY_DIR}/SinkConfig.cmake"
23 "${CMAKE_CURRENT_BINARY_DIR}/SinkCommonConfigVersion.cmake" 23 "${CMAKE_CURRENT_BINARY_DIR}/SinkConfigVersion.cmake"
24 DESTINATION "${CMAKECONFIG_INSTALL_DIR}" 24 DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
25 COMPONENT Devel 25 COMPONENT Devel
26) 26)
27 27
28install(EXPORT SinkCommonTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE SinkCommonTargets.cmake) 28add_definitions("-fvisibility=hidden")
29
30install(EXPORT SinkTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE SinkTargets.cmake)
29 31
30set(storage_SRCS storage_lmdb.cpp) 32set(storage_SRCS storage_lmdb.cpp)
31set(storage_LIBS lmdb) 33set(storage_LIBS lmdb)
32 34
33set(command_SRCS 35set(command_SRCS
36 store.cpp
37 notifier.cpp
38 resourcecontrol.cpp
34 modelresult.cpp 39 modelresult.cpp
35 definitions.cpp 40 definitions.cpp
36 log.cpp 41 log.cpp
37 entitybuffer.cpp 42 entitybuffer.cpp
38 clientapi.cpp
39 facadefactory.cpp 43 facadefactory.cpp
40 commands.cpp 44 commands.cpp
41 facade.cpp 45 facade.cpp
@@ -85,7 +89,7 @@ generate_flatbuffers(
85 queuedcommand 89 queuedcommand
86) 90)
87 91
88generate_export_header(${PROJECT_NAME} BASE_NAME SinkCommon EXPORT_FILE_NAME sinkcommon_export.h) 92generate_export_header(${PROJECT_NAME} BASE_NAME Sink EXPORT_FILE_NAME sink_export.h)
89SET_TARGET_PROPERTIES(${PROJECT_NAME} 93SET_TARGET_PROPERTIES(${PROJECT_NAME}
90 PROPERTIES LINKER_LANGUAGE CXX 94 PROPERTIES LINKER_LANGUAGE CXX
91 VERSION "0.1" 95 VERSION "0.1"
@@ -95,15 +99,20 @@ SET_TARGET_PROPERTIES(${PROJECT_NAME}
95qt5_use_modules(${PROJECT_NAME} Network) 99qt5_use_modules(${PROJECT_NAME} Network)
96target_link_libraries(${PROJECT_NAME} ${storage_LIBS} KF5::Async) 100target_link_libraries(${PROJECT_NAME} ${storage_LIBS} KF5::Async)
97install(TARGETS ${PROJECT_NAME} 101install(TARGETS ${PROJECT_NAME}
98 EXPORT SinkCommonTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) 102 EXPORT SinkTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} )
103
104add_clang_static_analysis(${PROJECT_NAME})
99 105
100install(FILES 106install(FILES
101 clientapi.h 107 store.h
108 notifier.h
109 resourcecontrol.h
102 domain/applicationdomaintype.h 110 domain/applicationdomaintype.h
103 query.h 111 query.h
104 inspection.h 112 inspection.h
105 notification.h 113 notification.h
106 bufferadaptor.h 114 bufferadaptor.h
115 ${CMAKE_CURRENT_BINARY_DIR}/sink_export.h
107 DESTINATION ${INCLUDE_INSTALL_DIR}/${PROJECT_NAME} COMPONENT Devel 116 DESTINATION ${INCLUDE_INSTALL_DIR}/${PROJECT_NAME} COMPONENT Devel
108) 117)
109 118
diff --git a/common/SinkCommonConfig.cmake.in b/common/SinkConfig.cmake.in
index 6c2a1c2..930b2db 100644
--- a/common/SinkCommonConfig.cmake.in
+++ b/common/SinkConfig.cmake.in
@@ -2,4 +2,4 @@
2 2
3find_dependency(KF5Mime "@KMIME_LIB_VERSION@") 3find_dependency(KF5Mime "@KMIME_LIB_VERSION@")
4 4
5include("${CMAKE_CURRENT_LIST_DIR}/SinkCommonTargets.cmake") 5include("${CMAKE_CURRENT_LIST_DIR}/SinkTargets.cmake")
diff --git a/common/clientapi.h b/common/clientapi.h
deleted file mode 100644
index 64c4f64..0000000
--- a/common/clientapi.h
+++ /dev/null
@@ -1,140 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#pragma once
22
23#include <QString>
24#include <QSharedPointer>
25
26#include <Async/Async>
27
28#include "query.h"
29#include "inspection.h"
30#include "applicationdomaintype.h"
31
32class QAbstractItemModel;
33
34namespace Sink {
35class ResourceAccess;
36class Notification;
37
38/**
39 * Store interface used in the client API.
40 */
41class Store {
42public:
43 static QString storageLocation();
44 static QByteArray resourceName(const QByteArray &instanceIdentifier);
45
46 enum Roles {
47 DomainObjectRole = Qt::UserRole + 1, //Must be the same as in ModelResult
48 ChildrenFetchedRole,
49 DomainObjectBaseRole
50 };
51
52 /**
53 * Asynchronusly load a dataset with tree structure information
54 */
55 template <class DomainType>
56 static QSharedPointer<QAbstractItemModel> loadModel(Query query);
57
58 /**
59 * Create a new entity.
60 */
61 template <class DomainType>
62 static KAsync::Job<void> create(const DomainType &domainObject);
63
64 /**
65 * Modify an entity.
66 *
67 * This includes moving etc. since these are also simple settings on a property.
68 */
69 template <class DomainType>
70 static KAsync::Job<void> modify(const DomainType &domainObject);
71
72 /**
73 * Remove an entity.
74 */
75 template <class DomainType>
76 static KAsync::Job<void> remove(const DomainType &domainObject);
77
78 /**
79 * Synchronize data to local cache.
80 */
81 static KAsync::Job<void> synchronize(const Sink::Query &query);
82
83 /**
84 * Shutdown resource.
85 */
86 static KAsync::Job<void> shutdown(const QByteArray &resourceIdentifier);
87
88 /**
89 * Start resource.
90 *
91 * The resource is ready for operation once this command completes.
92 * This command is only necessary if a resource was shutdown previously,
93 * otherwise the resource process will automatically start as necessary.
94 */
95 static KAsync::Job<void> start(const QByteArray &resourceIdentifier);
96
97 /**
98 * Flushes any pending messages to disk
99 */
100 static KAsync::Job<void> flushMessageQueue(const QByteArrayList &resourceIdentifier);
101
102 /**
103 * Flushes any pending messages that haven't been replayed to the source.
104 */
105 static KAsync::Job<void> flushReplayQueue(const QByteArrayList &resourceIdentifier);
106
107 /**
108 * Removes a resource from disk.
109 */
110 static void removeFromDisk(const QByteArray &resourceIdentifier);
111
112 template <class DomainType>
113 static KAsync::Job<DomainType> fetchOne(const Sink::Query &query);
114
115 template <class DomainType>
116 static KAsync::Job<QList<typename DomainType::Ptr> > fetchAll(const Sink::Query &query);
117
118 template <class DomainType>
119 static KAsync::Job<QList<typename DomainType::Ptr> > fetch(const Sink::Query &query, int minimumAmount = 0);
120};
121
122namespace Resources {
123 template <class DomainType>
124 KAsync::Job<void> inspect(const Inspection &inspectionCommand);
125}
126
127class Notifier {
128public:
129 Notifier(const QSharedPointer<ResourceAccess> &resourceAccess);
130 // Notifier(const QByteArray &resource);
131 // Notifier(const QByteArrayList &resource);
132 void registerHandler(std::function<void(const Notification &)>);
133
134private:
135 class Private;
136 QScopedPointer<Private> d;
137};
138
139}
140
diff --git a/common/commands.cpp b/common/commands.cpp
index 8b915f0..5d38afa 100644
--- a/common/commands.cpp
+++ b/common/commands.cpp
@@ -61,6 +61,8 @@ QByteArray name(int commandId)
61 return "RevisionReplayed"; 61 return "RevisionReplayed";
62 case InspectionCommand: 62 case InspectionCommand:
63 return "Inspection"; 63 return "Inspection";
64 case RemoveFromDiskCommand:
65 return "RemoveFromDisk";
64 case CustomCommand: 66 case CustomCommand:
65 return "Custom"; 67 return "Custom";
66 }; 68 };
diff --git a/common/commands.h b/common/commands.h
index 19c827e..64abd76 100644
--- a/common/commands.h
+++ b/common/commands.h
@@ -20,7 +20,7 @@
20 20
21#pragma once 21#pragma once
22 22
23#include <sinkcommon_export.h> 23#include "sink_export.h"
24#include <flatbuffers/flatbuffers.h> 24#include <flatbuffers/flatbuffers.h>
25#include <QByteArray> 25#include <QByteArray>
26 26
@@ -48,16 +48,17 @@ enum CommandIds {
48 PingCommand, 48 PingCommand,
49 RevisionReplayedCommand, 49 RevisionReplayedCommand,
50 InspectionCommand, 50 InspectionCommand,
51 RemoveFromDiskCommand,
51 CustomCommand = 0xffff 52 CustomCommand = 0xffff
52}; 53};
53 54
54 55
55QByteArray name(int commandId); 56QByteArray name(int commandId);
56 57
57int SINKCOMMON_EXPORT headerSize(); 58int SINK_EXPORT headerSize();
58void SINKCOMMON_EXPORT write(QIODevice *device, int messageId, int commandId); 59void SINK_EXPORT write(QIODevice *device, int messageId, int commandId);
59void SINKCOMMON_EXPORT write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size); 60void SINK_EXPORT write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size);
60void SINKCOMMON_EXPORT write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb); 61void SINK_EXPORT write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb);
61 62
62} 63}
63 64
diff --git a/common/commands/notification.fbs b/common/commands/notification.fbs
index ff01fc5..8750ff5 100644
--- a/common/commands/notification.fbs
+++ b/common/commands/notification.fbs
@@ -1,6 +1,6 @@
1namespace Sink.Commands; 1namespace Sink.Commands;
2 2
3enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress, Inspection } 3enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress, Inspection, RevisionUpdate }
4enum NotificationCode : byte { Success = 0, Failure = 1, UserCode } 4enum NotificationCode : byte { Success = 0, Failure = 1, UserCode }
5 5
6table Notification { 6table Notification {
diff --git a/common/definitions.h b/common/definitions.h
index 5834f01..029d3f8 100644
--- a/common/definitions.h
+++ b/common/definitions.h
@@ -20,10 +20,11 @@
20 20
21#pragma once 21#pragma once
22 22
23#include "sink_export.h"
23#include <QString> 24#include <QString>
24#include <QByteArray> 25#include <QByteArray>
25 26
26namespace Sink { 27namespace Sink {
27 QString storageLocation(); 28 QString SINK_EXPORT storageLocation();
28 QByteArray resourceName(const QByteArray &instanceIdentifier); 29 QByteArray SINK_EXPORT resourceName(const QByteArray &instanceIdentifier);
29} 30}
diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp
index b0433be..df10327 100644
--- a/common/domain/applicationdomaintype.cpp
+++ b/common/domain/applicationdomaintype.cpp
@@ -64,6 +64,12 @@ ApplicationDomainType::~ApplicationDomainType()
64{ 64{
65} 65}
66 66
67bool ApplicationDomainType::hasProperty(const QByteArray &key) const
68{
69 Q_ASSERT(mAdaptor);
70 return mAdaptor->availableProperties().contains(key);
71}
72
67QVariant ApplicationDomainType::getProperty(const QByteArray &key) const 73QVariant ApplicationDomainType::getProperty(const QByteArray &key) const
68{ 74{
69 Q_ASSERT(mAdaptor); 75 Q_ASSERT(mAdaptor);
@@ -76,13 +82,18 @@ QVariant ApplicationDomainType::getProperty(const QByteArray &key) const
76void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value) 82void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value)
77{ 83{
78 Q_ASSERT(mAdaptor); 84 Q_ASSERT(mAdaptor);
79 mChangeSet.insert(key, value); 85 mChangeSet.insert(key);
80 mAdaptor->setProperty(key, value); 86 mAdaptor->setProperty(key, value);
81} 87}
82 88
89void ApplicationDomainType::setChangedProperties(const QSet<QByteArray> &changeset)
90{
91 mChangeSet = changeset;
92}
93
83QByteArrayList ApplicationDomainType::changedProperties() const 94QByteArrayList ApplicationDomainType::changedProperties() const
84{ 95{
85 return mChangeSet.keys(); 96 return mChangeSet.toList();
86} 97}
87 98
88qint64 ApplicationDomainType::revision() const 99qint64 ApplicationDomainType::revision() const
@@ -100,6 +111,36 @@ QByteArray ApplicationDomainType::identifier() const
100 return mIdentifier; 111 return mIdentifier;
101} 112}
102 113
114Entity::~Entity()
115{
116
117}
118
119Event::~Event()
120{
121
122}
123
124Todo::~Todo()
125{
126
127}
128
129Mail::~Mail()
130{
131
132}
133
134Folder::~Folder()
135{
136
137}
138
139SinkResource::~SinkResource()
140{
141
142}
143
103template<> 144template<>
104QByteArray getTypeName<Event>() 145QByteArray getTypeName<Event>()
105{ 146{
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index 63f030c..32d8999 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -19,9 +19,11 @@
19 */ 19 */
20#pragma once 20#pragma once
21 21
22#include "sink_export.h"
22#include <QSharedPointer> 23#include <QSharedPointer>
23#include <QVariant> 24#include <QVariant>
24#include <QByteArray> 25#include <QByteArray>
26#include <QDebug>
25#include "bufferadaptor.h" 27#include "bufferadaptor.h"
26 28
27namespace Sink { 29namespace Sink {
@@ -35,7 +37,7 @@ namespace ApplicationDomain {
35 * 37 *
36 * ApplicationDomainTypes don't adhere to any standard and are meant to be extended frequently (hence the non-typesafe interface). 38 * ApplicationDomainTypes don't adhere to any standard and are meant to be extended frequently (hence the non-typesafe interface).
37 */ 39 */
38class ApplicationDomainType { 40class SINK_EXPORT ApplicationDomainType {
39public: 41public:
40 typedef QSharedPointer<ApplicationDomainType> Ptr; 42 typedef QSharedPointer<ApplicationDomainType> Ptr;
41 43
@@ -55,16 +57,19 @@ public:
55 57
56 virtual ~ApplicationDomainType(); 58 virtual ~ApplicationDomainType();
57 59
58 virtual QVariant getProperty(const QByteArray &key) const; 60 bool hasProperty(const QByteArray &key) const;
59 virtual void setProperty(const QByteArray &key, const QVariant &value); 61 QVariant getProperty(const QByteArray &key) const;
60 virtual QByteArrayList changedProperties() const; 62 void setProperty(const QByteArray &key, const QVariant &value);
63 void setChangedProperties(const QSet<QByteArray> &changeset);
64 QByteArrayList changedProperties() const;
61 qint64 revision() const; 65 qint64 revision() const;
62 QByteArray resourceInstanceIdentifier() const; 66 QByteArray resourceInstanceIdentifier() const;
63 QByteArray identifier() const; 67 QByteArray identifier() const;
64 68
65private: 69private:
70 friend QDebug operator<<(QDebug, const ApplicationDomainType &);
66 QSharedPointer<BufferAdaptor> mAdaptor; 71 QSharedPointer<BufferAdaptor> mAdaptor;
67 QHash<QByteArray, QVariant> mChangeSet; 72 QSet<QByteArray> mChangeSet;
68 /* 73 /*
69 * Each domain object needs to store the resource, identifier, revision triple so we can link back to the storage location. 74 * Each domain object needs to store the resource, identifier, revision triple so we can link back to the storage location.
70 */ 75 */
@@ -82,34 +87,50 @@ inline bool operator==(const ApplicationDomainType& lhs, const ApplicationDomain
82 && lhs.resourceInstanceIdentifier() == rhs.resourceInstanceIdentifier(); 87 && lhs.resourceInstanceIdentifier() == rhs.resourceInstanceIdentifier();
83} 88}
84 89
85struct Entity : public ApplicationDomainType { 90inline QDebug operator<< (QDebug d, const ApplicationDomainType &type)
91{
92 d << "ApplicationDomainType(\n";
93 for (const auto &property : type.mAdaptor->availableProperties()) {
94 d << " " << property << "\t" << type.getProperty(property) << "\n";
95 }
96 d << ")";
97 return d;
98}
99
100struct SINK_EXPORT Entity : public ApplicationDomainType {
86 typedef QSharedPointer<Entity> Ptr; 101 typedef QSharedPointer<Entity> Ptr;
87 using ApplicationDomainType::ApplicationDomainType; 102 using ApplicationDomainType::ApplicationDomainType;
103 virtual ~Entity();
88}; 104};
89 105
90struct Event : public Entity { 106struct SINK_EXPORT Event : public Entity {
91 typedef QSharedPointer<Event> Ptr; 107 typedef QSharedPointer<Event> Ptr;
92 using Entity::Entity; 108 using Entity::Entity;
109 virtual ~Event();
93}; 110};
94 111
95struct Todo : public Entity { 112struct SINK_EXPORT Todo : public Entity {
96 typedef QSharedPointer<Todo> Ptr; 113 typedef QSharedPointer<Todo> Ptr;
97 using Entity::Entity; 114 using Entity::Entity;
115 virtual ~Todo();
98}; 116};
99 117
100struct Calendar : public Entity { 118struct SINK_EXPORT Calendar : public Entity {
101 typedef QSharedPointer<Calendar> Ptr; 119 typedef QSharedPointer<Calendar> Ptr;
102 using Entity::Entity; 120 using Entity::Entity;
121 virtual ~Calendar();
103}; 122};
104 123
105struct Mail : public Entity { 124struct SINK_EXPORT Mail : public Entity {
106 typedef QSharedPointer<Mail> Ptr; 125 typedef QSharedPointer<Mail> Ptr;
107 using Entity::Entity; 126 using Entity::Entity;
127 virtual ~Mail();
108}; 128};
109 129
110struct Folder : public Entity { 130struct SINK_EXPORT Folder : public Entity {
111 typedef QSharedPointer<Folder> Ptr; 131 typedef QSharedPointer<Folder> Ptr;
112 using Entity::Entity; 132 using Entity::Entity;
133 virtual ~Folder();
113}; 134};
114 135
115/** 136/**
@@ -118,9 +139,10 @@ struct Folder : public Entity {
118 * This type is used for configuration of resources, 139 * This type is used for configuration of resources,
119 * and for creating and removing resource instances. 140 * and for creating and removing resource instances.
120 */ 141 */
121struct SinkResource : public ApplicationDomainType { 142struct SINK_EXPORT SinkResource : public ApplicationDomainType {
122 typedef QSharedPointer<SinkResource> Ptr; 143 typedef QSharedPointer<SinkResource> Ptr;
123 using ApplicationDomainType::ApplicationDomainType; 144 using ApplicationDomainType::ApplicationDomainType;
145 virtual ~SinkResource();
124}; 146};
125 147
126/** 148/**
@@ -129,22 +151,22 @@ struct SinkResource : public ApplicationDomainType {
129 * Do not store these types to disk, they may change over time. 151 * Do not store these types to disk, they may change over time.
130 */ 152 */
131template<class DomainType> 153template<class DomainType>
132QByteArray getTypeName(); 154QByteArray SINK_EXPORT getTypeName();
133 155
134template<> 156template<>
135QByteArray getTypeName<Event>(); 157QByteArray SINK_EXPORT getTypeName<Event>();
136 158
137template<> 159template<>
138QByteArray getTypeName<Todo>(); 160QByteArray SINK_EXPORT getTypeName<Todo>();
139 161
140template<> 162template<>
141QByteArray getTypeName<SinkResource>(); 163QByteArray SINK_EXPORT getTypeName<SinkResource>();
142 164
143template<> 165template<>
144QByteArray getTypeName<Mail>(); 166QByteArray SINK_EXPORT getTypeName<Mail>();
145 167
146template<> 168template<>
147QByteArray getTypeName<Folder>(); 169QByteArray SINK_EXPORT getTypeName<Folder>();
148 170
149/** 171/**
150 * Type implementation. 172 * Type implementation.
@@ -153,7 +175,7 @@ QByteArray getTypeName<Folder>();
153 * Contains all non-resource specific, but type-specific code. 175 * Contains all non-resource specific, but type-specific code.
154 */ 176 */
155template<typename DomainType> 177template<typename DomainType>
156class TypeImplementation; 178class SINK_EXPORT TypeImplementation;
157 179
158} 180}
159} 181}
diff --git a/common/domain/event.cpp b/common/domain/event.cpp
index 9f81eb8..4210125 100644
--- a/common/domain/event.cpp
+++ b/common/domain/event.cpp
@@ -69,6 +69,7 @@ QSharedPointer<ReadPropertyMapper<TypeImplementation<Event>::Buffer> > TypeImple
69{ 69{
70 auto propertyMapper = QSharedPointer<ReadPropertyMapper<Buffer> >::create(); 70 auto propertyMapper = QSharedPointer<ReadPropertyMapper<Buffer> >::create();
71 propertyMapper->addMapping<QString, Buffer>("summary", &Buffer::summary); 71 propertyMapper->addMapping<QString, Buffer>("summary", &Buffer::summary);
72 propertyMapper->addMapping<QString, Buffer>("description", &Buffer::description);
72 propertyMapper->addMapping<QString, Buffer>("uid", &Buffer::uid); 73 propertyMapper->addMapping<QString, Buffer>("uid", &Buffer::uid);
73 propertyMapper->addMapping<QByteArray, Buffer>("attachment", &Buffer::attachment); 74 propertyMapper->addMapping<QByteArray, Buffer>("attachment", &Buffer::attachment);
74 return propertyMapper; 75 return propertyMapper;
@@ -78,6 +79,7 @@ QSharedPointer<WritePropertyMapper<TypeImplementation<Event>::BufferBuilder> > T
78{ 79{
79 auto propertyMapper = QSharedPointer<WritePropertyMapper<BufferBuilder> >::create(); 80 auto propertyMapper = QSharedPointer<WritePropertyMapper<BufferBuilder> >::create();
80 propertyMapper->addMapping<QString>("summary", &BufferBuilder::add_summary); 81 propertyMapper->addMapping<QString>("summary", &BufferBuilder::add_summary);
82 propertyMapper->addMapping<QString>("description", &BufferBuilder::add_description);
81 propertyMapper->addMapping<QString>("uid", &BufferBuilder::add_uid); 83 propertyMapper->addMapping<QString>("uid", &BufferBuilder::add_uid);
82 propertyMapper->addMapping<QByteArray>("attachment", &BufferBuilder::add_attachment); 84 propertyMapper->addMapping<QByteArray>("attachment", &BufferBuilder::add_attachment);
83 return propertyMapper; 85 return propertyMapper;
diff --git a/common/domainadaptor.h b/common/domainadaptor.h
index d43fad7..893c8d0 100644
--- a/common/domainadaptor.h
+++ b/common/domainadaptor.h
@@ -19,6 +19,7 @@
19 19
20#pragma once 20#pragma once
21 21
22#include "sink_export.h"
22#include <QVariant> 23#include <QVariant>
23#include <QByteArray> 24#include <QByteArray>
24#include <functional> 25#include <functional>
@@ -121,7 +122,7 @@ public:
121 * This is required by the facade the read the value, and by the pipeline preprocessors to access the domain values in a generic way. 122 * This is required by the facade the read the value, and by the pipeline preprocessors to access the domain values in a generic way.
122 */ 123 */
123template<typename DomainType, typename ResourceBuffer, typename ResourceBuilder> 124template<typename DomainType, typename ResourceBuffer, typename ResourceBuilder>
124class DomainTypeAdaptorFactory : public DomainTypeAdaptorFactoryInterface 125class SINK_EXPORT DomainTypeAdaptorFactory : public DomainTypeAdaptorFactoryInterface
125{ 126{
126 typedef typename Sink::ApplicationDomain::TypeImplementation<DomainType>::Buffer LocalBuffer; 127 typedef typename Sink::ApplicationDomain::TypeImplementation<DomainType>::Buffer LocalBuffer;
127 typedef typename Sink::ApplicationDomain::TypeImplementation<DomainType>::BufferBuilder LocalBuilder; 128 typedef typename Sink::ApplicationDomain::TypeImplementation<DomainType>::BufferBuilder LocalBuilder;
diff --git a/common/domaintypeadaptorfactoryinterface.h b/common/domaintypeadaptorfactoryinterface.h
index d974bbf..4d0ce04 100644
--- a/common/domaintypeadaptorfactoryinterface.h
+++ b/common/domaintypeadaptorfactoryinterface.h
@@ -38,5 +38,11 @@ public:
38 typedef QSharedPointer<DomainTypeAdaptorFactoryInterface> Ptr; 38 typedef QSharedPointer<DomainTypeAdaptorFactoryInterface> Ptr;
39 virtual ~DomainTypeAdaptorFactoryInterface() {}; 39 virtual ~DomainTypeAdaptorFactoryInterface() {};
40 virtual QSharedPointer<Sink::ApplicationDomain::BufferAdaptor> createAdaptor(const Sink::Entity &entity) = 0; 40 virtual QSharedPointer<Sink::ApplicationDomain::BufferAdaptor> createAdaptor(const Sink::Entity &entity) = 0;
41
42 /*
43 * Creates a buffer from @param domainType
44 *
45 * Note that this only serialized parameters that are part of ApplicationDomainType::changedProperties()
46 */
41 virtual void createBuffer(const Sink::ApplicationDomain::ApplicationDomainType &domainType, flatbuffers::FlatBufferBuilder &fbb, void const *metadataData = 0, size_t metadataSize = 0) = 0; 47 virtual void createBuffer(const Sink::ApplicationDomain::ApplicationDomainType &domainType, flatbuffers::FlatBufferBuilder &fbb, void const *metadataData = 0, size_t metadataSize = 0) = 0;
42}; 48};
diff --git a/common/entitybuffer.cpp b/common/entitybuffer.cpp
index 0e5435a..b4a5cb2 100644
--- a/common/entitybuffer.cpp
+++ b/common/entitybuffer.cpp
@@ -18,6 +18,12 @@ EntityBuffer::EntityBuffer(const void *dataValue, int dataSize)
18 } 18 }
19} 19}
20 20
21EntityBuffer::EntityBuffer(const QByteArray &data)
22 : EntityBuffer(data.constData(), data.size())
23{
24
25}
26
21bool EntityBuffer::isValid() const 27bool EntityBuffer::isValid() const
22{ 28{
23 return mEntity; 29 return mEntity;
@@ -25,6 +31,7 @@ bool EntityBuffer::isValid() const
25 31
26const Sink::Entity &EntityBuffer::entity() 32const Sink::Entity &EntityBuffer::entity()
27{ 33{
34 Q_ASSERT(mEntity);
28 return *mEntity; 35 return *mEntity;
29} 36}
30 37
diff --git a/common/entitybuffer.h b/common/entitybuffer.h
index c9c2453..474a619 100644
--- a/common/entitybuffer.h
+++ b/common/entitybuffer.h
@@ -1,14 +1,17 @@
1#pragma once 1#pragma once
2 2
3#include "sink_export.h"
3#include <functional> 4#include <functional>
4#include <flatbuffers/flatbuffers.h> 5#include <flatbuffers/flatbuffers.h>
6#include <QByteArray>
5 7
6namespace Sink { 8namespace Sink {
7struct Entity; 9struct Entity;
8 10
9class EntityBuffer { 11class SINK_EXPORT EntityBuffer {
10public: 12public:
11 EntityBuffer(const void *dataValue, int size); 13 EntityBuffer(const void *dataValue, int size);
14 EntityBuffer(const QByteArray &data);
12 const uint8_t *resourceBuffer(); 15 const uint8_t *resourceBuffer();
13 const uint8_t *metadataBuffer(); 16 const uint8_t *metadataBuffer();
14 const uint8_t *localBuffer(); 17 const uint8_t *localBuffer();
diff --git a/common/facade.cpp b/common/facade.cpp
index 8cb776c..1b91ce4 100644
--- a/common/facade.cpp
+++ b/common/facade.cpp
@@ -29,6 +29,14 @@
29 29
30using namespace Sink; 30using namespace Sink;
31 31
32#undef DEBUG_AREA
33#define DEBUG_AREA "client.facade"
34
35/**
36 * A factory for resource access instances that caches the instance for some time.
37 *
38 * This avoids constantly recreating connections, and should allow a single process to have one connection per resource.
39 */
32class ResourceAccessFactory { 40class ResourceAccessFactory {
33public: 41public:
34 static ResourceAccessFactory &instance() 42 static ResourceAccessFactory &instance()
@@ -140,6 +148,7 @@ QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr>
140{ 148{
141 //The runner lives for the lifetime of the query 149 //The runner lives for the lifetime of the query
142 auto runner = new QueryRunner<DomainType>(query, mResourceAccess, mResourceInstanceIdentifier, mDomainTypeAdaptorFactory, bufferTypeForDomainType()); 150 auto runner = new QueryRunner<DomainType>(query, mResourceAccess, mResourceInstanceIdentifier, mDomainTypeAdaptorFactory, bufferTypeForDomainType());
151 runner->setResultTransformation(mResultTransformation);
143 return qMakePair(KAsync::null<void>(), runner->emitter()); 152 return qMakePair(KAsync::null<void>(), runner->emitter());
144} 153}
145 154
diff --git a/common/facade.h b/common/facade.h
index c25464f..a85b1de 100644
--- a/common/facade.h
+++ b/common/facade.h
@@ -19,6 +19,7 @@
19 19
20#pragma once 20#pragma once
21 21
22#include "sink_export.h"
22#include "facadeinterface.h" 23#include "facadeinterface.h"
23 24
24#include <QByteArray> 25#include <QByteArray>
@@ -43,7 +44,7 @@ namespace Sink {
43 * Additionally a resource only has to provide a synchronizer plugin to execute the synchronization 44 * Additionally a resource only has to provide a synchronizer plugin to execute the synchronization
44 */ 45 */
45template <typename DomainType> 46template <typename DomainType>
46class GenericFacade: public Sink::StoreFacade<DomainType> 47class SINK_EXPORT GenericFacade: public Sink::StoreFacade<DomainType>
47{ 48{
48public: 49public:
49 /** 50 /**
@@ -59,9 +60,10 @@ public:
59 KAsync::Job<void> create(const DomainType &domainObject) Q_DECL_OVERRIDE; 60 KAsync::Job<void> create(const DomainType &domainObject) Q_DECL_OVERRIDE;
60 KAsync::Job<void> modify(const DomainType &domainObject) Q_DECL_OVERRIDE; 61 KAsync::Job<void> modify(const DomainType &domainObject) Q_DECL_OVERRIDE;
61 KAsync::Job<void> remove(const DomainType &domainObject) Q_DECL_OVERRIDE; 62 KAsync::Job<void> remove(const DomainType &domainObject) Q_DECL_OVERRIDE;
62 QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE; 63 virtual QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE;
63 64
64protected: 65protected:
66 std::function<void(Sink::ApplicationDomain::ApplicationDomainType &domainObject)> mResultTransformation;
65 //TODO use one resource access instance per application & per resource 67 //TODO use one resource access instance per application & per resource
66 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess; 68 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess;
67 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory; 69 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory;
diff --git a/common/facadefactory.h b/common/facadefactory.h
index 3dca63b..ef2a3f9 100644
--- a/common/facadefactory.h
+++ b/common/facadefactory.h
@@ -20,6 +20,7 @@
20 20
21#pragma once 21#pragma once
22 22
23#include "sink_export.h"
23#include <QByteArray> 24#include <QByteArray>
24#include <QDebug> 25#include <QDebug>
25#include <QMutex> 26#include <QMutex>
@@ -37,7 +38,7 @@ namespace Sink {
37 * 38 *
38 * If we were to provide default implementations for certain capabilities. Here would be the place to do so. 39 * If we were to provide default implementations for certain capabilities. Here would be the place to do so.
39 */ 40 */
40class FacadeFactory { 41class SINK_EXPORT FacadeFactory {
41public: 42public:
42 typedef std::function<std::shared_ptr<void>(const QByteArray &)> FactoryFunction; 43 typedef std::function<std::shared_ptr<void>(const QByteArray &)> FactoryFunction;
43 44
diff --git a/common/genericresource.cpp b/common/genericresource.cpp
index c7326d3..2688df0 100644
--- a/common/genericresource.cpp
+++ b/common/genericresource.cpp
@@ -17,11 +17,17 @@
17 17
18#include <QUuid> 18#include <QUuid>
19#include <QDataStream> 19#include <QDataStream>
20#include <QTime>
20 21
21static int sBatchSize = 100; 22static int sBatchSize = 100;
23//This interval directly affects the roundtrip time of single commands
24static int sCommitInterval = 10;
22 25
23using namespace Sink; 26using namespace Sink;
24 27
28#undef DEBUG_AREA
29#define DEBUG_AREA "resource.changereplay"
30
25/** 31/**
26 * Replays changes from the storage one by one. 32 * Replays changes from the storage one by one.
27 * 33 *
@@ -87,7 +93,7 @@ public Q_SLOTS:
87 const auto uid = Storage::getUidFromRevision(mainStoreTransaction, revision); 93 const auto uid = Storage::getUidFromRevision(mainStoreTransaction, revision);
88 const auto type = Storage::getTypeFromRevision(mainStoreTransaction, revision); 94 const auto type = Storage::getTypeFromRevision(mainStoreTransaction, revision);
89 const auto key = Storage::assembleKey(uid, revision); 95 const auto key = Storage::assembleKey(uid, revision);
90 mainStoreTransaction.openDatabase(type + ".main").scan(key, [&lastReplayedRevision, type, this](const QByteArray &key, const QByteArray &value) -> bool { 96 Storage::mainDatabase(mainStoreTransaction, type).scan(key, [&lastReplayedRevision, type, this](const QByteArray &key, const QByteArray &value) -> bool {
91 mReplayFunction(type, key, value).exec(); 97 mReplayFunction(type, key, value).exec();
92 //TODO make for loop async, and pass to async replay function together with type 98 //TODO make for loop async, and pass to async replay function together with type
93 Trace() << "Replaying " << key; 99 Trace() << "Replaying " << key;
@@ -110,6 +116,9 @@ private:
110 ReplayFunction mReplayFunction; 116 ReplayFunction mReplayFunction;
111}; 117};
112 118
119#undef DEBUG_AREA
120#define DEBUG_AREA "resource.commandprocessor"
121
113/** 122/**
114 * Drives the pipeline using the output from all command queues 123 * Drives the pipeline using the output from all command queues
115 */ 124 */
@@ -197,7 +206,6 @@ private slots:
197 default: 206 default:
198 return KAsync::error<qint64>(-1, "Unhandled command"); 207 return KAsync::error<qint64>(-1, "Unhandled command");
199 } 208 }
200 return KAsync::null<qint64>();
201 } 209 }
202 210
203 KAsync::Job<qint64, qint64> processQueuedCommand(const QByteArray &data) 211 KAsync::Job<qint64, qint64> processQueuedCommand(const QByteArray &data)
@@ -226,15 +234,17 @@ private slots:
226 //Process all messages of this queue 234 //Process all messages of this queue
227 KAsync::Job<void> processQueue(MessageQueue *queue) 235 KAsync::Job<void> processQueue(MessageQueue *queue)
228 { 236 {
237 auto time = QSharedPointer<QTime>::create();
229 return KAsync::start<void>([this](){ 238 return KAsync::start<void>([this](){
230 mPipeline->startTransaction(); 239 mPipeline->startTransaction();
231 }).then(KAsync::dowhile( 240 }).then(KAsync::dowhile(
232 [queue]() { return !queue->isEmpty(); }, 241 [queue]() { return !queue->isEmpty(); },
233 [this, queue](KAsync::Future<void> &future) { 242 [this, queue, time](KAsync::Future<void> &future) {
234 queue->dequeueBatch(sBatchSize, [this](const QByteArray &data) { 243 queue->dequeueBatch(sBatchSize, [this, time](const QByteArray &data) {
235 return KAsync::start<void>([this, data](KAsync::Future<void> &future) { 244 time->start();
236 processQueuedCommand(data).then<void, qint64>([&future, this](qint64 createdRevision) { 245 return KAsync::start<void>([this, data, time](KAsync::Future<void> &future) {
237 Trace() << "Created revision " << createdRevision; 246 processQueuedCommand(data).then<void, qint64>([&future, this, time](qint64 createdRevision) {
247 Trace() << "Created revision " << createdRevision << ". Processing took: " << Log::TraceTime(time->elapsed());
238 future.setFinished(); 248 future.setFinished();
239 }).exec(); 249 }).exec();
240 }); 250 });
@@ -256,21 +266,27 @@ private slots:
256 266
257 KAsync::Job<void> processPipeline() 267 KAsync::Job<void> processPipeline()
258 { 268 {
269 auto time = QSharedPointer<QTime>::create();
270 time->start();
259 mPipeline->startTransaction(); 271 mPipeline->startTransaction();
260 Trace() << "Cleaning up from " << mPipeline->cleanedUpRevision() + 1 << " to " << mLowerBoundRevision; 272 Trace() << "Cleaning up from " << mPipeline->cleanedUpRevision() + 1 << " to " << mLowerBoundRevision;
261 for (qint64 revision = mPipeline->cleanedUpRevision() + 1; revision <= mLowerBoundRevision; revision++) { 273 for (qint64 revision = mPipeline->cleanedUpRevision() + 1; revision <= mLowerBoundRevision; revision++) {
262 mPipeline->cleanupRevision(revision); 274 mPipeline->cleanupRevision(revision);
263 } 275 }
264 mPipeline->commit(); 276 mPipeline->commit();
277 Trace() << "Cleanup done." << Log::TraceTime(time->elapsed());
265 278
266 //Go through all message queues 279 //Go through all message queues
267 auto it = QSharedPointer<QListIterator<MessageQueue*> >::create(mCommandQueues); 280 auto it = QSharedPointer<QListIterator<MessageQueue*> >::create(mCommandQueues);
268 return KAsync::dowhile( 281 return KAsync::dowhile(
269 [it]() { return it->hasNext(); }, 282 [it]() { return it->hasNext(); },
270 [it, this](KAsync::Future<void> &future) { 283 [it, this](KAsync::Future<void> &future) {
284 auto time = QSharedPointer<QTime>::create();
285 time->start();
286
271 auto queue = it->next(); 287 auto queue = it->next();
272 processQueue(queue).then<void>([&future]() { 288 processQueue(queue).then<void>([&future, time]() {
273 Trace() << "Queue processed"; 289 Trace() << "Queue processed." << Log::TraceTime(time->elapsed());
274 future.setFinished(); 290 future.setFinished();
275 }).exec(); 291 }).exec();
276 } 292 }
@@ -287,6 +303,8 @@ private:
287 InspectionFunction mInspect; 303 InspectionFunction mInspect;
288}; 304};
289 305
306#undef DEBUG_AREA
307#define DEBUG_AREA "resource"
290 308
291GenericResource::GenericResource(const QByteArray &resourceInstanceIdentifier, const QSharedPointer<Pipeline> &pipeline) 309GenericResource::GenericResource(const QByteArray &resourceInstanceIdentifier, const QSharedPointer<Pipeline> &pipeline)
292 : Sink::Resource(), 310 : Sink::Resource(),
@@ -313,12 +331,14 @@ GenericResource::GenericResource(const QByteArray &resourceInstanceIdentifier, c
313 QVariant expectedValue; 331 QVariant expectedValue;
314 s >> expectedValue; 332 s >> expectedValue;
315 inspect(inspectionType, inspectionId, domainType, entityId, property, expectedValue).then<void>([=]() { 333 inspect(inspectionType, inspectionId, domainType, entityId, property, expectedValue).then<void>([=]() {
334 Log_area("resource.inspection") << "Inspection was successful: " << inspectionType << inspectionId << entityId;
316 Sink::Notification n; 335 Sink::Notification n;
317 n.type = Sink::Commands::NotificationType_Inspection; 336 n.type = Sink::Commands::NotificationType_Inspection;
318 n.id = inspectionId; 337 n.id = inspectionId;
319 n.code = Sink::Commands::NotificationCode_Success; 338 n.code = Sink::Commands::NotificationCode_Success;
320 emit notify(n); 339 emit notify(n);
321 }, [=](int code, const QString &message) { 340 }, [=](int code, const QString &message) {
341 Log() << "Inspection failed: "<< inspectionType << inspectionId << entityId << message;
322 Sink::Notification n; 342 Sink::Notification n;
323 n.type = Sink::Commands::NotificationType_Inspection; 343 n.type = Sink::Commands::NotificationType_Inspection;
324 n.message = message; 344 n.message = message;
@@ -341,7 +361,7 @@ GenericResource::GenericResource(const QByteArray &resourceInstanceIdentifier, c
341 mClientLowerBoundRevision = mPipeline->cleanedUpRevision(); 361 mClientLowerBoundRevision = mPipeline->cleanedUpRevision();
342 mProcessor->setOldestUsedRevision(mSourceChangeReplay->getLastReplayedRevision()); 362 mProcessor->setOldestUsedRevision(mSourceChangeReplay->getLastReplayedRevision());
343 363
344 mCommitQueueTimer.setInterval(100); 364 mCommitQueueTimer.setInterval(sCommitInterval);
345 mCommitQueueTimer.setSingleShot(true); 365 mCommitQueueTimer.setSingleShot(true);
346 QObject::connect(&mCommitQueueTimer, &QTimer::timeout, &mUserQueue, &MessageQueue::commit); 366 QObject::connect(&mCommitQueueTimer, &QTimer::timeout, &mUserQueue, &MessageQueue::commit);
347} 367}
@@ -381,13 +401,18 @@ KAsync::Job<void> GenericResource::replay(Sink::Storage &synchronizationStore, c
381 return KAsync::null<void>(); 401 return KAsync::null<void>();
382} 402}
383 403
404void GenericResource::removeDataFromDisk()
405{
406 removeFromDisk(mResourceInstanceIdentifier);
407}
408
384void GenericResource::removeFromDisk(const QByteArray &instanceIdentifier) 409void GenericResource::removeFromDisk(const QByteArray &instanceIdentifier)
385{ 410{
386 Warning() << "Removing from generic resource";
387 Sink::Storage(Sink::storageLocation(), instanceIdentifier, Sink::Storage::ReadWrite).removeFromDisk(); 411 Sink::Storage(Sink::storageLocation(), instanceIdentifier, Sink::Storage::ReadWrite).removeFromDisk();
388 Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".userqueue", Sink::Storage::ReadWrite).removeFromDisk(); 412 Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".userqueue", Sink::Storage::ReadWrite).removeFromDisk();
389 Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".synchronizerqueue", Sink::Storage::ReadWrite).removeFromDisk(); 413 Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".synchronizerqueue", Sink::Storage::ReadWrite).removeFromDisk();
390 Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".changereplay", Sink::Storage::ReadWrite).removeFromDisk(); 414 Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".changereplay", Sink::Storage::ReadWrite).removeFromDisk();
415 Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".synchronization", Sink::Storage::ReadWrite).removeFromDisk();
391} 416}
392 417
393qint64 GenericResource::diskUsage(const QByteArray &instanceIdentifier) 418qint64 GenericResource::diskUsage(const QByteArray &instanceIdentifier)
@@ -556,38 +581,39 @@ void GenericResource::deleteEntity(const QByteArray &sinkId, qint64 revision, co
556 581
557void GenericResource::recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction) 582void GenericResource::recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction)
558{ 583{
559 Index index("rid.mapping." + bufferType, transaction); 584 Index("rid.mapping." + bufferType, transaction).add(remoteId, localId);;
560 Index localIndex("localid.mapping." + bufferType, transaction); 585 Index("localid.mapping." + bufferType, transaction).add(localId, remoteId);
561 index.add(remoteId, localId);
562 localIndex.add(localId, remoteId);
563} 586}
564 587
565void GenericResource::removeRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction) 588void GenericResource::removeRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction)
566{ 589{
567 Index index("rid.mapping." + bufferType, transaction); 590 Index("rid.mapping." + bufferType, transaction).remove(remoteId, localId);
568 Index localIndex("localid.mapping." + bufferType, transaction); 591 Index("localid.mapping." + bufferType, transaction).remove(localId, remoteId);
569 index.remove(remoteId, localId); 592}
570 localIndex.remove(localId, remoteId); 593
594void GenericResource::updateRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction)
595{
596 const auto oldRemoteId = Index("localid.mapping." + bufferType, transaction).lookup(localId);
597 removeRemoteId(bufferType, localId, oldRemoteId, transaction);
598 recordRemoteId(bufferType, localId, remoteId, transaction);
571} 599}
572 600
573QByteArray GenericResource::resolveRemoteId(const QByteArray &bufferType, const QByteArray &remoteId, Sink::Storage::Transaction &transaction) 601QByteArray GenericResource::resolveRemoteId(const QByteArray &bufferType, const QByteArray &remoteId, Sink::Storage::Transaction &transaction)
574{ 602{
575 //Lookup local id for remote id, or insert a new pair otherwise 603 //Lookup local id for remote id, or insert a new pair otherwise
576 Index index("rid.mapping." + bufferType, transaction); 604 Index index("rid.mapping." + bufferType, transaction);
577 Index localIndex("localid.mapping." + bufferType, transaction);
578 QByteArray sinkId = index.lookup(remoteId); 605 QByteArray sinkId = index.lookup(remoteId);
579 if (sinkId.isEmpty()) { 606 if (sinkId.isEmpty()) {
580 sinkId = QUuid::createUuid().toString().toUtf8(); 607 sinkId = QUuid::createUuid().toString().toUtf8();
581 index.add(remoteId, sinkId); 608 index.add(remoteId, sinkId);
582 localIndex.add(sinkId, remoteId); 609 Index("localid.mapping." + bufferType, transaction).add(sinkId, remoteId);
583 } 610 }
584 return sinkId; 611 return sinkId;
585} 612}
586 613
587QByteArray GenericResource::resolveLocalId(const QByteArray &bufferType, const QByteArray &localId, Sink::Storage::Transaction &transaction) 614QByteArray GenericResource::resolveLocalId(const QByteArray &bufferType, const QByteArray &localId, Sink::Storage::Transaction &transaction)
588{ 615{
589 Index index("localid.mapping." + bufferType, transaction); 616 QByteArray remoteId = Index("localid.mapping." + bufferType, transaction).lookup(localId);
590 QByteArray remoteId = index.lookup(localId);
591 if (remoteId.isEmpty()) { 617 if (remoteId.isEmpty()) {
592 Warning() << "Couldn't find the remote id for " << localId; 618 Warning() << "Couldn't find the remote id for " << localId;
593 return QByteArray(); 619 return QByteArray();
@@ -633,7 +659,7 @@ static QSharedPointer<Sink::ApplicationDomain::BufferAdaptor> getLatest(const Si
633 659
634void GenericResource::createOrModify(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, DomainTypeAdaptorFactoryInterface &adaptorFactory, const QByteArray &bufferType, const QByteArray &remoteId, const Sink::ApplicationDomain::ApplicationDomainType &entity) 660void GenericResource::createOrModify(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, DomainTypeAdaptorFactoryInterface &adaptorFactory, const QByteArray &bufferType, const QByteArray &remoteId, const Sink::ApplicationDomain::ApplicationDomainType &entity)
635{ 661{
636 auto mainDatabase = transaction.openDatabase(bufferType + ".main"); 662 auto mainDatabase = Storage::mainDatabase(transaction, bufferType);
637 const auto sinkId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction); 663 const auto sinkId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction);
638 const auto found = mainDatabase.contains(sinkId); 664 const auto found = mainDatabase.contains(sinkId);
639 if (!found) { 665 if (!found) {
@@ -663,4 +689,7 @@ void GenericResource::createOrModify(Sink::Storage::Transaction &transaction, Si
663} 689}
664 690
665 691
692#pragma clang diagnostic push
693#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
666#include "genericresource.moc" 694#include "genericresource.moc"
695#pragma clang diagnostic pop
diff --git a/common/genericresource.h b/common/genericresource.h
index 4ae2645..ceb6b61 100644
--- a/common/genericresource.h
+++ b/common/genericresource.h
@@ -19,7 +19,7 @@
19 */ 19 */
20#pragma once 20#pragma once
21 21
22#include <sinkcommon_export.h> 22#include "sink_export.h"
23#include <resource.h> 23#include <resource.h>
24#include <messagequeue.h> 24#include <messagequeue.h>
25#include <flatbuffers/flatbuffers.h> 25#include <flatbuffers/flatbuffers.h>
@@ -37,7 +37,7 @@ class Preprocessor;
37/** 37/**
38 * Generic Resource implementation. 38 * Generic Resource implementation.
39 */ 39 */
40class SINKCOMMON_EXPORT GenericResource : public Resource 40class SINK_EXPORT GenericResource : public Resource
41{ 41{
42public: 42public:
43 GenericResource(const QByteArray &resourceInstanceIdentifier, const QSharedPointer<Pipeline> &pipeline = QSharedPointer<Pipeline>()); 43 GenericResource(const QByteArray &resourceInstanceIdentifier, const QSharedPointer<Pipeline> &pipeline = QSharedPointer<Pipeline>());
@@ -52,6 +52,7 @@ public:
52 52
53 int error() const; 53 int error() const;
54 54
55 void removeDataFromDisk() Q_DECL_OVERRIDE;
55 static void removeFromDisk(const QByteArray &instanceIdentifier); 56 static void removeFromDisk(const QByteArray &instanceIdentifier);
56 static qint64 diskUsage(const QByteArray &instanceIdentifier); 57 static qint64 diskUsage(const QByteArray &instanceIdentifier);
57 58
@@ -74,6 +75,7 @@ protected:
74 */ 75 */
75 void recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction); 76 void recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction);
76 void removeRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction); 77 void removeRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction);
78 void updateRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction);
77 79
78 /** 80 /**
79 * Tries to find a local id for the remote id, and creates a new local id otherwise. 81 * Tries to find a local id for the remote id, and creates a new local id otherwise.
diff --git a/common/index.h b/common/index.h
index 20213b2..1a5b250 100644
--- a/common/index.h
+++ b/common/index.h
@@ -1,5 +1,6 @@
1#pragma once 1#pragma once
2 2
3#include "sink_export.h"
3#include <string> 4#include <string>
4#include <functional> 5#include <functional>
5#include <QString> 6#include <QString>
@@ -8,7 +9,7 @@
8/** 9/**
9 * An index for value pairs. 10 * An index for value pairs.
10 */ 11 */
11class Index 12class SINK_EXPORT Index
12{ 13{
13public: 14public:
14 enum ErrorCodes { 15 enum ErrorCodes {
diff --git a/common/inspection.h b/common/inspection.h
index b9f6bf3..d85eab6 100644
--- a/common/inspection.h
+++ b/common/inspection.h
@@ -24,7 +24,7 @@
24#include "applicationdomaintype.h" 24#include "applicationdomaintype.h"
25 25
26namespace Sink { 26namespace Sink {
27 namespace Resources { 27 namespace ResourceControl {
28 28
29struct Inspection { 29struct Inspection {
30 static Inspection PropertyInspection(const Sink::ApplicationDomain::Entity &entity, const QByteArray &property, const QVariant &expectedValue) 30 static Inspection PropertyInspection(const Sink::ApplicationDomain::Entity &entity, const QByteArray &property, const QVariant &expectedValue)
diff --git a/common/listener.cpp b/common/listener.cpp
index 13ebbbb..ed6f305 100644
--- a/common/listener.cpp
+++ b/common/listener.cpp
@@ -39,6 +39,9 @@
39#include <QTime> 39#include <QTime>
40#include <QDataStream> 40#include <QDataStream>
41 41
42#undef DEBUG_AREA
43#define DEBUG_AREA "resource.communication"
44
42Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent) 45Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent)
43 : QObject(parent), 46 : QObject(parent),
44 m_server(new QLocalServer(this)), 47 m_server(new QLocalServer(this)),
@@ -234,7 +237,7 @@ void Listener::processCommand(int commandId, uint messageId, const QByteArray &c
234 job = job.then<void>(loadResource()->processAllMessages()); 237 job = job.then<void>(loadResource()->processAllMessages());
235 } 238 }
236 job.then<void>([callback, timer]() { 239 job.then<void>([callback, timer]() {
237 Trace() << "Sync took " << timer->elapsed(); 240 Trace() << "Sync took " << Sink::Log::TraceTime(timer->elapsed());
238 callback(true); 241 callback(true);
239 }).exec(); 242 }).exec();
240 return; 243 return;
@@ -272,6 +275,14 @@ void Listener::processCommand(int commandId, uint messageId, const QByteArray &c
272 loadResource()->setLowerBoundRevision(lowerBoundRevision()); 275 loadResource()->setLowerBoundRevision(lowerBoundRevision());
273 } 276 }
274 break; 277 break;
278 case Sink::Commands::RemoveFromDiskCommand: {
279 Log() << QString("\tReceived a remove from disk command from %1").arg(client.name);
280 m_resource->removeDataFromDisk();
281 delete m_resource;
282 m_resource = nullptr;
283 loadResource()->setLowerBoundRevision(0);
284 }
285 break;
275 default: 286 default:
276 if (commandId > Sink::Commands::CustomCommand) { 287 if (commandId > Sink::Commands::CustomCommand) {
277 Log() << QString("\tReceived custom command from %1: ").arg(client.name) << commandId; 288 Log() << QString("\tReceived custom command from %1: ").arg(client.name) << commandId;
@@ -424,3 +435,7 @@ Sink::Resource *Listener::loadResource()
424 return m_resource; 435 return m_resource;
425} 436}
426 437
438#pragma clang diagnostic push
439#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
440#include "moc_listener.cpp"
441#pragma clang diagnostic pop
diff --git a/common/listener.h b/common/listener.h
index e17f315..5bcc93e 100644
--- a/common/listener.h
+++ b/common/listener.h
@@ -19,6 +19,7 @@
19 19
20#pragma once 20#pragma once
21 21
22#include "sink_export.h"
22#include <QObject> 23#include <QObject>
23 24
24#include <QPointer> 25#include <QPointer>
@@ -56,7 +57,7 @@ public:
56 qint64 currentRevision; 57 qint64 currentRevision;
57}; 58};
58 59
59class Listener : public QObject 60class SINK_EXPORT Listener : public QObject
60{ 61{
61 Q_OBJECT 62 Q_OBJECT
62 63
diff --git a/common/listmodelresult.h b/common/listmodelresult.h
deleted file mode 100644
index 71a0d09..0000000
--- a/common/listmodelresult.h
+++ /dev/null
@@ -1,125 +0,0 @@
1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#pragma once
22#include <QAbstractListModel>
23#include <QDebug>
24
25#include "resultprovider.h"
26
27enum Roles {
28 DomainObjectRole = Qt::UserRole + 1
29};
30
31template<class T>
32class ListModelResult : public QAbstractListModel
33{
34public:
35
36 ListModelResult(const QList<QByteArray> &propertyColumns)
37 :QAbstractListModel(),
38 mPropertyColumns(propertyColumns)
39 {
40 }
41
42 ListModelResult(const QSharedPointer<Sink::ResultEmitter<T> > &emitter, const QList<QByteArray> &propertyColumns)
43 :QAbstractListModel(),
44 mPropertyColumns(propertyColumns)
45 {
46 setEmitter(emitter);
47 }
48
49 void setEmitter(const QSharedPointer<Sink::ResultEmitter<T> > &emitter)
50 {
51 beginResetModel();
52 mEntities.clear();
53 mEmitter = emitter;
54 emitter->onAdded([this](const T &value) {
55 const auto keys = mEntities.keys();
56 int index = 0;
57 for (; index < keys.size(); index++) {
58 if (value->identifier() < keys.at(index)) {
59 break;
60 }
61 }
62 beginInsertRows(QModelIndex(), index, index);
63 mEntities.insert(value->identifier(), value);
64 endInsertRows();
65 });
66 emitter->onModified([this](const T &value) {
67 auto i = mEntities.keys().indexOf(value->identifier());
68 mEntities.remove(value->identifier());
69 mEntities.insert(value->identifier(), value);
70 auto idx = index(i, 0, QModelIndex());
71 emit dataChanged(idx, idx);
72 });
73 emitter->onRemoved([this](const T &value) {
74 auto index = mEntities.keys().indexOf(value->identifier());
75 beginRemoveRows(QModelIndex(), index, index);
76 mEntities.remove(value->identifier());
77 endRemoveRows();
78 });
79 emitter->onInitialResultSetComplete([this]() {
80 });
81 emitter->onComplete([this]() {
82 mEmitter.clear();
83 });
84 emitter->onClear([this]() {
85 beginResetModel();
86 mEntities.clear();
87 endResetModel();
88 });
89 endResetModel();
90 }
91
92 int rowCount(const QModelIndex &parent = QModelIndex()) const
93 {
94 return mEntities.size();
95 }
96
97 int columnCount(const QModelIndex &parent = QModelIndex()) const
98 {
99 return mPropertyColumns.size();
100 }
101
102 virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
103 {
104 if (index.row() >= mEntities.size()) {
105 qWarning() << "Out of bounds access";
106 return QVariant();
107 }
108 if (role == Qt::DisplayRole) {
109 if (index.column() < mPropertyColumns.size()) {
110 auto entity = mEntities.value(mEntities.keys().at(index.row()));
111 return entity->getProperty(mPropertyColumns.at(index.column())).toString();
112 }
113 }
114 if (role == DomainObjectRole) {
115 return QVariant::fromValue(mEntities.value(mEntities.keys().at(index.row())));
116 }
117 return QVariant();
118 }
119
120private:
121 QSharedPointer<Sink::ResultEmitter<T> > mEmitter;
122 QMap<QByteArray, T> mEntities;
123 QList<QByteArray> mPropertyColumns;
124};
125
diff --git a/common/log.cpp b/common/log.cpp
index 45bbec1..96c6f82 100644
--- a/common/log.cpp
+++ b/common/log.cpp
@@ -3,11 +3,19 @@
3#include <QString> 3#include <QString>
4#include <QIODevice> 4#include <QIODevice>
5#include <QCoreApplication> 5#include <QCoreApplication>
6#include <QSettings>
7#include <QStandardPaths>
8#include <QSharedPointer>
6#include <iostream> 9#include <iostream>
7#include <unistd.h> 10#include <unistd.h>
8 11
9using namespace Sink::Log; 12using namespace Sink::Log;
10 13
14static QSharedPointer<QSettings> config()
15{
16 return QSharedPointer<QSettings>::create(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/sink/log.ini", QSettings::IniFormat);
17}
18
11class DebugStream: public QIODevice 19class DebugStream: public QIODevice
12{ 20{
13public: 21public:
@@ -17,24 +25,24 @@ public:
17 { 25 {
18 open(WriteOnly); 26 open(WriteOnly);
19 } 27 }
20 virtual ~DebugStream(){}; 28 virtual ~DebugStream();
21 29
22 bool isSequential() const { return true; } 30 bool isSequential() const { return true; }
23 qint64 readData(char *, qint64) { return 0; /* eof */ } 31 qint64 readData(char *, qint64) { return 0; /* eof */ }
24 qint64 readLineData(char *, qint64) { return 0; /* eof */ } 32 qint64 readLineData(char *, qint64) { return 0; /* eof */ }
25 qint64 writeData(const char *data, qint64 len) 33 qint64 writeData(const char *data, qint64 len)
26 { 34 {
27 const QByteArray buf = QByteArray::fromRawData(data, len); 35 std::cout << data << std::endl;
28 // if (!qgetenv("IMAP_TRACE").isEmpty()) {
29 // qt_message_output(QtDebugMsg, buf.trimmed().constData());
30 std::cout << buf.trimmed().constData() << std::endl;
31 // }
32 return len; 36 return len;
33 } 37 }
34private: 38private:
35 Q_DISABLE_COPY(DebugStream) 39 Q_DISABLE_COPY(DebugStream)
36}; 40};
37 41
42//Virtual method anchor
43DebugStream::~DebugStream()
44{}
45
38class NullStream: public QIODevice 46class NullStream: public QIODevice
39{ 47{
40public: 48public:
@@ -43,7 +51,7 @@ public:
43 { 51 {
44 open(WriteOnly); 52 open(WriteOnly);
45 } 53 }
46 virtual ~NullStream(){}; 54 virtual ~NullStream();
47 55
48 bool isSequential() const { return true; } 56 bool isSequential() const { return true; }
49 qint64 readData(char *, qint64) { return 0; /* eof */ } 57 qint64 readData(char *, qint64) { return 0; /* eof */ }
@@ -56,6 +64,10 @@ private:
56 Q_DISABLE_COPY(NullStream) 64 Q_DISABLE_COPY(NullStream)
57}; 65};
58 66
67//Virtual method anchor
68NullStream::~NullStream()
69{}
70
59 /* 71 /*
60 * ANSI color codes: 72 * ANSI color codes:
61 * 0: reset colors/style 73 * 0: reset colors/style
@@ -130,31 +142,95 @@ DebugLevel Sink::Log::debugLevelFromName(const QByteArray &name)
130 142
131void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel) 143void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel)
132{ 144{
133 qputenv("SINKDEBUGLEVEL", debugLevelName(debugLevel)); 145 config()->setValue("level", debugLevel);
134} 146}
135 147
136Sink::Log::DebugLevel Sink::Log::debugOutputLevel() 148Sink::Log::DebugLevel Sink::Log::debugOutputLevel()
137{ 149{
138 return debugLevelFromName(qgetenv("SINKDEBUGLEVEL")); 150 return static_cast<Sink::Log::DebugLevel>(config()->value("level", Sink::Log::Log).toInt());
151}
152
153void Sink::Log::setDebugOutputFilter(FilterType type, const QByteArrayList &filter)
154{
155 switch (type) {
156 case ApplicationName:
157 config()->setValue("applicationfilter", QVariant::fromValue(filter));
158 break;
159 case Area:
160 config()->setValue("areafilter", QVariant::fromValue(filter));
161 break;
162 }
163}
164
165QByteArrayList Sink::Log::debugOutputFilter(FilterType type)
166{
167 switch (type) {
168 case ApplicationName:
169 return config()->value("applicationfilter").value<QByteArrayList>();
170 case Area:
171 return config()->value("areafilter").value<QByteArrayList>();
172 }
173}
174
175void Sink::Log::setDebugOutputFields(const QByteArrayList &output)
176{
177 config()->setValue("outputfields", QVariant::fromValue(output));
178}
179
180QByteArrayList Sink::Log::debugOutputFields()
181{
182 return config()->value("outputfields").value<QByteArrayList>();
183}
184
185static QByteArray getProgramName()
186{
187 if (QCoreApplication::instance()) {
188 return QCoreApplication::instance()->applicationName().toLocal8Bit();
189 } else {
190 return "<unknown program name>";
191 }
192}
193
194static bool containsItemStartingWith(const QByteArray &pattern, const QByteArrayList &list)
195{
196 for (const auto &item : list) {
197 if (pattern.startsWith(item)) {
198 return true;
199 }
200 }
201 return false;
202}
203
204static bool caseInsensitiveContains(const QByteArray &pattern, const QByteArrayList &list)
205{
206 for (const auto &item : list) {
207 if (item.toLower() == pattern) {
208 return true;
209 }
210 }
211 return false;
139} 212}
140 213
141QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea) 214QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea)
142{ 215{
143 DebugLevel debugOutputLevel = debugLevelFromName(qgetenv("SINKDEBUGLEVEL")); 216 static NullStream nullstream;
144 if (debugLevel < debugOutputLevel) { 217 if (debugLevel < debugOutputLevel()) {
145 static NullStream stream; 218 return QDebug(&nullstream);
146 return QDebug(&stream);
147 } 219 }
148 220
149 static DebugStream stream; 221 auto areas = debugOutputFilter(Sink::Log::Area);
150 QDebug debug(&stream); 222 if (debugArea && !areas.isEmpty()) {
223 if (!containsItemStartingWith(debugArea, areas)) {
224 return QDebug(&nullstream);
225 }
226 }
227 static QByteArray programName = getProgramName();
151 228
152 static QByteArray programName; 229 auto filter = debugOutputFilter(Sink::Log::ApplicationName);
153 if (programName.isEmpty()) { 230 if (!filter.isEmpty() && !filter.contains(programName)) {
154 if (QCoreApplication::instance()) 231 if (!containsItemStartingWith(programName, filter)) {
155 programName = QCoreApplication::instance()->applicationName().toLocal8Bit(); 232 return QDebug(&nullstream);
156 else 233 }
157 programName = "<unknown program name>";
158 } 234 }
159 235
160 QString prefix; 236 QString prefix;
@@ -175,29 +251,54 @@ QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char* file,
175 prefix = "Error: "; 251 prefix = "Error: ";
176 prefixColorCode = ANSI_Colors::Red; 252 prefixColorCode = ANSI_Colors::Red;
177 break; 253 break;
178 default:
179 break;
180 }; 254 };
181 255
182 bool showLocation = false; 256 auto debugOutput = debugOutputFields();
183 bool showProgram = true; 257
258 bool showLocation = debugOutput.isEmpty() ? false : caseInsensitiveContains("location", debugOutput);
259 bool showFunction = debugOutput.isEmpty() ? false : caseInsensitiveContains("function", debugOutput);
260 bool showProgram = debugOutput.isEmpty() ? false : caseInsensitiveContains("application", debugOutput);
261 bool useColor = true;
262 bool multiline = false;
184 263
185 const QString resetColor = colorCommand(ANSI_Colors::Reset); 264 const QString resetColor = colorCommand(ANSI_Colors::Reset);
186 QString output; 265 QString output;
187 output += colorCommand(QList<int>() << ANSI_Colors::Bold << prefixColorCode) + prefix + resetColor; 266 if (useColor) {
267 output += colorCommand(QList<int>() << ANSI_Colors::Bold << prefixColorCode);
268 }
269 output += prefix;
270 if (useColor) {
271 output += resetColor;
272 }
188 if (showProgram) { 273 if (showProgram) {
189 output += QString(" %1(%2)").arg(QString::fromLatin1(programName)).arg(unsigned(getpid())); 274 int width = 10;
275 output += QString(" %1(%2)").arg(QString::fromLatin1(programName).leftJustified(width, ' ', true)).arg(unsigned(getpid())).rightJustified(width + 8, ' ');
190 } 276 }
191 if (debugArea) { 277 if (debugArea) {
192 output += colorCommand(QList<int>() << ANSI_Colors::Bold << prefixColorCode) + QString(" %1 ").arg(QString::fromLatin1(debugArea)) + resetColor; 278 if (useColor) {
279 output += colorCommand(QList<int>() << ANSI_Colors::Bold << prefixColorCode);
280 }
281 output += QString(" %1 ").arg(QString::fromLatin1(debugArea).leftJustified(25, ' ', true));
282 if (useColor) {
283 output += resetColor;
284 }
285 }
286 if (showFunction) {
287 output += QString(" %3").arg(QString::fromLatin1(function).leftJustified(25, ' ', true));
193 } 288 }
194 if (showLocation) { 289 if (showLocation) {
195 output += QString(" %3").arg(function); 290 const auto filename = QString::fromLatin1(file).split('/').last();
196 output += QString("%1:%2").arg(file).arg(line); 291 output += QString(" %1:%2").arg(filename.right(25)).arg(QString::number(line).leftJustified(4, ' ')).leftJustified(30, ' ', true);
292 }
293 if (multiline) {
294 output += "\n ";
197 } 295 }
198 output += ":"; 296 output += ":";
199 297
200 debug << output; 298 static DebugStream stream;
299 QDebug debug(&stream);
300
301 debug.noquote().nospace() << output;
201 302
202 return debug; 303 return debug.space().quote();
203} 304}
diff --git a/common/log.h b/common/log.h
index 483f16f..415c7f7 100644
--- a/common/log.h
+++ b/common/log.h
@@ -1,5 +1,6 @@
1#pragma once 1#pragma once
2 2
3#include "sink_export.h"
3#include <QDebug> 4#include <QDebug>
4 5
5namespace Sink { 6namespace Sink {
@@ -12,19 +13,75 @@ enum DebugLevel {
12 Error 13 Error
13}; 14};
14 15
15QByteArray debugLevelName(DebugLevel debugLevel); 16QByteArray SINK_EXPORT debugLevelName(DebugLevel debugLevel);
16DebugLevel debugLevelFromName(const QByteArray &name); 17DebugLevel SINK_EXPORT debugLevelFromName(const QByteArray &name);
17 18
18void setDebugOutputLevel(DebugLevel); 19/**
19DebugLevel debugOutputLevel(); 20 * Sets the debug output level.
21 *
22 * Everything below is ignored.
23 */
24void SINK_EXPORT setDebugOutputLevel(DebugLevel);
25DebugLevel SINK_EXPORT debugOutputLevel();
20 26
21QDebug debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea = 0); 27enum FilterType {
28 Area,
29 ApplicationName
30};
31
32/**
33 * Sets a debug output filter.
34 *
35 * Everything that is not matching the filter is ignored.
36 * An empty filter matches everything.
37 *
38 * Note: In case of resources the application name is the identifier.
39 */
40void SINK_EXPORT setDebugOutputFilter(FilterType, const QByteArrayList &filter);
41QByteArrayList SINK_EXPORT debugOutputFilter(FilterType type);
42
43/**
44 * Set the debug output fields.
45 *
46 * Currently supported are:
47 * * Name: Application name used for filter.
48 * * Function: The function name:
49 * * Location: The source code location.
50 *
51 * These are additional items to the default ones (level, area, message).
52 */
53void SINK_EXPORT setDebugOutputFields(const QByteArrayList &filter);
54QByteArrayList SINK_EXPORT debugOutputFields();
55
56QDebug SINK_EXPORT debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea = 0);
57
58struct SINK_EXPORT TraceTime
59{
60 TraceTime(int i) : time(i){};
61 const int time;
62};
63
64inline QDebug SINK_EXPORT operator<<(QDebug d, const TraceTime &time)
65{
66 d << time.time << "[ms]";
67 return d;
68}
22 69
23} 70}
24} 71}
25 72
26#define Trace() Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO) 73#define DEBUG_AREA nullptr
27#define Log() Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO) 74
28#define Warning() Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, __LINE__, __FILE__, Q_FUNC_INFO) 75#define Trace_() Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO)
76#define Log_() Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO)
77
78#define Trace_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, AREA)
79#define Log_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, AREA)
80#define Warning_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, __LINE__, __FILE__, Q_FUNC_INFO, AREA)
81#define Error_area(AREA) Sink::Log::debugStream(Sink::Log::DebugLevel::Error, __LINE__, __FILE__, Q_FUNC_INFO, AREA)
82
83#define Trace() Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, DEBUG_AREA)
84#define Log() Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, DEBUG_AREA)
85#define Warning() Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, __LINE__, __FILE__, Q_FUNC_INFO, DEBUG_AREA)
29//FIXME Error clashes with Storage::Error and MessageQueue::Error 86//FIXME Error clashes with Storage::Error and MessageQueue::Error
30#define ErrorMsg() Sink::Log::debugStream(Sink::Log::DebugLevel::Error, __LINE__, __FILE__, Q_FUNC_INFO) 87#define ErrorMsg() Sink::Log::debugStream(Sink::Log::DebugLevel::Error, __LINE__, __FILE__, Q_FUNC_INFO, DEBUG_AREA)
diff --git a/common/messagequeue.cpp b/common/messagequeue.cpp
index 1055922..73198a5 100644
--- a/common/messagequeue.cpp
+++ b/common/messagequeue.cpp
@@ -174,3 +174,7 @@ bool MessageQueue::isEmpty()
174 return count == 0; 174 return count == 0;
175} 175}
176 176
177#pragma clang diagnostic push
178#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
179#include "moc_messagequeue.cpp"
180#pragma clang diagnostic pop
diff --git a/common/messagequeue.h b/common/messagequeue.h
index 3206388..9399055 100644
--- a/common/messagequeue.h
+++ b/common/messagequeue.h
@@ -1,5 +1,6 @@
1#pragma once 1#pragma once
2 2
3#include "sink_export.h"
3#include <QObject> 4#include <QObject>
4#include <QByteArrayList> 5#include <QByteArrayList>
5#include <string> 6#include <string>
@@ -11,7 +12,7 @@
11/** 12/**
12 * A persistent FIFO message queue. 13 * A persistent FIFO message queue.
13 */ 14 */
14class MessageQueue : public QObject 15class SINK_EXPORT MessageQueue : public QObject
15{ 16{
16 Q_OBJECT 17 Q_OBJECT
17public: 18public:
diff --git a/common/modelresult.cpp b/common/modelresult.cpp
index 3a9fb95..f28c665 100644
--- a/common/modelresult.cpp
+++ b/common/modelresult.cpp
@@ -24,6 +24,9 @@
24#include "domain/folder.h" 24#include "domain/folder.h"
25#include "log.h" 25#include "log.h"
26 26
27#undef DEBUG_AREA
28#define DEBUG_AREA "client.modelresult"
29
27static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type) 30static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type)
28{ 31{
29 Q_ASSERT(!type.resourceInstanceIdentifier().isEmpty()); 32 Q_ASSERT(!type.resourceInstanceIdentifier().isEmpty());
diff --git a/common/notification.h b/common/notification.h
index ae24bd2..cf69a99 100644
--- a/common/notification.h
+++ b/common/notification.h
@@ -19,7 +19,7 @@
19 */ 19 */
20#pragma once 20#pragma once
21 21
22#include <sinkcommon_export.h> 22#include "sink_export.h"
23#include <QString> 23#include <QString>
24 24
25namespace Sink 25namespace Sink
@@ -28,7 +28,7 @@ namespace Sink
28/** 28/**
29 * A notification 29 * A notification
30 */ 30 */
31class SINKCOMMON_EXPORT Notification 31class SINK_EXPORT Notification
32{ 32{
33public: 33public:
34 QByteArray id; 34 QByteArray id;
diff --git a/common/notifier.cpp b/common/notifier.cpp
new file mode 100644
index 0000000..e4248df
--- /dev/null
+++ b/common/notifier.cpp
@@ -0,0 +1,69 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "notifier.h"
22
23#include <functional>
24
25#include "resourceaccess.h"
26#include "log.h"
27
28using namespace Sink;
29
30class Sink::Notifier::Private {
31public:
32 Private()
33 : context(new QObject)
34 {
35
36 }
37 QList<QSharedPointer<ResourceAccess> > resourceAccess;
38 QList<std::function<void(const Notification &)> > handler;
39 QSharedPointer<QObject> context;
40};
41
42Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess)
43 : d(new Sink::Notifier::Private)
44{
45 QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification &notification) {
46 for (const auto &handler : d->handler) {
47 handler(notification);
48 }
49 });
50 d->resourceAccess << resourceAccess;
51}
52
53Notifier::Notifier(const QByteArray &instanceIdentifier)
54 : d(new Sink::Notifier::Private)
55{
56 auto resourceAccess = Sink::ResourceAccess::Ptr::create(instanceIdentifier);
57 resourceAccess->open();
58 QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification &notification) {
59 for (const auto &handler : d->handler) {
60 handler(notification);
61 }
62 });
63 d->resourceAccess << resourceAccess;
64}
65
66void Notifier::registerHandler(std::function<void(const Notification &)> handler)
67{
68 d->handler << handler;
69}
diff --git a/common/listmodelresult.cpp b/common/notifier.h
index 6ef1c5f..d16a311 100644
--- a/common/listmodelresult.cpp
+++ b/common/notifier.h
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm> 2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 * 3 *
4 * This library is free software; you can redistribute it and/or 4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public 5 * modify it under the terms of the GNU Lesser General Public
@@ -18,4 +18,29 @@
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */ 19 */
20 20
21#include "listmodelresult.h" 21#pragma once
22
23#include "sink_export.h"
24#include <QByteArray>
25#include <QSharedPointer>
26
27#include <Async/Async>
28
29class QAbstractItemModel;
30
31namespace Sink {
32class ResourceAccess;
33class Notification;
34
35class SINK_EXPORT Notifier {
36public:
37 Notifier(const QSharedPointer<ResourceAccess> &resourceAccess);
38 Notifier(const QByteArray &resourceInstanceIdentifier);
39 void registerHandler(std::function<void(const Notification &)>);
40
41private:
42 class Private;
43 QSharedPointer<Private> d;
44};
45
46}
diff --git a/common/pipeline.cpp b/common/pipeline.cpp
index 401c26d..93d8236 100644
--- a/common/pipeline.cpp
+++ b/common/pipeline.cpp
@@ -25,6 +25,7 @@
25#include <QVector> 25#include <QVector>
26#include <QUuid> 26#include <QUuid>
27#include <QDebug> 27#include <QDebug>
28#include <QTime>
28#include "entity_generated.h" 29#include "entity_generated.h"
29#include "metadata_generated.h" 30#include "metadata_generated.h"
30#include "createentity_generated.h" 31#include "createentity_generated.h"
@@ -36,6 +37,9 @@
36#include "definitions.h" 37#include "definitions.h"
37#include "bufferutils.h" 38#include "bufferutils.h"
38 39
40#undef DEBUG_AREA
41#define DEBUG_AREA "resource.pipeline"
42
39namespace Sink 43namespace Sink
40{ 44{
41 45
@@ -53,8 +57,24 @@ public:
53 QHash<QString, QVector<Preprocessor *> > processors; 57 QHash<QString, QVector<Preprocessor *> > processors;
54 QHash<QString, DomainTypeAdaptorFactoryInterface::Ptr> adaptorFactory; 58 QHash<QString, DomainTypeAdaptorFactoryInterface::Ptr> adaptorFactory;
55 bool revisionChanged; 59 bool revisionChanged;
60 void storeNewRevision(qint64 newRevision, const flatbuffers::FlatBufferBuilder &fbb, const QByteArray &bufferType, const QByteArray &uid);
61 QTime transactionTime;
62 int transactionItemCount;
56}; 63};
57 64
65void Pipeline::Private::storeNewRevision(qint64 newRevision, const flatbuffers::FlatBufferBuilder &fbb, const QByteArray &bufferType, const QByteArray &uid)
66{
67 Storage::mainDatabase(transaction, bufferType).write(Storage::assembleKey(uid, newRevision), BufferUtils::extractBuffer(fbb),
68 [uid, newRevision](const Storage::Error &error) {
69 Warning() << "Failed to write entity" << uid << newRevision;
70 }
71 );
72 revisionChanged = true;
73 Storage::setMaxRevision(transaction, newRevision);
74 Storage::recordRevision(transaction, newRevision, uid, bufferType);
75}
76
77
58Pipeline::Pipeline(const QString &resourceName, QObject *parent) 78Pipeline::Pipeline(const QString &resourceName, QObject *parent)
59 : QObject(parent), 79 : QObject(parent),
60 d(new Private(resourceName)) 80 d(new Private(resourceName))
@@ -86,7 +106,10 @@ void Pipeline::startTransaction()
86 if (d->transaction) { 106 if (d->transaction) {
87 return; 107 return;
88 } 108 }
89 d->transaction = std::move(storage().createTransaction(Sink::Storage::ReadWrite)); 109 Trace() << "Starting transaction.";
110 d->transactionTime.start();
111 d->transactionItemCount = 0;
112 d->transaction = std::move(storage().createTransaction(Storage::ReadWrite));
90} 113}
91 114
92void Pipeline::commit() 115void Pipeline::commit()
@@ -96,8 +119,9 @@ void Pipeline::commit()
96 // for (auto processor : d->processors[bufferType]) { 119 // for (auto processor : d->processors[bufferType]) {
97 // processor->finalize(); 120 // processor->finalize();
98 // } 121 // }
99 const auto revision = Sink::Storage::maxRevision(d->transaction); 122 const auto revision = Storage::maxRevision(d->transaction);
100 Trace() << "Committing " << revision; 123 const auto elapsed = d->transactionTime.elapsed();
124 Trace() << "Committing revision: " << revision << ":" << d->transactionItemCount << " items in: " << Log::TraceTime(elapsed) << " " << (double)elapsed/(double)qMax(d->transactionItemCount, 1) << "[ms/item]";
101 if (d->transaction) { 125 if (d->transaction) {
102 d->transaction.commit(); 126 d->transaction.commit();
103 } 127 }
@@ -118,41 +142,30 @@ Storage &Pipeline::storage() const
118 return d->storage; 142 return d->storage;
119} 143}
120 144
121void Pipeline::storeNewRevision(qint64 newRevision, const flatbuffers::FlatBufferBuilder &fbb, const QByteArray &bufferType, const QByteArray &uid)
122{
123 d->transaction.openDatabase(bufferType + ".main").write(Sink::Storage::assembleKey(uid, newRevision), BufferUtils::extractBuffer(fbb),
124 [](const Sink::Storage::Error &error) {
125 Warning() << "Failed to write entity";
126 }
127 );
128 d->revisionChanged = true;
129 Sink::Storage::setMaxRevision(d->transaction, newRevision);
130 Sink::Storage::recordRevision(d->transaction, newRevision, uid, bufferType);
131}
132
133KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size) 145KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size)
134{ 146{
135 Trace() << "Pipeline: New Entity"; 147 Trace() << "Pipeline: New Entity";
148 d->transactionItemCount++;
136 149
137 { 150 {
138 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(command), size); 151 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(command), size);
139 if (!Sink::Commands::VerifyCreateEntityBuffer(verifyer)) { 152 if (!Commands::VerifyCreateEntityBuffer(verifyer)) {
140 Warning() << "invalid buffer, not a create entity buffer"; 153 Warning() << "invalid buffer, not a create entity buffer";
141 return KAsync::error<qint64>(0); 154 return KAsync::error<qint64>(0);
142 } 155 }
143 } 156 }
144 auto createEntity = Sink::Commands::GetCreateEntity(command); 157 auto createEntity = Commands::GetCreateEntity(command);
145 158
146 const bool replayToSource = createEntity->replayToSource(); 159 const bool replayToSource = createEntity->replayToSource();
147 const QByteArray bufferType = QByteArray(reinterpret_cast<char const*>(createEntity->domainType()->Data()), createEntity->domainType()->size()); 160 const QByteArray bufferType = QByteArray(reinterpret_cast<char const*>(createEntity->domainType()->Data()), createEntity->domainType()->size());
148 { 161 {
149 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(createEntity->delta()->Data()), createEntity->delta()->size()); 162 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(createEntity->delta()->Data()), createEntity->delta()->size());
150 if (!Sink::VerifyEntityBuffer(verifyer)) { 163 if (!VerifyEntityBuffer(verifyer)) {
151 Warning() << "invalid buffer, not an entity buffer"; 164 Warning() << "invalid buffer, not an entity buffer";
152 return KAsync::error<qint64>(0); 165 return KAsync::error<qint64>(0);
153 } 166 }
154 } 167 }
155 auto entity = Sink::GetEntity(createEntity->delta()->Data()); 168 auto entity = GetEntity(createEntity->delta()->Data());
156 if (!entity->resource()->size() && !entity->local()->size()) { 169 if (!entity->resource()->size() && !entity->local()->size()) {
157 Warning() << "No local and no resource buffer while trying to create entity."; 170 Warning() << "No local and no resource buffer while trying to create entity.";
158 return KAsync::error<qint64>(0); 171 return KAsync::error<qint64>(0);
@@ -161,7 +174,7 @@ KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size)
161 QByteArray key; 174 QByteArray key;
162 if (createEntity->entityId()) { 175 if (createEntity->entityId()) {
163 key = QByteArray(reinterpret_cast<char const*>(createEntity->entityId()->Data()), createEntity->entityId()->size()); 176 key = QByteArray(reinterpret_cast<char const*>(createEntity->entityId()->Data()), createEntity->entityId()->size());
164 if (d->transaction.openDatabase(bufferType + ".main").contains(key)) { 177 if (Storage::mainDatabase(d->transaction, bufferType).contains(key)) {
165 ErrorMsg() << "An entity with this id already exists: " << key; 178 ErrorMsg() << "An entity with this id already exists: " << key;
166 return KAsync::error<qint64>(0); 179 return KAsync::error<qint64>(0);
167 } 180 }
@@ -171,21 +184,21 @@ KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size)
171 key = QUuid::createUuid().toString().toUtf8(); 184 key = QUuid::createUuid().toString().toUtf8();
172 } 185 }
173 Q_ASSERT(!key.isEmpty()); 186 Q_ASSERT(!key.isEmpty());
174 const qint64 newRevision = Sink::Storage::maxRevision(d->transaction) + 1; 187 const qint64 newRevision = Storage::maxRevision(d->transaction) + 1;
175 188
176 //Add metadata buffer 189 //Add metadata buffer
177 flatbuffers::FlatBufferBuilder metadataFbb; 190 flatbuffers::FlatBufferBuilder metadataFbb;
178 auto metadataBuilder = Sink::MetadataBuilder(metadataFbb); 191 auto metadataBuilder = MetadataBuilder(metadataFbb);
179 metadataBuilder.add_revision(newRevision); 192 metadataBuilder.add_revision(newRevision);
180 metadataBuilder.add_operation(Sink::Operation_Creation); 193 metadataBuilder.add_operation(Operation_Creation);
181 metadataBuilder.add_replayToSource(replayToSource); 194 metadataBuilder.add_replayToSource(replayToSource);
182 auto metadataBuffer = metadataBuilder.Finish(); 195 auto metadataBuffer = metadataBuilder.Finish();
183 Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer); 196 FinishMetadataBuffer(metadataFbb, metadataBuffer);
184 197
185 flatbuffers::FlatBufferBuilder fbb; 198 flatbuffers::FlatBufferBuilder fbb;
186 EntityBuffer::assembleEntityBuffer(fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), entity->resource()->Data(), entity->resource()->size(), entity->local()->Data(), entity->local()->size()); 199 EntityBuffer::assembleEntityBuffer(fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), entity->resource()->Data(), entity->resource()->size(), entity->local()->Data(), entity->local()->size());
187 200
188 storeNewRevision(newRevision, fbb, bufferType, key); 201 d->storeNewRevision(newRevision, fbb, bufferType, key);
189 202
190 auto adaptorFactory = d->adaptorFactory.value(bufferType); 203 auto adaptorFactory = d->adaptorFactory.value(bufferType);
191 if (!adaptorFactory) { 204 if (!adaptorFactory) {
@@ -194,14 +207,14 @@ KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size)
194 } 207 }
195 208
196 Log() << "Pipeline: wrote entity: " << key << newRevision << bufferType; 209 Log() << "Pipeline: wrote entity: " << key << newRevision << bufferType;
197 d->transaction.openDatabase(bufferType + ".main").scan(Sink::Storage::assembleKey(key, newRevision), [this, bufferType, newRevision, adaptorFactory, key](const QByteArray &, const QByteArray &value) -> bool { 210 Storage::mainDatabase(d->transaction, bufferType).scan(Storage::assembleKey(key, newRevision), [this, bufferType, newRevision, adaptorFactory, key](const QByteArray &, const QByteArray &value) -> bool {
198 auto entity = Sink::GetEntity(value); 211 auto entity = GetEntity(value);
199 auto adaptor = adaptorFactory->createAdaptor(*entity); 212 auto adaptor = adaptorFactory->createAdaptor(*entity);
200 for (auto processor : d->processors[bufferType]) { 213 for (auto processor : d->processors[bufferType]) {
201 processor->newEntity(key, newRevision, *adaptor, d->transaction); 214 processor->newEntity(key, newRevision, *adaptor, d->transaction);
202 } 215 }
203 return false; 216 return false;
204 }, [this](const Sink::Storage::Error &error) { 217 }, [this](const Storage::Error &error) {
205 ErrorMsg() << "Failed to find value in pipeline: " << error.message; 218 ErrorMsg() << "Failed to find value in pipeline: " << error.message;
206 }); 219 });
207 return KAsync::start<qint64>([newRevision](){ 220 return KAsync::start<qint64>([newRevision](){
@@ -212,17 +225,18 @@ KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size)
212KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size) 225KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
213{ 226{
214 Trace() << "Pipeline: Modified Entity"; 227 Trace() << "Pipeline: Modified Entity";
228 d->transactionItemCount++;
215 229
216 const qint64 newRevision = Sink::Storage::maxRevision(d->transaction) + 1; 230 const qint64 newRevision = Storage::maxRevision(d->transaction) + 1;
217 231
218 { 232 {
219 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(command), size); 233 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(command), size);
220 if (!Sink::Commands::VerifyModifyEntityBuffer(verifyer)) { 234 if (!Commands::VerifyModifyEntityBuffer(verifyer)) {
221 Warning() << "invalid buffer, not a modify entity buffer"; 235 Warning() << "invalid buffer, not a modify entity buffer";
222 return KAsync::error<qint64>(0); 236 return KAsync::error<qint64>(0);
223 } 237 }
224 } 238 }
225 auto modifyEntity = Sink::Commands::GetModifyEntity(command); 239 auto modifyEntity = Commands::GetModifyEntity(command);
226 Q_ASSERT(modifyEntity); 240 Q_ASSERT(modifyEntity);
227 241
228 const qint64 baseRevision = modifyEntity->revision(); 242 const qint64 baseRevision = modifyEntity->revision();
@@ -236,7 +250,7 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
236 } 250 }
237 { 251 {
238 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(modifyEntity->delta()->Data()), modifyEntity->delta()->size()); 252 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(modifyEntity->delta()->Data()), modifyEntity->delta()->size());
239 if (!Sink::VerifyEntityBuffer(verifyer)) { 253 if (!VerifyEntityBuffer(verifyer)) {
240 Warning() << "invalid buffer, not an entity buffer"; 254 Warning() << "invalid buffer, not an entity buffer";
241 return KAsync::error<qint64>(0); 255 return KAsync::error<qint64>(0);
242 } 256 }
@@ -249,13 +263,13 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
249 return KAsync::error<qint64>(0); 263 return KAsync::error<qint64>(0);
250 } 264 }
251 265
252 auto diffEntity = Sink::GetEntity(modifyEntity->delta()->Data()); 266 auto diffEntity = GetEntity(modifyEntity->delta()->Data());
253 Q_ASSERT(diffEntity); 267 Q_ASSERT(diffEntity);
254 auto diff = adaptorFactory->createAdaptor(*diffEntity); 268 auto diff = adaptorFactory->createAdaptor(*diffEntity);
255 269
256 QSharedPointer<Sink::ApplicationDomain::BufferAdaptor> current; 270 QSharedPointer<ApplicationDomain::BufferAdaptor> current;
257 d->transaction.openDatabase(bufferType + ".main").findLatest(key, [&current, adaptorFactory](const QByteArray &key, const QByteArray &data) -> bool { 271 Storage::mainDatabase(d->transaction, bufferType).findLatest(key, [&current, adaptorFactory](const QByteArray &key, const QByteArray &data) -> bool {
258 Sink::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); 272 EntityBuffer buffer(const_cast<const char *>(data.data()), data.size());
259 if (!buffer.isValid()) { 273 if (!buffer.isValid()) {
260 Warning() << "Read invalid buffer from disk"; 274 Warning() << "Read invalid buffer from disk";
261 } else { 275 } else {
@@ -273,15 +287,22 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
273 } 287 }
274 288
275 //resource and uid don't matter at this point 289 //resource and uid don't matter at this point
276 const Sink::ApplicationDomain::ApplicationDomainType existingObject("", "", newRevision, current); 290 const ApplicationDomain::ApplicationDomainType existingObject("", "", newRevision, current);
277 auto newObject = Sink::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<Sink::ApplicationDomain::ApplicationDomainType>(existingObject); 291 auto newObject = ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<ApplicationDomain::ApplicationDomainType>(existingObject);
278 292
279 //Apply diff 293 //Apply diff
280 //FIXME only apply the properties that are available in the buffer 294 //FIXME only apply the properties that are available in the buffer
281 Trace() << "Applying changed properties: " << diff->availableProperties(); 295 Trace() << "Applying changed properties: " << diff->availableProperties();
296 QSet<QByteArray> changeset;
282 for (const auto &property : diff->availableProperties()) { 297 for (const auto &property : diff->availableProperties()) {
283 newObject->setProperty(property, diff->getProperty(property)); 298 changeset << property;
299 const auto value = diff->getProperty(property);
300 if (value.isValid()) {
301 newObject->setProperty(property, value);
302 }
284 } 303 }
304 //Altough we only set some properties, we want all to be serialized
305 newObject->setChangedProperties(changeset);
285 306
286 //Remove deletions 307 //Remove deletions
287 if (modifyEntity->deletions()) { 308 if (modifyEntity->deletions()) {
@@ -292,26 +313,29 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
292 313
293 //Add metadata buffer 314 //Add metadata buffer
294 flatbuffers::FlatBufferBuilder metadataFbb; 315 flatbuffers::FlatBufferBuilder metadataFbb;
295 auto metadataBuilder = Sink::MetadataBuilder(metadataFbb); 316 auto metadataBuilder = MetadataBuilder(metadataFbb);
296 metadataBuilder.add_revision(newRevision); 317 metadataBuilder.add_revision(newRevision);
297 metadataBuilder.add_operation(Sink::Operation_Modification); 318 metadataBuilder.add_operation(Operation_Modification);
298 metadataBuilder.add_replayToSource(replayToSource); 319 metadataBuilder.add_replayToSource(replayToSource);
299 auto metadataBuffer = metadataBuilder.Finish(); 320 auto metadataBuffer = metadataBuilder.Finish();
300 Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer); 321 FinishMetadataBuffer(metadataFbb, metadataBuffer);
301 322
302 flatbuffers::FlatBufferBuilder fbb; 323 flatbuffers::FlatBufferBuilder fbb;
303 adaptorFactory->createBuffer(*newObject, fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize()); 324 adaptorFactory->createBuffer(*newObject, fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize());
304 325
305 storeNewRevision(newRevision, fbb, bufferType, key); 326 d->storeNewRevision(newRevision, fbb, bufferType, key);
306 Log() << "Pipeline: modified entity: " << key << newRevision << bufferType; 327 Log() << "Pipeline: modified entity: " << key << newRevision << bufferType;
307 d->transaction.openDatabase(bufferType + ".main").scan(Sink::Storage::assembleKey(key, newRevision), [this, bufferType, newRevision, adaptorFactory, current, key](const QByteArray &, const QByteArray &value) -> bool { 328 Storage::mainDatabase(d->transaction, bufferType).scan(Storage::assembleKey(key, newRevision), [this, bufferType, newRevision, adaptorFactory, current, key](const QByteArray &k, const QByteArray &value) -> bool {
308 auto entity = Sink::GetEntity(value); 329 if (value.isEmpty()) {
330 ErrorMsg() << "Read buffer is empty.";
331 }
332 auto entity = GetEntity(value.data());
309 auto newEntity = adaptorFactory->createAdaptor(*entity); 333 auto newEntity = adaptorFactory->createAdaptor(*entity);
310 for (auto processor : d->processors[bufferType]) { 334 for (auto processor : d->processors[bufferType]) {
311 processor->modifiedEntity(key, newRevision, *current, *newEntity, d->transaction); 335 processor->modifiedEntity(key, newRevision, *current, *newEntity, d->transaction);
312 } 336 }
313 return false; 337 return false;
314 }, [this](const Sink::Storage::Error &error) { 338 }, [this](const Storage::Error &error) {
315 ErrorMsg() << "Failed to find value in pipeline: " << error.message; 339 ErrorMsg() << "Failed to find value in pipeline: " << error.message;
316 }); 340 });
317 return KAsync::start<qint64>([newRevision](){ 341 return KAsync::start<qint64>([newRevision](){
@@ -322,15 +346,16 @@ KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size)
322KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size) 346KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size)
323{ 347{
324 Trace() << "Pipeline: Deleted Entity"; 348 Trace() << "Pipeline: Deleted Entity";
349 d->transactionItemCount++;
325 350
326 { 351 {
327 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(command), size); 352 flatbuffers::Verifier verifyer(reinterpret_cast<const uint8_t *>(command), size);
328 if (!Sink::Commands::VerifyDeleteEntityBuffer(verifyer)) { 353 if (!Commands::VerifyDeleteEntityBuffer(verifyer)) {
329 Warning() << "invalid buffer, not a delete entity buffer"; 354 Warning() << "invalid buffer, not a delete entity buffer";
330 return KAsync::error<qint64>(0); 355 return KAsync::error<qint64>(0);
331 } 356 }
332 } 357 }
333 auto deleteEntity = Sink::Commands::GetDeleteEntity(command); 358 auto deleteEntity = Commands::GetDeleteEntity(command);
334 359
335 const bool replayToSource = deleteEntity->replayToSource(); 360 const bool replayToSource = deleteEntity->replayToSource();
336 const QByteArray bufferType = QByteArray(reinterpret_cast<char const*>(deleteEntity->domainType()->Data()), deleteEntity->domainType()->size()); 361 const QByteArray bufferType = QByteArray(reinterpret_cast<char const*>(deleteEntity->domainType()->Data()), deleteEntity->domainType()->size());
@@ -338,13 +363,12 @@ KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size)
338 363
339 bool found = false; 364 bool found = false;
340 bool alreadyRemoved = false; 365 bool alreadyRemoved = false;
341 d->transaction.openDatabase(bufferType + ".main").findLatest(key, [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) -> bool { 366 Storage::mainDatabase(d->transaction, bufferType).findLatest(key, [&found, &alreadyRemoved](const QByteArray &key, const QByteArray &data) -> bool {
342 Sink::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); 367 auto entity = GetEntity(data.data());
343 auto entity = Sink::GetEntity(data.data());
344 if (entity && entity->metadata()) { 368 if (entity && entity->metadata()) {
345 auto metadata = Sink::GetMetadata(entity->metadata()->Data()); 369 auto metadata = GetMetadata(entity->metadata()->Data());
346 found = true; 370 found = true;
347 if (metadata->operation() == Sink::Operation_Removal) { 371 if (metadata->operation() == Operation_Removal) {
348 alreadyRemoved = true; 372 alreadyRemoved = true;
349 } 373 }
350 374
@@ -364,16 +388,16 @@ KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size)
364 return KAsync::error<qint64>(0); 388 return KAsync::error<qint64>(0);
365 } 389 }
366 390
367 const qint64 newRevision = Sink::Storage::maxRevision(d->transaction) + 1; 391 const qint64 newRevision = Storage::maxRevision(d->transaction) + 1;
368 392
369 //Add metadata buffer 393 //Add metadata buffer
370 flatbuffers::FlatBufferBuilder metadataFbb; 394 flatbuffers::FlatBufferBuilder metadataFbb;
371 auto metadataBuilder = Sink::MetadataBuilder(metadataFbb); 395 auto metadataBuilder = MetadataBuilder(metadataFbb);
372 metadataBuilder.add_revision(newRevision); 396 metadataBuilder.add_revision(newRevision);
373 metadataBuilder.add_operation(Sink::Operation_Removal); 397 metadataBuilder.add_operation(Operation_Removal);
374 metadataBuilder.add_replayToSource(replayToSource); 398 metadataBuilder.add_replayToSource(replayToSource);
375 auto metadataBuffer = metadataBuilder.Finish(); 399 auto metadataBuffer = metadataBuilder.Finish();
376 Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer); 400 FinishMetadataBuffer(metadataFbb, metadataBuffer);
377 401
378 flatbuffers::FlatBufferBuilder fbb; 402 flatbuffers::FlatBufferBuilder fbb;
379 EntityBuffer::assembleEntityBuffer(fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), 0, 0, 0, 0); 403 EntityBuffer::assembleEntityBuffer(fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), 0, 0, 0, 0);
@@ -384,20 +408,20 @@ KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size)
384 return KAsync::error<qint64>(0); 408 return KAsync::error<qint64>(0);
385 } 409 }
386 410
387 QSharedPointer<Sink::ApplicationDomain::BufferAdaptor> current; 411 QSharedPointer<ApplicationDomain::BufferAdaptor> current;
388 d->transaction.openDatabase(bufferType + ".main").findLatest(key, [this, bufferType, newRevision, adaptorFactory, key, &current](const QByteArray &, const QByteArray &data) -> bool { 412 Storage::mainDatabase(d->transaction, bufferType).findLatest(key, [this, bufferType, newRevision, adaptorFactory, key, &current](const QByteArray &, const QByteArray &data) -> bool {
389 Sink::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); 413 EntityBuffer buffer(const_cast<const char *>(data.data()), data.size());
390 if (!buffer.isValid()) { 414 if (!buffer.isValid()) {
391 Warning() << "Read invalid buffer from disk"; 415 Warning() << "Read invalid buffer from disk";
392 } else { 416 } else {
393 current = adaptorFactory->createAdaptor(buffer.entity()); 417 current = adaptorFactory->createAdaptor(buffer.entity());
394 } 418 }
395 return false; 419 return false;
396 }, [this](const Sink::Storage::Error &error) { 420 }, [this](const Storage::Error &error) {
397 ErrorMsg() << "Failed to find value in pipeline: " << error.message; 421 ErrorMsg() << "Failed to find value in pipeline: " << error.message;
398 }); 422 });
399 423
400 storeNewRevision(newRevision, fbb, bufferType, key); 424 d->storeNewRevision(newRevision, fbb, bufferType, key);
401 Log() << "Pipeline: deleted entity: "<< newRevision; 425 Log() << "Pipeline: deleted entity: "<< newRevision;
402 426
403 for (auto processor : d->processors[bufferType]) { 427 for (auto processor : d->processors[bufferType]) {
@@ -411,33 +435,33 @@ KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size)
411 435
412void Pipeline::cleanupRevision(qint64 revision) 436void Pipeline::cleanupRevision(qint64 revision)
413{ 437{
414 const auto uid = Sink::Storage::getUidFromRevision(d->transaction, revision); 438 const auto uid = Storage::getUidFromRevision(d->transaction, revision);
415 const auto bufferType = Sink::Storage::getTypeFromRevision(d->transaction, revision); 439 const auto bufferType = Storage::getTypeFromRevision(d->transaction, revision);
416 Trace() << "Cleaning up revision " << revision << uid << bufferType; 440 Trace() << "Cleaning up revision " << revision << uid << bufferType;
417 d->transaction.openDatabase(bufferType + ".main").scan(uid, [&](const QByteArray &key, const QByteArray &data) -> bool { 441 Storage::mainDatabase(d->transaction, bufferType).scan(uid, [&](const QByteArray &key, const QByteArray &data) -> bool {
418 Sink::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); 442 EntityBuffer buffer(const_cast<const char *>(data.data()), data.size());
419 if (!buffer.isValid()) { 443 if (!buffer.isValid()) {
420 Warning() << "Read invalid buffer from disk"; 444 Warning() << "Read invalid buffer from disk";
421 } else { 445 } else {
422 const auto metadata = flatbuffers::GetRoot<Sink::Metadata>(buffer.metadataBuffer()); 446 const auto metadata = flatbuffers::GetRoot<Metadata>(buffer.metadataBuffer());
423 const qint64 rev = metadata->revision(); 447 const qint64 rev = metadata->revision();
424 //Remove old revisions, and the current if the entity has already been removed 448 //Remove old revisions, and the current if the entity has already been removed
425 if (rev < revision || metadata->operation() == Sink::Operation_Removal) { 449 if (rev < revision || metadata->operation() == Operation_Removal) {
426 Sink::Storage::removeRevision(d->transaction, rev); 450 Storage::removeRevision(d->transaction, rev);
427 d->transaction.openDatabase(bufferType + ".main").remove(key); 451 Storage::mainDatabase(d->transaction, bufferType).remove(key);
428 } 452 }
429 } 453 }
430 454
431 return true; 455 return true;
432 }, [](const Sink::Storage::Error &error) { 456 }, [](const Storage::Error &error) {
433 Warning() << "Error while reading: " << error.message; 457 Warning() << "Error while reading: " << error.message;
434 }, true); 458 }, true);
435 Sink::Storage::setCleanedUpRevision(d->transaction, revision); 459 Storage::setCleanedUpRevision(d->transaction, revision);
436} 460}
437 461
438qint64 Pipeline::cleanedUpRevision() 462qint64 Pipeline::cleanedUpRevision()
439{ 463{
440 return Sink::Storage::cleanedUpRevision(d->transaction); 464 return Storage::cleanedUpRevision(d->transaction);
441} 465}
442 466
443Preprocessor::Preprocessor() 467Preprocessor::Preprocessor()
@@ -459,3 +483,7 @@ void Preprocessor::finalize()
459 483
460} // namespace Sink 484} // namespace Sink
461 485
486#pragma clang diagnostic push
487#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
488#include "moc_pipeline.cpp"
489#pragma clang diagnostic pop
diff --git a/common/pipeline.h b/common/pipeline.h
index 60a5fa5..096771f 100644
--- a/common/pipeline.h
+++ b/common/pipeline.h
@@ -25,7 +25,7 @@
25#include <QSharedDataPointer> 25#include <QSharedDataPointer>
26#include <QObject> 26#include <QObject>
27 27
28#include <sinkcommon_export.h> 28#include "sink_export.h"
29#include <storage.h> 29#include <storage.h>
30 30
31#include <Async/Async> 31#include <Async/Async>
@@ -37,7 +37,7 @@ namespace Sink
37 37
38class Preprocessor; 38class Preprocessor;
39 39
40class SINKCOMMON_EXPORT Pipeline : public QObject 40class SINK_EXPORT Pipeline : public QObject
41{ 41{
42 Q_OBJECT 42 Q_OBJECT
43 43
@@ -73,13 +73,11 @@ Q_SIGNALS:
73 void revisionUpdated(qint64); 73 void revisionUpdated(qint64);
74 74
75private: 75private:
76 void storeNewRevision(qint64 newRevision, const flatbuffers::FlatBufferBuilder &fbb, const QByteArray &bufferType, const QByteArray &uid);
77
78 class Private; 76 class Private;
79 Private * const d; 77 Private * const d;
80}; 78};
81 79
82class SINKCOMMON_EXPORT Preprocessor 80class SINK_EXPORT Preprocessor
83{ 81{
84public: 82public:
85 Preprocessor(); 83 Preprocessor();
diff --git a/common/propertymapper.h b/common/propertymapper.h
index efde72c..57202ab 100644
--- a/common/propertymapper.h
+++ b/common/propertymapper.h
@@ -19,6 +19,7 @@
19 19
20#pragma once 20#pragma once
21 21
22#include "sink_export.h"
22#include <QVariant> 23#include <QVariant>
23#include <QByteArray> 24#include <QByteArray>
24#include <functional> 25#include <functional>
@@ -28,17 +29,17 @@
28 * Defines how to convert qt primitives to flatbuffer ones 29 * Defines how to convert qt primitives to flatbuffer ones
29 */ 30 */
30template <class T> 31template <class T>
31flatbuffers::uoffset_t variantToProperty(const QVariant &, flatbuffers::FlatBufferBuilder &fbb); 32flatbuffers::uoffset_t SINK_EXPORT variantToProperty(const QVariant &, flatbuffers::FlatBufferBuilder &fbb);
32 33
33/** 34/**
34 * Defines how to convert flatbuffer primitives to qt ones 35 * Defines how to convert flatbuffer primitives to qt ones
35 */ 36 */
36template <typename T> 37template <typename T>
37QVariant propertyToVariant(const flatbuffers::String *); 38QVariant SINK_EXPORT propertyToVariant(const flatbuffers::String *);
38template <typename T> 39template <typename T>
39QVariant propertyToVariant(uint8_t); 40QVariant SINK_EXPORT propertyToVariant(uint8_t);
40template <typename T> 41template <typename T>
41QVariant propertyToVariant(const flatbuffers::Vector<uint8_t> *); 42QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<uint8_t> *);
42 43
43 44
44/** 45/**
@@ -52,6 +53,8 @@ template<typename BufferType>
52class ReadPropertyMapper 53class ReadPropertyMapper
53{ 54{
54public: 55public:
56 virtual ~ReadPropertyMapper(){};
57
55 virtual QVariant getProperty(const QByteArray &key, BufferType const *buffer) const 58 virtual QVariant getProperty(const QByteArray &key, BufferType const *buffer) const
56 { 59 {
57 if (mReadAccessors.contains(key)) { 60 if (mReadAccessors.contains(key)) {
@@ -106,6 +109,8 @@ template<typename BufferBuilder>
106class WritePropertyMapper 109class WritePropertyMapper
107{ 110{
108public: 111public:
112 virtual ~WritePropertyMapper(){};
113
109 virtual void setProperty(const QByteArray &key, const QVariant &value, QList<std::function<void(BufferBuilder &)> > &builderCalls, flatbuffers::FlatBufferBuilder &fbb) const 114 virtual void setProperty(const QByteArray &key, const QVariant &value, QList<std::function<void(BufferBuilder &)> > &builderCalls, flatbuffers::FlatBufferBuilder &fbb) const
110 { 115 {
111 if (mWriteAccessors.contains(key)) { 116 if (mWriteAccessors.contains(key)) {
diff --git a/common/queryrunner.cpp b/common/queryrunner.cpp
index 25d69b1..22682d3 100644
--- a/common/queryrunner.cpp
+++ b/common/queryrunner.cpp
@@ -26,6 +26,9 @@
26#include "domainadaptor.h" 26#include "domainadaptor.h"
27#include "asyncutils.h" 27#include "asyncutils.h"
28 28
29#undef DEBUG_AREA
30#define DEBUG_AREA "client.queryrunner"
31
29using namespace Sink; 32using namespace Sink;
30 33
31/* 34/*
@@ -38,14 +41,14 @@ template<typename DomainType>
38class QueryWorker : public QObject 41class QueryWorker : public QObject
39{ 42{
40public: 43public:
41 QueryWorker(const Sink::Query &query, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &, const QByteArray &bufferType); 44 QueryWorker(const Sink::Query &query, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &, const QByteArray &bufferType, const QueryRunnerBase::ResultTransformation &transformation);
42 virtual ~QueryWorker(); 45 virtual ~QueryWorker();
43 46
44 qint64 executeIncrementalQuery(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider); 47 qint64 executeIncrementalQuery(const Sink::Query &query, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider);
45 qint64 executeInitialQuery(const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider); 48 qint64 executeInitialQuery(const Sink::Query &query, const typename DomainType::Ptr &parent, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider);
46 49
47private: 50private:
48 static void replaySet(ResultSet &resultSet, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const QList<QByteArray> &properties); 51 void replaySet(ResultSet &resultSet, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const QList<QByteArray> &properties);
49 52
50 void readEntity(const Sink::Storage::NamedDatabase &db, const QByteArray &key, const std::function<void(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &, Sink::Operation)> &resultCallback); 53 void readEntity(const Sink::Storage::NamedDatabase &db, const QByteArray &key, const std::function<void(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &, Sink::Operation)> &resultCallback);
51 54
@@ -57,6 +60,7 @@ private:
57 qint64 load(const Sink::Query &query, const std::function<ResultSet(Sink::Storage::Transaction &, QSet<QByteArray> &)> &baseSetRetriever, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, bool initialQuery); 60 qint64 load(const Sink::Query &query, const std::function<ResultSet(Sink::Storage::Transaction &, QSet<QByteArray> &)> &baseSetRetriever, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, bool initialQuery);
58 61
59private: 62private:
63 QueryRunnerBase::ResultTransformation mResultTransformation;
60 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory; 64 DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory;
61 QByteArray mResourceInstanceIdentifier; 65 QByteArray mResourceInstanceIdentifier;
62 QByteArray mBufferType; 66 QByteArray mBufferType;
@@ -72,11 +76,11 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
72{ 76{
73 Trace() << "Starting query"; 77 Trace() << "Starting query";
74 //We delegate loading of initial data to the result provider, os it can decide for itself what it needs to load. 78 //We delegate loading of initial data to the result provider, os it can decide for itself what it needs to load.
75 mResultProvider->setFetcher([this, query, instanceIdentifier, factory, bufferType](const typename DomainType::Ptr &parent) { 79 mResultProvider->setFetcher([=](const typename DomainType::Ptr &parent) {
76 Trace() << "Running fetcher"; 80 Trace() << "Running fetcher";
77 auto resultProvider = mResultProvider; 81 auto resultProvider = mResultProvider;
78 async::run<qint64>([query, instanceIdentifier, factory, bufferType, parent, resultProvider]() -> qint64 { 82 async::run<qint64>([=]() -> qint64 {
79 QueryWorker<DomainType> worker(query, instanceIdentifier, factory, bufferType); 83 QueryWorker<DomainType> worker(query, instanceIdentifier, factory, bufferType, mResultTransformation);
80 const qint64 newRevision = worker.executeInitialQuery(query, parent, *resultProvider); 84 const qint64 newRevision = worker.executeInitialQuery(query, parent, *resultProvider);
81 return newRevision; 85 return newRevision;
82 }) 86 })
@@ -91,18 +95,17 @@ QueryRunner<DomainType>::QueryRunner(const Sink::Query &query, const Sink::Resou
91 // In case of a live query we keep the runner for as long alive as the result provider exists 95 // In case of a live query we keep the runner for as long alive as the result provider exists
92 if (query.liveQuery) { 96 if (query.liveQuery) {
93 //Incremental updates are always loaded directly, leaving it up to the result to discard the changes if they are not interesting 97 //Incremental updates are always loaded directly, leaving it up to the result to discard the changes if they are not interesting
94 setQuery([this, query, instanceIdentifier, factory, bufferType] () -> KAsync::Job<void> { 98 setQuery([=] () -> KAsync::Job<void> {
95 auto resultProvider = mResultProvider; 99 auto resultProvider = mResultProvider;
96 return async::run<qint64>([query, instanceIdentifier, factory, bufferType, resultProvider]() -> qint64 { 100 return async::run<qint64>([=]() -> qint64 {
97 QueryWorker<DomainType> worker(query, instanceIdentifier, factory, bufferType); 101 QueryWorker<DomainType> worker(query, instanceIdentifier, factory, bufferType, mResultTransformation);
98 const qint64 newRevision = worker.executeIncrementalQuery(query, *resultProvider); 102 const qint64 newRevision = worker.executeIncrementalQuery(query, *resultProvider);
99 return newRevision; 103 return newRevision;
100 }) 104 })
101 .template then<void, qint64>([query, this](qint64 newRevision) { 105 .template then<void, qint64>([query, this](qint64 newRevision) {
102 //Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise. 106 //Only send the revision replayed information if we're connected to the resource, there's no need to start the resource otherwise.
103 mResourceAccess->sendRevisionReplayedCommand(newRevision); 107 mResourceAccess->sendRevisionReplayedCommand(newRevision);
104 }) 108 });
105 .template then<void>([](){});
106 }); 109 });
107 //Ensure the connection is open, if it wasn't already opened 110 //Ensure the connection is open, if it wasn't already opened
108 //TODO If we are not connected already, we have to check for the latest revision once connected, otherwise we could miss some updates 111 //TODO If we are not connected already, we have to check for the latest revision once connected, otherwise we could miss some updates
@@ -118,6 +121,12 @@ QueryRunner<DomainType>::~QueryRunner()
118} 121}
119 122
120template<class DomainType> 123template<class DomainType>
124void QueryRunner<DomainType>::setResultTransformation(const ResultTransformation &transformation)
125{
126 mResultTransformation = transformation;
127}
128
129template<class DomainType>
121typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr QueryRunner<DomainType>::emitter() 130typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr QueryRunner<DomainType>::emitter()
122{ 131{
123 return mResultProvider->emitter(); 132 return mResultProvider->emitter();
@@ -129,7 +138,7 @@ static inline ResultSet fullScan(const Sink::Storage::Transaction &transaction,
129{ 138{
130 //TODO use a result set with an iterator, to read values on demand 139 //TODO use a result set with an iterator, to read values on demand
131 QVector<QByteArray> keys; 140 QVector<QByteArray> keys;
132 transaction.openDatabase(bufferType + ".main").scan(QByteArray(), [&](const QByteArray &key, const QByteArray &value) -> bool { 141 Storage::mainDatabase(transaction, bufferType).scan(QByteArray(), [&](const QByteArray &key, const QByteArray &value) -> bool {
133 //Skip internals 142 //Skip internals
134 if (Sink::Storage::isInternalKey(key)) { 143 if (Sink::Storage::isInternalKey(key)) {
135 return true; 144 return true;
@@ -147,8 +156,9 @@ static inline ResultSet fullScan(const Sink::Storage::Transaction &transaction,
147 156
148 157
149template<class DomainType> 158template<class DomainType>
150QueryWorker<DomainType>::QueryWorker(const Sink::Query &query, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &factory, const QByteArray &bufferType) 159QueryWorker<DomainType>::QueryWorker(const Sink::Query &query, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &factory, const QByteArray &bufferType, const QueryRunnerBase::ResultTransformation &transformation)
151 : QObject(), 160 : QObject(),
161 mResultTransformation(transformation),
152 mDomainTypeAdaptorFactory(factory), 162 mDomainTypeAdaptorFactory(factory),
153 mResourceInstanceIdentifier(instanceIdentifier), 163 mResourceInstanceIdentifier(instanceIdentifier),
154 mBufferType(bufferType), 164 mBufferType(bufferType),
@@ -167,20 +177,25 @@ template<class DomainType>
167void QueryWorker<DomainType>::replaySet(ResultSet &resultSet, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const QList<QByteArray> &properties) 177void QueryWorker<DomainType>::replaySet(ResultSet &resultSet, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const QList<QByteArray> &properties)
168{ 178{
169 int counter = 0; 179 int counter = 0;
170 while (resultSet.next([&resultProvider, &counter, &properties](const Sink::ApplicationDomain::ApplicationDomainType::Ptr &value, Sink::Operation operation) -> bool { 180 while (resultSet.next([this, &resultProvider, &counter, &properties](const Sink::ApplicationDomain::ApplicationDomainType::Ptr &value, Sink::Operation operation) -> bool {
181 //FIXME allow maildir resource to set the mimeMessage property
182 auto valueCopy = Sink::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value, properties).template staticCast<DomainType>();
183 if (mResultTransformation) {
184 mResultTransformation(*valueCopy);
185 }
171 counter++; 186 counter++;
172 switch (operation) { 187 switch (operation) {
173 case Sink::Operation_Creation: 188 case Sink::Operation_Creation:
174 // Trace() << "Got creation"; 189 // Trace() << "Got creation";
175 resultProvider.add(Sink::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value, properties).template staticCast<DomainType>()); 190 resultProvider.add(valueCopy);
176 break; 191 break;
177 case Sink::Operation_Modification: 192 case Sink::Operation_Modification:
178 // Trace() << "Got modification"; 193 // Trace() << "Got modification";
179 resultProvider.modify(Sink::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value, properties).template staticCast<DomainType>()); 194 resultProvider.modify(valueCopy);
180 break; 195 break;
181 case Sink::Operation_Removal: 196 case Sink::Operation_Removal:
182 // Trace() << "Got removal"; 197 // Trace() << "Got removal";
183 resultProvider.remove(Sink::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value, properties).template staticCast<DomainType>()); 198 resultProvider.remove(valueCopy);
184 break; 199 break;
185 } 200 }
186 return true; 201 return true;
@@ -319,7 +334,7 @@ qint64 QueryWorker<DomainType>::load(const Sink::Query &query, const std::functi
319 Warning() << "Error during query: " << error.store << error.message; 334 Warning() << "Error during query: " << error.store << error.message;
320 }); 335 });
321 auto transaction = storage.createTransaction(Sink::Storage::ReadOnly); 336 auto transaction = storage.createTransaction(Sink::Storage::ReadOnly);
322 auto db = transaction.openDatabase(mBufferType + ".main"); 337 auto db = Storage::mainDatabase(transaction, mBufferType);
323 338
324 QSet<QByteArray> remainingFilters; 339 QSet<QByteArray> remainingFilters;
325 auto resultSet = baseSetRetriever(transaction, remainingFilters); 340 auto resultSet = baseSetRetriever(transaction, remainingFilters);
diff --git a/common/queryrunner.h b/common/queryrunner.h
index 0ee6a81..04e4587 100644
--- a/common/queryrunner.h
+++ b/common/queryrunner.h
@@ -32,6 +32,9 @@
32class QueryRunnerBase : public QObject 32class QueryRunnerBase : public QObject
33{ 33{
34 Q_OBJECT 34 Q_OBJECT
35public:
36 typedef std::function<void(Sink::ApplicationDomain::ApplicationDomainType &domainObject)> ResultTransformation;
37
35protected: 38protected:
36 typedef std::function<KAsync::Job<void>()> QueryFunction; 39 typedef std::function<KAsync::Job<void>()> QueryFunction;
37 40
@@ -43,7 +46,6 @@ protected:
43 queryFunction = query; 46 queryFunction = query;
44 } 47 }
45 48
46
47protected slots: 49protected slots:
48 /** 50 /**
49 * Rerun query with new revision 51 * Rerun query with new revision
@@ -82,10 +84,17 @@ public:
82 QueryRunner(const Sink::Query &query, const Sink::ResourceAccessInterface::Ptr &, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &, const QByteArray &bufferType); 84 QueryRunner(const Sink::Query &query, const Sink::ResourceAccessInterface::Ptr &, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &, const QByteArray &bufferType);
83 virtual ~QueryRunner(); 85 virtual ~QueryRunner();
84 86
87 /**
88 * Allows you to run a transformation on every result.
89 * This transformation is executed in the query thread.
90 */
91 void setResultTransformation(const ResultTransformation &transformation);
92
85 typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr emitter(); 93 typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr emitter();
86 94
87private: 95private:
88 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess; 96 QSharedPointer<Sink::ResourceAccessInterface> mResourceAccess;
89 QSharedPointer<Sink::ResultProvider<typename DomainType::Ptr> > mResultProvider; 97 QSharedPointer<Sink::ResultProvider<typename DomainType::Ptr> > mResultProvider;
98 ResultTransformation mResultTransformation;
90}; 99};
91 100
diff --git a/common/resource.cpp b/common/resource.cpp
index 8c448a8..5cbb2f2 100644
--- a/common/resource.cpp
+++ b/common/resource.cpp
@@ -34,7 +34,7 @@ Resource::Resource()
34 : QObject(), 34 : QObject(),
35 d(0) 35 d(0)
36{ 36{
37 37 Q_UNUSED(d);
38} 38}
39 39
40Resource::~Resource() 40Resource::~Resource()
@@ -63,6 +63,11 @@ void Resource::setLowerBoundRevision(qint64 revision)
63 Q_UNUSED(revision) 63 Q_UNUSED(revision)
64} 64}
65 65
66void Resource::removeDataFromDisk()
67{
68}
69
70
66class ResourceFactory::Private 71class ResourceFactory::Private
67{ 72{
68public: 73public:
@@ -75,7 +80,7 @@ ResourceFactory::ResourceFactory(QObject *parent)
75 : QObject(parent), 80 : QObject(parent),
76 d(0) 81 d(0)
77{ 82{
78 83 Q_UNUSED(d);
79} 84}
80 85
81ResourceFactory::~ResourceFactory() 86ResourceFactory::~ResourceFactory()
@@ -129,3 +134,9 @@ ResourceFactory *ResourceFactory::load(const QString &resourceName)
129} 134}
130 135
131} // namespace Sink 136} // namespace Sink
137
138//Ignore warning I don't know how to fix in a moc file
139#pragma clang diagnostic push
140#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
141#include "moc_resource.cpp"
142#pragma clang diagnostic pop
diff --git a/common/resource.h b/common/resource.h
index 30d6c46..ab30cb9 100644
--- a/common/resource.h
+++ b/common/resource.h
@@ -19,7 +19,7 @@
19 */ 19 */
20#pragma once 20#pragma once
21 21
22#include <sinkcommon_export.h> 22#include "sink_export.h"
23 23
24#include <Async/Async> 24#include <Async/Async>
25#include "notification.h" 25#include "notification.h"
@@ -31,7 +31,7 @@ class FacadeFactory;
31/** 31/**
32 * Resource interface 32 * Resource interface
33 */ 33 */
34class SINKCOMMON_EXPORT Resource : public QObject 34class SINK_EXPORT Resource : public QObject
35{ 35{
36 Q_OBJECT 36 Q_OBJECT
37public: 37public:
@@ -55,6 +55,11 @@ public:
55 */ 55 */
56 virtual void setLowerBoundRevision(qint64 revision); 56 virtual void setLowerBoundRevision(qint64 revision);
57 57
58 /**
59 * Remove the data from disk
60 */
61 virtual void removeDataFromDisk();
62
58Q_SIGNALS: 63Q_SIGNALS:
59 void revisionUpdated(qint64); 64 void revisionUpdated(qint64);
60 void notify(Notification); 65 void notify(Notification);
@@ -67,7 +72,7 @@ private:
67/** 72/**
68 * Factory interface for resource to implement. 73 * Factory interface for resource to implement.
69 */ 74 */
70class ResourceFactory : public QObject 75class SINK_EXPORT ResourceFactory : public QObject
71{ 76{
72public: 77public:
73 static ResourceFactory *load(const QString &resourceName); 78 static ResourceFactory *load(const QString &resourceName);
diff --git a/common/resourceaccess.cpp b/common/resourceaccess.cpp
index 0c435c9..80d60e8 100644
--- a/common/resourceaccess.cpp
+++ b/common/resourceaccess.cpp
@@ -41,11 +41,13 @@
41#include <QProcess> 41#include <QProcess>
42#include <QDataStream> 42#include <QDataStream>
43#include <QBuffer> 43#include <QBuffer>
44#include <QTime>
44 45
45#undef Trace 46#undef Trace
46#define Trace() Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess") 47#define TracePrivate() Trace_area("client.communication." + resourceInstanceIdentifier)
48#define Trace() Trace_area("client.communication." + d->resourceInstanceIdentifier)
47#undef Log 49#undef Log
48#define Log(IDENTIFIER) Sink::Log::debugStream(Sink::Log::DebugLevel::Log, __LINE__, __FILE__, Q_FUNC_INFO, "ResourceAccess("+IDENTIFIER+")") 50#define Log() Log_area("client.communication." + d->resourceInstanceIdentifier)
49 51
50static void queuedInvoke(const std::function<void()> &f, QObject *context = 0) 52static void queuedInvoke(const std::function<void()> &f, QObject *context = 0)
51{ 53{
@@ -168,45 +170,48 @@ KAsync::Job<void> ResourceAccess::Private::tryToConnect()
168 return !socket; 170 return !socket;
169 }, 171 },
170 [this, counter](KAsync::Future<void> &future) { 172 [this, counter](KAsync::Future<void> &future) {
171 Trace() << "Loop"; 173 TracePrivate() << "Loop";
172 KAsync::wait(50) 174 connectToServer(resourceInstanceIdentifier)
173 .then(connectToServer(resourceInstanceIdentifier))
174 .then<void, QSharedPointer<QLocalSocket> >([this, &future](const QSharedPointer<QLocalSocket> &s) { 175 .then<void, QSharedPointer<QLocalSocket> >([this, &future](const QSharedPointer<QLocalSocket> &s) {
175 Q_ASSERT(s); 176 Q_ASSERT(s);
176 socket = s; 177 socket = s;
177 future.setFinished(); 178 future.setFinished();
178 }, 179 }, [&future, counter, this](int errorCode, const QString &errorString) {
179 [&future, counter](int errorCode, const QString &errorString) { 180 static int waitTime = 10;
180 const int maxRetries = 10; 181 static int timeout = 500;
182 static int maxRetries = timeout / waitTime;
181 if (*counter > maxRetries) { 183 if (*counter > maxRetries) {
182 Trace() << "Giving up"; 184 TracePrivate() << "Giving up";
183 future.setError(-1, "Failed to connect to socket"); 185 future.setError(-1, "Failed to connect to socket");
184 } else { 186 } else {
185 future.setFinished(); 187 KAsync::wait(waitTime).then<void>([&future]() {
188 future.setFinished();
189 }).exec();
186 } 190 }
187 *counter = *counter + 1; 191 *counter = *counter + 1;
188 }).exec(); 192 })
193 .exec();
189 }); 194 });
190} 195}
191 196
192KAsync::Job<void> ResourceAccess::Private::initializeSocket() 197KAsync::Job<void> ResourceAccess::Private::initializeSocket()
193{ 198{
194 return KAsync::start<void>([this](KAsync::Future<void> &future) { 199 return KAsync::start<void>([this](KAsync::Future<void> &future) {
195 Trace() << "Trying to connect"; 200 TracePrivate() << "Trying to connect";
196 connectToServer(resourceInstanceIdentifier).then<void, QSharedPointer<QLocalSocket> >([this, &future](const QSharedPointer<QLocalSocket> &s) { 201 connectToServer(resourceInstanceIdentifier).then<void, QSharedPointer<QLocalSocket> >([this, &future](const QSharedPointer<QLocalSocket> &s) {
197 Trace() << "Connected to resource, without having to start it."; 202 TracePrivate() << "Connected to resource, without having to start it.";
198 Q_ASSERT(s); 203 Q_ASSERT(s);
199 socket = s; 204 socket = s;
200 future.setFinished(); 205 future.setFinished();
201 }, 206 },
202 [this, &future](int errorCode, const QString &errorString) { 207 [this, &future](int errorCode, const QString &errorString) {
203 Trace() << "Failed to connect, starting resource"; 208 TracePrivate() << "Failed to connect, starting resource";
204 //We failed to connect, so let's start the resource 209 //We failed to connect, so let's start the resource
205 QStringList args; 210 QStringList args;
206 args << resourceInstanceIdentifier; 211 args << resourceInstanceIdentifier;
207 qint64 pid = 0; 212 qint64 pid = 0;
208 if (QProcess::startDetached("sink_synchronizer", args, QDir::homePath(), &pid)) { 213 if (QProcess::startDetached("sink_synchronizer", args, QDir::homePath(), &pid)) {
209 Trace() << "Started resource " << pid; 214 TracePrivate() << "Started resource " << pid;
210 tryToConnect() 215 tryToConnect()
211 .then<void>([&future]() { 216 .then<void>([&future]() {
212 future.setFinished(); 217 future.setFinished();
@@ -232,12 +237,12 @@ ResourceAccess::ResourceAccess(const QByteArray &resourceInstanceIdentifier)
232 : ResourceAccessInterface(), 237 : ResourceAccessInterface(),
233 d(new Private(getResourceName(resourceInstanceIdentifier), resourceInstanceIdentifier, this)) 238 d(new Private(getResourceName(resourceInstanceIdentifier), resourceInstanceIdentifier, this))
234{ 239{
235 log("Starting access"); 240 Log() << "Starting access";
236} 241}
237 242
238ResourceAccess::~ResourceAccess() 243ResourceAccess::~ResourceAccess()
239{ 244{
240 log("Closing access"); 245 Log() << "Closing access";
241 if (!d->resultHandler.isEmpty()) { 246 if (!d->resultHandler.isEmpty()) {
242 Warning() << "Left jobs running while shutting down ResourceAccess: " << d->resultHandler.keys(); 247 Warning() << "Left jobs running while shutting down ResourceAccess: " << d->resultHandler.keys();
243 } 248 }
@@ -380,9 +385,11 @@ void ResourceAccess::open()
380 if (d->openingSocket) { 385 if (d->openingSocket) {
381 return; 386 return;
382 } 387 }
388 auto time = QSharedPointer<QTime>::create();
389 time->start();
383 d->openingSocket = true; 390 d->openingSocket = true;
384 d->initializeSocket().then<void>([this]() { 391 d->initializeSocket().then<void>([this, time]() {
385 Trace() << "Socket is initialized"; 392 Trace() << "Socket is initialized." << Log::TraceTime(time->elapsed());
386 d->openingSocket = false; 393 d->openingSocket = false;
387 QObject::connect(d->socket.data(), &QLocalSocket::disconnected, 394 QObject::connect(d->socket.data(), &QLocalSocket::disconnected,
388 this, &ResourceAccess::disconnected); 395 this, &ResourceAccess::disconnected);
@@ -400,7 +407,7 @@ void ResourceAccess::open()
400 407
401void ResourceAccess::close() 408void ResourceAccess::close()
402{ 409{
403 log(QString("Closing %1").arg(d->socket->fullServerName())); 410 Log() << QString("Closing %1").arg(d->socket->fullServerName());
404 Trace() << "Pending commands: " << d->pendingCommands.size(); 411 Trace() << "Pending commands: " << d->pendingCommands.size();
405 Trace() << "Queued commands: " << d->commandQueue.size(); 412 Trace() << "Queued commands: " << d->commandQueue.size();
406 d->socket->close(); 413 d->socket->close();
@@ -412,7 +419,7 @@ void ResourceAccess::sendCommand(const QSharedPointer<QueuedCommand> &command)
412 //TODO: we should have a timeout for commands 419 //TODO: we should have a timeout for commands
413 d->messageId++; 420 d->messageId++;
414 const auto messageId = d->messageId; 421 const auto messageId = d->messageId;
415 log(QString("Sending command \"%1\" with messageId %2").arg(QString(Sink::Commands::name(command->commandId))).arg(d->messageId)); 422 Log() << QString("Sending command \"%1\" with messageId %2").arg(QString(Sink::Commands::name(command->commandId))).arg(d->messageId);
416 Q_ASSERT(command->callback); 423 Q_ASSERT(command->callback);
417 registerCallback(d->messageId, [this, messageId, command](int errorCode, QString errorMessage) { 424 registerCallback(d->messageId, [this, messageId, command](int errorCode, QString errorMessage) {
418 Trace() << "Command complete " << messageId; 425 Trace() << "Command complete " << messageId;
@@ -452,7 +459,7 @@ void ResourceAccess::connected()
452 return; 459 return;
453 } 460 }
454 461
455 log(QString("Connected: %1").arg(d->socket->fullServerName())); 462 Log() << QString("Connected: %1").arg(d->socket->fullServerName());
456 463
457 { 464 {
458 flatbuffers::FlatBufferBuilder fbb; 465 flatbuffers::FlatBufferBuilder fbb;
@@ -472,7 +479,7 @@ void ResourceAccess::connected()
472 479
473void ResourceAccess::disconnected() 480void ResourceAccess::disconnected()
474{ 481{
475 log(QString("Disconnected from %1").arg(d->socket->fullServerName())); 482 Log() << QString("Disconnected from %1").arg(d->socket->fullServerName());
476 d->socket->close(); 483 d->socket->close();
477 emit ready(false); 484 emit ready(false);
478} 485}
@@ -480,7 +487,7 @@ void ResourceAccess::disconnected()
480void ResourceAccess::connectionError(QLocalSocket::LocalSocketError error) 487void ResourceAccess::connectionError(QLocalSocket::LocalSocketError error)
481{ 488{
482 if (error == QLocalSocket::PeerClosedError) { 489 if (error == QLocalSocket::PeerClosedError) {
483 Log(d->resourceInstanceIdentifier) << "The resource closed the connection."; 490 Log() << "The resource closed the connection.";
484 d->abortPendingOperations(); 491 d->abortPendingOperations();
485 } else { 492 } else {
486 Warning() << QString("Connection error: %1 : %2").arg(error).arg(d->socket->errorString()); 493 Warning() << QString("Connection error: %1 : %2").arg(error).arg(d->socket->errorString());
@@ -524,14 +531,17 @@ bool ResourceAccess::processMessageBuffer()
524 switch (commandId) { 531 switch (commandId) {
525 case Commands::RevisionUpdateCommand: { 532 case Commands::RevisionUpdateCommand: {
526 auto buffer = Commands::GetRevisionUpdate(d->partialMessageBuffer.constData() + headerSize); 533 auto buffer = Commands::GetRevisionUpdate(d->partialMessageBuffer.constData() + headerSize);
527 log(QString("Revision updated to: %1").arg(buffer->revision())); 534 Log() << QString("Revision updated to: %1").arg(buffer->revision());
535 Notification n;
536 n.type = Sink::Commands::NotificationType::NotificationType_RevisionUpdate;
537 emit notification(n);
528 emit revisionChanged(buffer->revision()); 538 emit revisionChanged(buffer->revision());
529 539
530 break; 540 break;
531 } 541 }
532 case Commands::CommandCompletionCommand: { 542 case Commands::CommandCompletionCommand: {
533 auto buffer = Commands::GetCommandCompletion(d->partialMessageBuffer.constData() + headerSize); 543 auto buffer = Commands::GetCommandCompletion(d->partialMessageBuffer.constData() + headerSize);
534 log(QString("Command with messageId %1 completed %2").arg(buffer->id()).arg(buffer->success() ? "sucessfully" : "unsuccessfully")); 544 Log() << QString("Command with messageId %1 completed %2").arg(buffer->id()).arg(buffer->success() ? "sucessfully" : "unsuccessfully");
535 545
536 d->completeCommands << buffer->id(); 546 d->completeCommands << buffer->id();
537 //The callbacks can result in this object getting destroyed directly, so we need to ensure we finish our work first 547 //The callbacks can result in this object getting destroyed directly, so we need to ensure we finish our work first
@@ -544,11 +554,11 @@ bool ResourceAccess::processMessageBuffer()
544 auto buffer = Commands::GetNotification(d->partialMessageBuffer.constData() + headerSize); 554 auto buffer = Commands::GetNotification(d->partialMessageBuffer.constData() + headerSize);
545 switch (buffer->type()) { 555 switch (buffer->type()) {
546 case Sink::Commands::NotificationType::NotificationType_Shutdown: 556 case Sink::Commands::NotificationType::NotificationType_Shutdown:
547 Log(d->resourceInstanceIdentifier) << "Received shutdown notification."; 557 Log() << "Received shutdown notification.";
548 close(); 558 close();
549 break; 559 break;
550 case Sink::Commands::NotificationType::NotificationType_Inspection: { 560 case Sink::Commands::NotificationType::NotificationType_Inspection: {
551 Log(d->resourceInstanceIdentifier) << "Received inspection notification."; 561 Log() << "Received inspection notification.";
552 Notification n; 562 Notification n;
553 if (buffer->identifier()) { 563 if (buffer->identifier()) {
554 //Don't use fromRawData, the buffer is gone once we invoke emit notification 564 //Don't use fromRawData, the buffer is gone once we invoke emit notification
@@ -566,6 +576,10 @@ bool ResourceAccess::processMessageBuffer()
566 }, this); 576 }, this);
567 } 577 }
568 break; 578 break;
579 case Sink::Commands::NotificationType::NotificationType_Status:
580 case Sink::Commands::NotificationType::NotificationType_Warning:
581 case Sink::Commands::NotificationType::NotificationType_Progress:
582 case Sink::Commands::NotificationType::NotificationType_RevisionUpdate:
569 default: 583 default:
570 Warning() << "Received unknown notification: " << buffer->type(); 584 Warning() << "Received unknown notification: " << buffer->type();
571 break; 585 break;
@@ -580,9 +594,9 @@ bool ResourceAccess::processMessageBuffer()
580 return d->partialMessageBuffer.size() >= headerSize; 594 return d->partialMessageBuffer.size() >= headerSize;
581} 595}
582 596
583void ResourceAccess::log(const QString &message)
584{
585 Log(d->resourceInstanceIdentifier) << this << message;
586} 597}
587 598
588} 599#pragma clang diagnostic push
600#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
601#include "moc_resourceaccess.cpp"
602#pragma clang diagnostic pop
diff --git a/common/resourceaccess.h b/common/resourceaccess.h
index 73b676b..4c10adb 100644
--- a/common/resourceaccess.h
+++ b/common/resourceaccess.h
@@ -20,6 +20,7 @@
20 20
21#pragma once 21#pragma once
22 22
23#include "sink_export.h"
23#include <QLocalSocket> 24#include <QLocalSocket>
24#include <QObject> 25#include <QObject>
25#include <QTimer> 26#include <QTimer>
@@ -34,7 +35,7 @@ namespace Sink
34 35
35struct QueuedCommand; 36struct QueuedCommand;
36 37
37class ResourceAccessInterface : public QObject 38class SINK_EXPORT ResourceAccessInterface : public QObject
38{ 39{
39 Q_OBJECT 40 Q_OBJECT
40public: 41public:
@@ -62,7 +63,7 @@ public Q_SLOTS:
62 virtual void close() = 0; 63 virtual void close() = 0;
63}; 64};
64 65
65class ResourceAccess : public ResourceAccessInterface 66class SINK_EXPORT ResourceAccess : public ResourceAccessInterface
66{ 67{
67 Q_OBJECT 68 Q_OBJECT
68public: 69public:
@@ -100,7 +101,6 @@ private Q_SLOTS:
100 101
101private: 102private:
102 void connected(); 103 void connected();
103 void log(const QString &message);
104 void registerCallback(uint messageId, const std::function<void(int error, const QString &)> &callback); 104 void registerCallback(uint messageId, const std::function<void(int error, const QString &)> &callback);
105 105
106 void sendCommand(const QSharedPointer<QueuedCommand> &command); 106 void sendCommand(const QSharedPointer<QueuedCommand> &command);
diff --git a/common/resourceconfig.h b/common/resourceconfig.h
index cc9cb94..2108caa 100644
--- a/common/resourceconfig.h
+++ b/common/resourceconfig.h
@@ -19,12 +19,13 @@
19 19
20#pragma once 20#pragma once
21 21
22#include "sink_export.h"
22#include <QList> 23#include <QList>
23#include <QByteArray> 24#include <QByteArray>
24#include <QVariant> 25#include <QVariant>
25#include <QMap> 26#include <QMap>
26 27
27class ResourceConfig 28class SINK_EXPORT ResourceConfig
28{ 29{
29public: 30public:
30 static QMap<QByteArray, QByteArray> getResources(); 31 static QMap<QByteArray, QByteArray> getResources();
diff --git a/common/resourcecontrol.cpp b/common/resourcecontrol.cpp
new file mode 100644
index 0000000..83558bd
--- /dev/null
+++ b/common/resourcecontrol.cpp
@@ -0,0 +1,125 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "resourcecontrol.h"
22
23#include <QTime>
24#include <QUuid>
25#include <functional>
26
27#include "resourceaccess.h"
28#include "commands.h"
29#include "log.h"
30#include "notifier.h"
31
32#undef DEBUG_AREA
33#define DEBUG_AREA "client.resourcecontrol"
34
35namespace Sink
36{
37
38KAsync::Job<void> ResourceControl::shutdown(const QByteArray &identifier)
39{
40 Trace() << "shutdown " << identifier;
41 auto time = QSharedPointer<QTime>::create();
42 time->start();
43 return ResourceAccess::connectToServer(identifier).then<void, QSharedPointer<QLocalSocket>>([identifier, time](QSharedPointer<QLocalSocket> socket, KAsync::Future<void> &future) {
44 //We can't currently reuse the socket
45 socket->close();
46 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(identifier);
47 resourceAccess->open();
48 resourceAccess->sendCommand(Sink::Commands::ShutdownCommand).then<void>([&future, resourceAccess, time]() {
49 Trace() << "Shutdown complete." << Log::TraceTime(time->elapsed());
50 future.setFinished();
51 }).exec();
52 },
53 [](int, const QString &) {
54 Trace() << "Resource is already closed.";
55 //Resource isn't started, nothing to shutdown
56 });
57}
58
59KAsync::Job<void> ResourceControl::start(const QByteArray &identifier)
60{
61 Trace() << "start " << identifier;
62 auto time = QSharedPointer<QTime>::create();
63 time->start();
64 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(identifier);
65 resourceAccess->open();
66 return resourceAccess->sendCommand(Sink::Commands::PingCommand).then<void>([resourceAccess, time]() {
67 Trace() << "Start complete." << Log::TraceTime(time->elapsed());
68 });
69}
70
71KAsync::Job<void> ResourceControl::flushMessageQueue(const QByteArrayList &resourceIdentifier)
72{
73 Trace() << "flushMessageQueue" << resourceIdentifier;
74 return KAsync::iterate(resourceIdentifier)
75 .template each<void, QByteArray>([](const QByteArray &resource, KAsync::Future<void> &future) {
76 Trace() << "Flushing message queue " << resource;
77 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(resource);
78 resourceAccess->open();
79 resourceAccess->synchronizeResource(false, true).then<void>([&future, resourceAccess]() {
80 future.setFinished();
81 }).exec();
82 });
83}
84
85KAsync::Job<void> ResourceControl::flushReplayQueue(const QByteArrayList &resourceIdentifier)
86{
87 return flushMessageQueue(resourceIdentifier);
88}
89
90template <class DomainType>
91KAsync::Job<void> ResourceControl::inspect(const Inspection &inspectionCommand)
92{
93 auto resource = inspectionCommand.resourceIdentifier;
94
95 auto time = QSharedPointer<QTime>::create();
96 time->start();
97 Trace() << "Sending inspection " << resource;
98 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(resource);
99 resourceAccess->open();
100 auto notifier = QSharedPointer<Sink::Notifier>::create(resourceAccess);
101 auto id = QUuid::createUuid().toByteArray();
102 return resourceAccess->sendInspectionCommand(id, ApplicationDomain::getTypeName<DomainType>(), inspectionCommand.entityIdentifier, inspectionCommand.property, inspectionCommand.expectedValue)
103 .template then<void>([resourceAccess, notifier, id, time](KAsync::Future<void> &future) {
104 notifier->registerHandler([&future, id, time](const Notification &notification) {
105 if (notification.id == id) {
106 Trace() << "Inspection complete." << Log::TraceTime(time->elapsed());
107 if (notification.code) {
108 future.setError(-1, "Inspection returned an error: " + notification.message);
109 } else {
110 future.setFinished();
111 }
112 }
113 });
114 });
115}
116
117#define REGISTER_TYPE(T) template KAsync::Job<void> ResourceControl::inspect<T>(const Inspection &); \
118
119REGISTER_TYPE(ApplicationDomain::Event);
120REGISTER_TYPE(ApplicationDomain::Mail);
121REGISTER_TYPE(ApplicationDomain::Folder);
122REGISTER_TYPE(ApplicationDomain::SinkResource);
123
124} // namespace Sink
125
diff --git a/common/resourcecontrol.h b/common/resourcecontrol.h
new file mode 100644
index 0000000..5bfa67f
--- /dev/null
+++ b/common/resourcecontrol.h
@@ -0,0 +1,62 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#pragma once
22
23#include "sink_export.h"
24#include <QByteArray>
25
26#include <Async/Async>
27
28#include "inspection.h"
29
30namespace Sink {
31namespace ResourceControl {
32
33template <class DomainType>
34KAsync::Job<void> SINK_EXPORT inspect(const Inspection &inspectionCommand);
35
36/**
37 * Shutdown resource.
38 */
39KAsync::Job<void> SINK_EXPORT shutdown(const QByteArray &resourceIdentifier);
40
41/**
42 * Start resource.
43 *
44 * The resource is ready for operation once this command completes.
45 * This command is only necessary if a resource was shutdown previously,
46 * otherwise the resource process will automatically start as necessary.
47 */
48KAsync::Job<void> SINK_EXPORT start(const QByteArray &resourceIdentifier);
49
50/**
51 * Flushes any pending messages to disk
52 */
53KAsync::Job<void> SINK_EXPORT flushMessageQueue(const QByteArrayList &resourceIdentifier);
54
55/**
56 * Flushes any pending messages that haven't been replayed to the source.
57 */
58KAsync::Job<void> SINK_EXPORT flushReplayQueue(const QByteArrayList &resourceIdentifier);
59
60 }
61}
62
diff --git a/common/resultprovider.h b/common/resultprovider.h
index 6958dbf..2d6efbe 100644
--- a/common/resultprovider.h
+++ b/common/resultprovider.h
@@ -27,8 +27,6 @@
27#include "resultset.h" 27#include "resultset.h"
28#include "log.h" 28#include "log.h"
29 29
30using namespace async;
31
32namespace Sink { 30namespace Sink {
33 31
34/** 32/**
@@ -47,6 +45,11 @@ public:
47 45
48 } 46 }
49 47
48 virtual ~ResultProviderInterface()
49 {
50
51 }
52
50 virtual void add(const T &value) = 0; 53 virtual void add(const T &value) = 0;
51 virtual void modify(const T &value) = 0; 54 virtual void modify(const T &value) = 0;
52 virtual void remove(const T &value) = 0; 55 virtual void remove(const T &value) = 0;
@@ -183,7 +186,7 @@ public:
183 186
184 void onDone(const std::function<void()> &callback) 187 void onDone(const std::function<void()> &callback)
185 { 188 {
186 mThreadBoundary = QSharedPointer<ThreadBoundary>::create(); 189 mThreadBoundary = QSharedPointer<async::ThreadBoundary>::create();
187 mOnDoneCallback = callback; 190 mOnDoneCallback = callback;
188 } 191 }
189 192
@@ -212,7 +215,7 @@ private:
212 215
213 QWeakPointer<ResultEmitter<T> > mResultEmitter; 216 QWeakPointer<ResultEmitter<T> > mResultEmitter;
214 std::function<void()> mOnDoneCallback; 217 std::function<void()> mOnDoneCallback;
215 QSharedPointer<ThreadBoundary> mThreadBoundary; 218 QSharedPointer<async::ThreadBoundary> mThreadBoundary;
216 std::function<void(const T &parent)> mFetcher; 219 std::function<void(const T &parent)> mFetcher;
217}; 220};
218 221
@@ -327,7 +330,7 @@ private:
327 std::function<void(void)> clearHandler; 330 std::function<void(void)> clearHandler;
328 331
329 std::function<void(const DomainType &parent)> mFetcher; 332 std::function<void(const DomainType &parent)> mFetcher;
330 ThreadBoundary mThreadBoundary; 333 async::ThreadBoundary mThreadBoundary;
331}; 334};
332 335
333template<class DomainType> 336template<class DomainType>
diff --git a/common/storage.h b/common/storage.h
index 2d34f1f..ac03947 100644
--- a/common/storage.h
+++ b/common/storage.h
@@ -21,7 +21,7 @@
21 21
22#pragma once 22#pragma once
23 23
24#include <sinkcommon_export.h> 24#include "sink_export.h"
25#include <string> 25#include <string>
26#include <functional> 26#include <functional>
27#include <QString> 27#include <QString>
@@ -29,7 +29,7 @@
29namespace Sink 29namespace Sink
30{ 30{
31 31
32class SINKCOMMON_EXPORT Storage { 32class SINK_EXPORT Storage {
33public: 33public:
34 enum AccessMode { ReadOnly, ReadWrite }; 34 enum AccessMode { ReadOnly, ReadWrite };
35 35
@@ -180,6 +180,13 @@ public:
180 qint64 diskUsage() const; 180 qint64 diskUsage() const;
181 void removeFromDisk() const; 181 void removeFromDisk() const;
182 182
183 /**
184 * Clears all cached environments.
185 *
186 * This only ever has to be called if a database was removed from another process.
187 */
188 static void clearEnv();
189
183 static qint64 maxRevision(const Sink::Storage::Transaction &); 190 static qint64 maxRevision(const Sink::Storage::Transaction &);
184 static void setMaxRevision(Sink::Storage::Transaction &, qint64 revision); 191 static void setMaxRevision(Sink::Storage::Transaction &, qint64 revision);
185 192
@@ -200,6 +207,8 @@ public:
200 static QByteArray assembleKey(const QByteArray &key, qint64 revision); 207 static QByteArray assembleKey(const QByteArray &key, qint64 revision);
201 static QByteArray uidFromKey(const QByteArray &key); 208 static QByteArray uidFromKey(const QByteArray &key);
202 209
210 static NamedDatabase mainDatabase(const Sink::Storage::Transaction &, const QByteArray &type);
211
203private: 212private:
204 std::function<void(const Storage::Error &error)> mErrorHandler; 213 std::function<void(const Storage::Error &error)> mErrorHandler;
205 214
diff --git a/common/storage_common.cpp b/common/storage_common.cpp
index ea97ac2..0b842d1 100644
--- a/common/storage_common.cpp
+++ b/common/storage_common.cpp
@@ -156,6 +156,11 @@ QByteArray Storage::uidFromKey(const QByteArray &key)
156 return key.mid(0, 38); 156 return key.mid(0, 38);
157} 157}
158 158
159Storage::NamedDatabase Storage::mainDatabase(const Sink::Storage::Transaction &t, const QByteArray &type)
160{
161 return t.openDatabase(type + ".main");
162}
163
159bool Storage::NamedDatabase::contains(const QByteArray &uid) 164bool Storage::NamedDatabase::contains(const QByteArray &uid)
160{ 165{
161 bool found = false; 166 bool found = false;
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index 6539eb0..1efffc4 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -156,8 +156,8 @@ void Storage::NamedDatabase::remove(const QByteArray &k, const QByteArray &value
156 const std::function<void(const Storage::Error &error)> &errorHandler) 156 const std::function<void(const Storage::Error &error)> &errorHandler)
157{ 157{
158 if (!d || !d->transaction) { 158 if (!d || !d->transaction) {
159 Error error(d->name.toLatin1() + d->db, ErrorCodes::GenericError, "Not open");
160 if (d) { 159 if (d) {
160 Error error(d->name.toLatin1() + d->db, ErrorCodes::GenericError, "Not open");
161 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error); 161 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
162 } 162 }
163 return; 163 return;
@@ -443,8 +443,8 @@ Storage::NamedDatabase Storage::Transaction::openDatabase(const QByteArray &db,
443 d->implicitCommit = true; 443 d->implicitCommit = true;
444 auto p = new Storage::NamedDatabase::Private(db, allowDuplicates, d->defaultErrorHandler, d->name, d->transaction); 444 auto p = new Storage::NamedDatabase::Private(db, allowDuplicates, d->defaultErrorHandler, d->name, d->transaction);
445 if (!p->openDatabase(d->requestedRead, errorHandler)) { 445 if (!p->openDatabase(d->requestedRead, errorHandler)) {
446 return Storage::NamedDatabase();
447 delete p; 446 delete p;
447 return Storage::NamedDatabase();
448 } 448 }
449 return Storage::NamedDatabase(p); 449 return Storage::NamedDatabase(p);
450} 450}
@@ -463,7 +463,7 @@ QList<QByteArray> Storage::Transaction::getDatabaseNames() const
463 MDB_val data; 463 MDB_val data;
464 MDB_cursor *cursor; 464 MDB_cursor *cursor;
465 465
466 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor); 466 mdb_cursor_open(d->transaction, d->dbi, &cursor);
467 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST)) == 0) { 467 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST)) == 0) {
468 list << QByteArray::fromRawData((char*)key.mv_data, key.mv_size); 468 list << QByteArray::fromRawData((char*)key.mv_data, key.mv_size);
469 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 469 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
@@ -615,4 +615,12 @@ void Storage::removeFromDisk() const
615 mdb_env_close(env); 615 mdb_env_close(env);
616} 616}
617 617
618void Storage::clearEnv()
619{
620 for (auto env : Storage::Private::sEnvironments) {
621 mdb_env_close(env);
622 }
623 Storage::Private::sEnvironments.clear();
624}
625
618} // namespace Sink 626} // namespace Sink
diff --git a/common/clientapi.cpp b/common/store.cpp
index be9f3fd..6f080b5 100644
--- a/common/clientapi.cpp
+++ b/common/store.cpp
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm> 2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 * 3 *
4 * This library is free software; you can redistribute it and/or 4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public 5 * modify it under the terms of the GNU Lesser General Public
@@ -18,14 +18,10 @@
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */ 19 */
20 20
21#include "clientapi.h" 21#include "store.h"
22 22
23#include <QtConcurrent/QtConcurrentRun> 23#include <QTime>
24#include <QTimer>
25#include <QEventLoop>
26#include <QAbstractItemModel> 24#include <QAbstractItemModel>
27#include <QDir>
28#include <QUuid>
29#include <functional> 25#include <functional>
30#include <memory> 26#include <memory>
31 27
@@ -39,6 +35,9 @@
39#include "storage.h" 35#include "storage.h"
40#include "log.h" 36#include "log.h"
41 37
38#undef DEBUG_AREA
39#define DEBUG_AREA "client.store"
40
42namespace Sink 41namespace Sink
43{ 42{
44 43
@@ -47,11 +46,6 @@ QString Store::storageLocation()
47 return Sink::storageLocation(); 46 return Sink::storageLocation();
48} 47}
49 48
50QByteArray Store::resourceName(const QByteArray &instanceIdentifier)
51{
52 return Sink::resourceName(instanceIdentifier);
53}
54
55static QList<QByteArray> getResources(const QList<QByteArray> &resourceFilter, const QByteArray &type) 49static QList<QByteArray> getResources(const QList<QByteArray> &resourceFilter, const QByteArray &type)
56{ 50{
57 //Return the global resource (signified by an empty name) for types that don't eblong to a specific resource 51 //Return the global resource (signified by an empty name) for types that don't eblong to a specific resource
@@ -156,47 +150,21 @@ KAsync::Job<void> Store::remove(const DomainType &domainObject)
156 }); 150 });
157} 151}
158 152
159KAsync::Job<void> Store::shutdown(const QByteArray &identifier) 153KAsync::Job<void> Store::removeDataFromDisk(const QByteArray &identifier)
160{
161 Trace() << "shutdown " << identifier;
162 return ResourceAccess::connectToServer(identifier).then<void, QSharedPointer<QLocalSocket>>([identifier](QSharedPointer<QLocalSocket> socket, KAsync::Future<void> &future) {
163 //We can't currently reuse the socket
164 socket->close();
165 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(identifier);
166 resourceAccess->open();
167 resourceAccess->sendCommand(Sink::Commands::ShutdownCommand).then<void>([&future, resourceAccess]() {
168 Trace() << "Shutdown complete";
169 future.setFinished();
170 }).exec();
171 },
172 [](int, const QString &) {
173 Trace() << "Resource is already closed.";
174 //Resource isn't started, nothing to shutdown
175 })
176 //FIXME JOBAPI this is only required because we don't care about the return value of connectToServer
177 .template then<void>([](){});
178}
179
180KAsync::Job<void> Store::start(const QByteArray &identifier)
181{ 154{
182 Trace() << "start " << identifier; 155 //All databases are going to become invalid, nuke the environments
156 //TODO: all clients should react to a notification the resource
157 Sink::Storage::clearEnv();
158 Trace() << "Remove data from disk " << identifier;
159 auto time = QSharedPointer<QTime>::create();
160 time->start();
183 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(identifier); 161 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(identifier);
184 resourceAccess->open(); 162 resourceAccess->open();
185 return resourceAccess->sendCommand(Sink::Commands::PingCommand).then<void>([resourceAccess]() { 163 return resourceAccess->sendCommand(Sink::Commands::RemoveFromDiskCommand).then<void>([resourceAccess, time]() {
186 Trace() << "Start complete"; 164 Trace() << "Remove from disk complete." << Log::TraceTime(time->elapsed());
187 }); 165 });
188} 166}
189 167
190void Store::removeFromDisk(const QByteArray &identifier)
191{
192 //TODO By calling the resource executable with a --remove option instead
193 //we can ensure that no other resource process is running at the same time
194 QDir dir(Sink::storageLocation());
195 for (const auto &folder : dir.entryList(QStringList() << identifier + "*")) {
196 Sink::Storage(Sink::storageLocation(), folder, Sink::Storage::ReadWrite).removeFromDisk();
197 }
198}
199
200KAsync::Job<void> Store::synchronize(const Sink::Query &query) 168KAsync::Job<void> Store::synchronize(const Sink::Query &query)
201{ 169{
202 Trace() << "synchronize" << query.resources; 170 Trace() << "synchronize" << query.resources;
@@ -208,30 +176,7 @@ KAsync::Job<void> Store::synchronize(const Sink::Query &query)
208 resourceAccess->synchronizeResource(true, false).then<void>([&future, resourceAccess]() { 176 resourceAccess->synchronizeResource(true, false).then<void>([&future, resourceAccess]() {
209 future.setFinished(); 177 future.setFinished();
210 }).exec(); 178 }).exec();
211 }) 179 });
212 //FIXME JOBAPI this is only required because we don't care about the return value of each (and each shouldn't even have a return value)
213 .template then<void>([](){});
214}
215
216KAsync::Job<void> Store::flushMessageQueue(const QByteArrayList &resourceIdentifier)
217{
218 Trace() << "flushMessageQueue" << resourceIdentifier;
219 return KAsync::iterate(resourceIdentifier)
220 .template each<void, QByteArray>([](const QByteArray &resource, KAsync::Future<void> &future) {
221 Trace() << "Flushing message queue " << resource;
222 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(resource);
223 resourceAccess->open();
224 resourceAccess->synchronizeResource(false, true).then<void>([&future, resourceAccess]() {
225 future.setFinished();
226 }).exec();
227 })
228 //FIXME JOBAPI this is only required because we don't care about the return value of each (and each shouldn't even have a return value)
229 .template then<void>([](){});
230}
231
232KAsync::Job<void> Store::flushReplayQueue(const QByteArrayList &resourceIdentifier)
233{
234 return flushMessageQueue(resourceIdentifier);
235} 180}
236 181
237template <class DomainType> 182template <class DomainType>
@@ -295,63 +240,10 @@ KAsync::Job<QList<typename DomainType::Ptr> > Store::fetch(const Sink::Query &qu
295 }); 240 });
296} 241}
297 242
298template <class DomainType>
299KAsync::Job<void> Resources::inspect(const Inspection &inspectionCommand)
300{
301 auto resource = inspectionCommand.resourceIdentifier;
302
303 Trace() << "Sending inspection " << resource;
304 auto resourceAccess = QSharedPointer<Sink::ResourceAccess>::create(resource);
305 resourceAccess->open();
306 auto notifier = QSharedPointer<Sink::Notifier>::create(resourceAccess);
307 auto id = QUuid::createUuid().toByteArray();
308 return resourceAccess->sendInspectionCommand(id, ApplicationDomain::getTypeName<DomainType>(), inspectionCommand.entityIdentifier, inspectionCommand.property, inspectionCommand.expectedValue)
309 .template then<void>([resourceAccess, notifier, id](KAsync::Future<void> &future) {
310 notifier->registerHandler([&future, id](const Notification &notification) {
311 if (notification.id == id) {
312 if (notification.code) {
313 future.setError(-1, "Inspection returned an error: " + notification.message);
314 } else {
315 future.setFinished();
316 }
317 }
318 });
319 });
320}
321
322class Sink::Notifier::Private {
323public:
324 Private()
325 : context(new QObject)
326 {
327
328 }
329 QList<QSharedPointer<ResourceAccess> > resourceAccess;
330 QList<std::function<void(const Notification &)> > handler;
331 QSharedPointer<QObject> context;
332};
333
334Notifier::Notifier(const QSharedPointer<ResourceAccess> &resourceAccess)
335 : d(new Sink::Notifier::Private)
336{
337 QObject::connect(resourceAccess.data(), &ResourceAccess::notification, d->context.data(), [this](const Notification &notification) {
338 for (const auto &handler : d->handler) {
339 handler(notification);
340 }
341 });
342 d->resourceAccess << resourceAccess;
343}
344
345void Notifier::registerHandler(std::function<void(const Notification &)> handler)
346{
347 d->handler << handler;
348}
349
350#define REGISTER_TYPE(T) template KAsync::Job<void> Store::remove<T>(const T &domainObject); \ 243#define REGISTER_TYPE(T) template KAsync::Job<void> Store::remove<T>(const T &domainObject); \
351 template KAsync::Job<void> Store::create<T>(const T &domainObject); \ 244 template KAsync::Job<void> Store::create<T>(const T &domainObject); \
352 template KAsync::Job<void> Store::modify<T>(const T &domainObject); \ 245 template KAsync::Job<void> Store::modify<T>(const T &domainObject); \
353 template QSharedPointer<QAbstractItemModel> Store::loadModel<T>(Query query); \ 246 template QSharedPointer<QAbstractItemModel> Store::loadModel<T>(Query query); \
354 template KAsync::Job<void> Resources::inspect<T>(const Inspection &); \
355 template KAsync::Job<T> Store::fetchOne<T>(const Query &); \ 247 template KAsync::Job<T> Store::fetchOne<T>(const Query &); \
356 template KAsync::Job<QList<T::Ptr> > Store::fetchAll<T>(const Query &); \ 248 template KAsync::Job<QList<T::Ptr> > Store::fetchAll<T>(const Query &); \
357 template KAsync::Job<QList<T::Ptr> > Store::fetch<T>(const Query &, int); \ 249 template KAsync::Job<QList<T::Ptr> > Store::fetch<T>(const Query &, int); \
diff --git a/common/store.h b/common/store.h
new file mode 100644
index 0000000..6696833
--- /dev/null
+++ b/common/store.h
@@ -0,0 +1,101 @@
1/*
2 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#pragma once
22
23#include "sink_export.h"
24#include <QString>
25#include <QSharedPointer>
26
27#include <Async/Async>
28
29#include "query.h"
30#include "applicationdomaintype.h"
31
32class QAbstractItemModel;
33
34namespace Sink {
35
36/**
37 * The unified Sink Store.
38 *
39 * This is the primary interface for clients to interact with Sink.
40 * It provides a unified store where all data provided by various resources can be accessed and modified.
41 */
42namespace Store {
43
44QString SINK_EXPORT storageLocation();
45
46enum Roles {
47 DomainObjectRole = Qt::UserRole + 1, //Must be the same as in ModelResult
48 ChildrenFetchedRole,
49 DomainObjectBaseRole
50};
51
52/**
53 * Asynchronusly load a dataset with tree structure information
54 */
55template <class DomainType>
56QSharedPointer<QAbstractItemModel> SINK_EXPORT loadModel(Query query);
57
58/**
59 * Create a new entity.
60 */
61template <class DomainType>
62KAsync::Job<void> SINK_EXPORT create(const DomainType &domainObject);
63
64/**
65 * Modify an entity.
66 *
67 * This includes moving etc. since these are also simple settings on a property.
68 */
69template <class DomainType>
70KAsync::Job<void> SINK_EXPORT modify(const DomainType &domainObject);
71
72/**
73 * Remove an entity.
74 */
75template <class DomainType>
76KAsync::Job<void> SINK_EXPORT remove(const DomainType &domainObject);
77
78/**
79 * Synchronize data to local cache.
80 */
81KAsync::Job<void> SINK_EXPORT synchronize(const Sink::Query &query);
82
83/**
84 * Removes all resource data from disk.
85 *
86 * This will not touch the configuration. All commands that that arrived at the resource before this command will be dropped. All commands that arrived later will be executed.
87 */
88KAsync::Job<void> SINK_EXPORT removeDataFromDisk(const QByteArray &resourceIdentifier);
89
90template <class DomainType>
91KAsync::Job<DomainType> SINK_EXPORT fetchOne(const Sink::Query &query);
92
93template <class DomainType>
94KAsync::Job<QList<typename DomainType::Ptr> > SINK_EXPORT fetchAll(const Sink::Query &query);
95
96template <class DomainType>
97KAsync::Job<QList<typename DomainType::Ptr> > SINK_EXPORT fetch(const Sink::Query &query, int minimumAmount = 0);
98
99 }
100}
101
diff --git a/common/threadboundary.h b/common/threadboundary.h
index 0d8ed3b..7bea4ea 100644
--- a/common/threadboundary.h
+++ b/common/threadboundary.h
@@ -20,6 +20,8 @@
20 20
21#pragma once 21#pragma once
22 22
23#include "sink_export.h"
24
23#include <QObject> 25#include <QObject>
24#include <functional> 26#include <functional>
25 27
@@ -29,7 +31,7 @@ namespace async {
29* A helper class to invoke a method in a different thread using the event loop. 31* A helper class to invoke a method in a different thread using the event loop.
30* The ThreadBoundary object must live in the thread where the function should be called. 32* The ThreadBoundary object must live in the thread where the function should be called.
31*/ 33*/
32class ThreadBoundary : public QObject { 34class SINK_EXPORT ThreadBoundary : public QObject {
33 Q_OBJECT 35 Q_OBJECT
34public: 36public:
35 ThreadBoundary(); 37 ThreadBoundary();
diff --git a/common/typeindex.cpp b/common/typeindex.cpp
index 03ad8f7..b1bcf6a 100644
--- a/common/typeindex.cpp
+++ b/common/typeindex.cpp
@@ -22,6 +22,9 @@
22#include "index.h" 22#include "index.h"
23#include <QDateTime> 23#include <QDateTime>
24 24
25#undef DEBUG_AREA
26#define DEBUG_AREA "common.typeindex"
27
25TypeIndex::TypeIndex(const QByteArray &type) 28TypeIndex::TypeIndex(const QByteArray &type)
26 : mType(type) 29 : mType(type)
27{ 30{
@@ -111,9 +114,9 @@ ResultSet TypeIndex::query(const Sink::Query &query, QSet<QByteArray> &appliedFi
111 Warning() << "Error in index: " << error.message << property; 114 Warning() << "Error in index: " << error.message << property;
112 }); 115 });
113 appliedFilters << property; 116 appliedFilters << property;
117 Trace() << "Index lookup on " << property << " found " << keys.size() << " keys.";
118 return ResultSet(keys);
114 } 119 }
115 Trace() << "Index lookup on " << property << " found " << keys.size() << " keys.";
116 return ResultSet(keys);
117 } 120 }
118 Trace() << "No matching index"; 121 Trace() << "No matching index";
119 return ResultSet(keys); 122 return ResultSet(keys);
diff --git a/docs/building.md b/docs/building.md
index 907827d..17ef54b 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -85,3 +85,15 @@ mkdir build && cd build
85cmake .. 85cmake ..
86make install 86make install
87``` 87```
88
89# Dependencies
90
91* ExtraCmakeModules >= 0.0.10
92* Qt >= 5.2
93* KF5::Async >= 0.1
94* flatbuffers >= 1.0
95* libgit2
96* readline
97
98## Maildir Resource
99* KF5::Mime
diff --git a/docs/clientapi.md b/docs/clientapi.md
index 219f972..be8ff19 100644
--- a/docs/clientapi.md
+++ b/docs/clientapi.md
@@ -13,16 +13,6 @@ The client API consists of:
13* property-level on-demand loading of data 13* property-level on-demand loading of data
14* streaming support for large properties (attachments) 14* streaming support for large properties (attachments)
15 15
16## Domain Types
17A set of standardized domain types is defined. This is necessary to decouple applications from resources (so a calendar can access events from all resources), and to have a "language" for queries.
18
19The definition of the domain model directly affects:
20
21* granularity for data retrieval (email property, or individual subject, date, ...)
22* queriable properties for filtering and sorting (sender, id, ...)
23
24The purpose of these domain types is strictly to be the interface and the types are not necessarily meant to be used by applications directly, or to be restricted by any other specifications (such as ical). By nature these types will be part of the evolving interface, and will need to be adjusted for every new property that an application must understand.
25
26## Store Facade 16## Store Facade
27The store is always accessed through a store specific facade, which hides: 17The store is always accessed through a store specific facade, which hides:
28 18
@@ -52,118 +42,12 @@ Each modification is associated with a specific revision, which allows the synch
52### Conflict Resolution 42### Conflict Resolution
53Conflicts can occur at two points: 43Conflicts can occur at two points:
54 44
55* While i.e. an editor is open and we receive an update for the same entity 45* In the client: While i.e. an editor is open and we receive an update for the same entity
56* After a modification is sent to the synchronizer but before it's processed 46* In the synchronizer: After a modification is sent to the synchronizer but before it's processed
57 47
58In the first case the client is repsonsible to resolve the conflict, in the latter case it's the synchronizer's responsibility. 48In the first case the client is repsonsible to resolve the conflict, in the latter case it's the synchronizer's responsibility.
59A small window exists where the client has already started the modification (i.e. command is in socket), and a notification has not yet arrived that the same entity has been changed. In such a case the synchronizer may reject the modification because it has the revision the modification refers to no longer available. 49A small window exists where the client has already started the modification (i.e. command is in socket), and a notification has not yet arrived that the same entity has been changed. In such a case the synchronizer may reject the modification because it has the revision the modification refers to no longer available.
60 50
61This design allows the synchronizer to be in control of the revisions, and keeps it from having to wait for all clients to update until it can drop revisions.
62
63## Query System
64The query system should allow for efficient retrieval for just the amount of data required by the client. Efficient querying is supported by the indexes provided by the resources.
65
66The query always retrieves a set of entities matching the query, while not necessarily all properties of the entity need to be populated.
67
68Queries should are declarative to keep the specification simple and to allow the implementation to choose the most efficient execution.
69
70Queries can be kept open (live) to receive updates as the store changes.
71
72### Query
73The query consists of:
74
75* a set of filters to match the wanted entities
76* the set of properties to retrieve for each entity
77
78Queryable properties are defined by the [[Domain Types]] above.
79
80### Query Result
81The result is returned directly after running the query in form of a QAbstractItemModel. Each row in the model represents a matching entity.
82
83The model allows to access the domain object directly, or to access individual properties directly via the rows columns.
84
85The model is always populated asynchronously. It is therefore initially empty and will then populate itself gradually, through the regular update mechanisms (rowsInserted).
86
87Tree Queries allow the application to query for i.e. a folder hierarchy in a single query. This is necessary for performance reasons to avoid recursive querying in large hierarchies. To avoid on the other hand loading large hierchies directly into memory, the model only populates the toplevel rows automatically, all other rows need to be populated by calling `QAbstractItemModel::fetchMore(QModelIndex);`. This way the resource can deal efficiently with the query (i.e. by avoiding the many roundtrips that would be necessary with recursive queries), while keeping the amount of data in memory to a minimum (i.e. if the majority of the folder tree is collapsed in the view anyways). A tree result set can therefore be seen as a set of sets, where every subset corresponds to the children of one parent.
88
89If the query is live, the model updates itself if the update applies to one of the already loaded subsets (otherwise it's currently irrelevant and will load once the subset is loaded).
90
91#### Enhancements
92* Asynchronous loading of entities/properties can be achieved by returning an invalid QVariant initially, and emitting dataChanged once the value is loaded.
93* To avoid loading a large list when not all data is necessary, a batch size could be defined to guarantee for instance that there is sufficient data to fill the screen, and the fetchMore mechanism can be used to gradually load more data as required when scrolling in the application.
94
95#### Filter
96A filter consists of:
97
98* a property to filter on as defined by the [[Domain Types]]
99* a comparator to use
100* a value
101
102The available comparators are:
103
104* equal
105* greater than
106* less than
107* inclusive range
108
109Value types include:
110
111* Null
112* Bool
113* Regular Expression
114* Substring
115* A type-specific literal value (e.g. string, number, date, ..)
116
117Filters can be combined using AND, OR, NOT.
118
119#### Example
120```
121query = {
122 offset: int
123 limit: int
124 filter = {
125 and {
126 collection = foo
127 or {
128 resource = res1
129 resource = res2
130 }
131 }
132 }
133}
134```
135
136possible API:
137
138```
139query.filter().and().property("collection") = "foo"
140query.filter().and().or().property("resource") = "res1"
141query.filter().and().or().property("resource") = "res2"
142query.filter().and().property("start-date") = InclusiveRange(QDateTime, QDateTime)
143```
144
145The problem is that it is difficult to adjust an individual resource property like that.
146
147### Usecases ###
148Mail:
149
150* All mails in folder X within date-range Y that are unread.
151* All mails (in all folders) that contain the string X in property Y.
152
153Todos:
154
155* Give me all the todos in that collection where their RELATED-TO field maps to no other todo UID field in the collection
156* Give me all the todos in that collection where their RELATED-TO field has a given value
157* Give me all the collections which have a given collection as parent and which have a descendant matching a criteria on its attributes;
158
159Events:
160
161* All events of calendar X within date-range Y.
162
163Generic:
164* entity with identifier X
165* all entities of resource X
166
167### Lazy Loading ### 51### Lazy Loading ###
168The system provides property-level lazy loading. This allows i.e. to defer downloading of attachments until the attachments is accessed, at the expense of having to have access to the source (which could be connected via internet). 52The system provides property-level lazy loading. This allows i.e. to defer downloading of attachments until the attachments is accessed, at the expense of having to have access to the source (which could be connected via internet).
169 53
@@ -173,12 +57,3 @@ Note: We should perhaps define a minimum set of properties that *must* be availa
173 57
174### Data streaming ### 58### Data streaming ###
175Large properties such as attachments should be streamable. An API that allows to retrieve a single property of a defined entity in a streamable fashion is probably enough. 59Large properties such as attachments should be streamable. An API that allows to retrieve a single property of a defined entity in a streamable fashion is probably enough.
176
177### Indexes ###
178Since only properties of the domain types can be queried, default implementations for commonly used indexes can be provided. These indexes are populated by generic preprocessors that use the domain-type interface to extract properties from individual entites.
179
180## Notifications ##
181A notification mechanism is required to inform clients about changes. Running queries will automatically update the result-set if a notification is received.
182
183Note: A notification could supply a hint on what changed, allowing clients to ignore revisions with irrelevant changes.
184A running query can do all of that transparently behind the scenes. Note that the hints should indeed only hint what has changed, and not supply the actual changeset. These hints should be tailored to what we see as useful, and must therefore be easy to modify.
diff --git a/docs/design.md b/docs/design.md
index 4451b49..2890450 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -1,101 +1,45 @@
1# Design Goals
2## Axioms
31. Personal information is stored in multiple sources (address books, email stores, calendar files, ...)
42. These sources may local, remote or a mix of local and remote
5
6## Requirements
71. Local mirrors of these sources must be available to 1..N local clients simultaneously
82. Local clients must be able to make (or at least request) changes to the data in the local mirrors
93. Local mirrors must be usable without network, even if the source is remote
104. Local mirrors must be able to syncronoize local changes to their sources (local or remote)
115. Local mirrors must be able to syncronize remote changes and propagate those to local clients
126. Content must be searchable by a number of terms (dates, identities, body text ...)
137. This must all run with acceptable performance on a moderate consumer-grade desktop system
14
15Nice to haves:
16
171. As-close-to-zero-copy-as-possible for data
182. Simple change notification semantics
193. Resource-specific syncronization techniques
204. Data agnostic storage
21
22Immediate goals:
23
241. Ease development of new features in existing resources
252. Ease maintenance of existing resources
263. Make adding new resources easy
274. Make adding new types of data or data relations easy
285. Improve performance relative to existing Akonadi implementation
29
30Long-term goals:
31
321. Project view: given a query, show all items in all stores that match that query easily and quickly
33
34Implications of the above:
35
36* Local mirrors must support multi-reader, but are probably best served with single-writer semantics as this simplifies both local change recording as well as remote synchronization by keeping it in one process which can process write requests (local or remote) in sequential fashion.
37* There is no requirement for a central server if the readers can concurrently access the local mirror directly
38* A storage system which requires a schema (e.g. relational databases) are a poor fit given the desire for data agnosticism and low memory copying
39
40# Overview 1# Overview
41 2
42## Client API 3Sink is a data access layer that additionally handles synchronization with external sources and indexing of data for efficient queries.
43The client facing API hides all Sink internals from the applications and emulates a unified store that provides data through a standardized interface. 4
5## Store
6The client facing Store API hides all Sink internals from the applications and emulates a unified store that provides data through a standardized interface.
44This allows applications to transparently use various data sources with various data source formats. 7This allows applications to transparently use various data sources with various data source formats.
45 8
46## Resource 9## Resource
47A resource is a plugin that provides access to an additional source. It consists of a store, a synchronizer process that executes synchronization & change replay to the source and maintains the store, as well as a facade plugin for the client api. 10A resource is a plugin that provides access to an additional source. It consists of a store, a synchronizer process that executes synchronization & change replay to the source and maintains the store, as well as a facade plugin for the client api.
48 11
49## Store 12## Storage / Indexes
50Each resource maintains a store that can either store the full dataset for offline access or only metadata for quick lookups. Resources can define how data is stored. 13Each resource maintains a store that can either store the full dataset for offline access or only metadata for quick lookups. Resources can define how data is stored.
14The store consists of revisions with every revision containing one entity.
15
16The store additionally contains various secondary indexes for efficient lookups.
51 17
52## Types 18## Types
53### Domain Type 19### Domain Type
54The domain types exposed in the public interface. 20The domain types exposed in the public interface provide standardized access to the store. The domain types and their properties directly define the granularity of data retrieval and thus also what queries can be executed.
55 21
56### Buffer Type 22### Buffer Type
57The individual buffer types as specified by the resource. The are internal types that don't necessarily have a 1:1 mapping to the domain types, although that is the default case that the default implementations expect. 23The buffers used by the resources in the store may be different from resource to resource, and don't necessarily have a 1:1 mapping to the domain types.
24This allows resources to store data in a way that is convenient/efficient for synchronization, altough it may require a bit more effort when accessing the data.
25The individual buffer types are specified by the resource and internal to it. Default buffer types exist of all domain types.
26
27### Commands
28Commands are used to modify the store. The resource processes commands that are generated by clients and the synchronizer.
29
30### Notifications
31The resource emits notifications to inform clients of new revisions and other changes.
58 32
59## Mechanisms 33## Mechanisms
60### Change Replay 34### Change Replay
61The change replay is based on the revisions in the store. Clients (as well as also the write-back mechanism that replays changes to the source), are informed that a new revision is available. Each client can then go through all new revisions (starting from the last seen revision), and thus update its state to the latest revision. 35The change replay is based on the revisions in the store. Clients (as well as also the write-back mechanism that replays changes to the source), are informed that a new revision is available. Each client can then go through all new revisions (starting from the last seen revision), and thus update its state to the latest revision.
62 36
63### Preprocessor pipeline 37### Synchronization
64Each resource has an internal pipeline of preprocessors that can be used for tasks such as indexing or filtering. The pipeline guarantees that the preprocessor steps are executed before the entity is persisted. 38The synchronizer executes a periodic synchronization that results in change commands to synchronize the store with the source.
65 39The change-replay mechanism is used to write back changes to the source that happened locally.
66# Tradeoffs/Design Decisions
67* Key-Value store instead of relational
68 * `+` Schemaless, easier to evolve
69 * `-` No need to fully normalize the data in order to make it queriable. And without full normalization SQL is not really useful and bad performance wise.
70 * `-` We need to maintain our own indexes
71
72* Individual store per resource
73 * Storage format defined by resource individually
74 * `-` Each resource needs to define it's own schema
75 * `+` Resources can adjust storage format to map well on what it has to synchronize
76 * `+` Synchronization state can directly be embedded into messages
77 * `+` Individual resources could switch to another store technology
78 * `+` Easier maintenance
79 * `+` Resource is only responsible for it's own store and doesn't accidentaly break another resources store
80 * `-` Inter`-`resource moves are both more complicated and more expensive from a client perspective
81 * `+` Inter`-`resource moves become simple additions and removals from a resource perspective
82 * `-` No system`-`wide unique id per message (only resource/id tuple identifies a message uniquely)
83 * `+` Stores can work fully concurrently (also for writing)
84 40
85* Indexes defined and maintained by resources 41### Command processing
86 * `-` Relational queries accross resources are expensive (depending on the query perhaps not even feasible) 42The resources have an internal persitant command queue hat is populated by the synchronizer and clients continuously processed.
87 * `-` Each resource needs to define it's own set of indexes
88 * `+` Flexible design as it allows to change indexes on a per resource level
89 * `+` Indexes can be optimized towards resources main usecases
90 * `+` Indexes can be shared with the source (IMAP serverside threading)
91 43
92* Shared domain types as common interface for client applications 44Each resource has an internal pipeline of preprocessors that can be used for tasks such as indexing or filtering, and through which every command goes before it enters the store. The pipeline guarantees that the preprocessor steps are executed on any command before the entity is persisted.
93 * `-` yet another abstraction layer that requires translation to other layers and maintenance
94 * `+` decoupling of domain logic from data access
95 * `+` allows to evolve types according to needs (not coupled to specific application domain types)
96 45
97# Risks
98* key-value store does not perform with large amounts of data
99* query performance is not sufficient
100* turnaround time for modifications is too high to feel responsive
101* design turns out similarly complex as Akonadi
diff --git a/docs/designgoals.md b/docs/designgoals.md
new file mode 100644
index 0000000..4ffeeac
--- /dev/null
+++ b/docs/designgoals.md
@@ -0,0 +1,39 @@
1# Design Goals
2## Axioms
31. Personal information is stored in multiple sources (address books, email stores, calendar files, ...)
42. These sources may local, remote or a mix of local and remote
5
6## Requirements
71. Local mirrors of these sources must be available to 1..N local clients simultaneously
82. Local clients must be able to make (or at least request) changes to the data in the local mirrors
93. Local mirrors must be usable without network, even if the source is remote
104. Local mirrors must be able to syncronoize local changes to their sources (local or remote)
115. Local mirrors must be able to syncronize remote changes and propagate those to local clients
126. Content must be searchable by a number of terms (dates, identities, body text ...)
137. This must all run with acceptable performance on a moderate consumer-grade desktop system
14
15Nice to haves:
16
171. As-close-to-zero-copy-as-possible for data
182. Simple change notification semantics
193. Resource-specific syncronization techniques
204. Data agnostic storage
21
22Immediate goals:
23
241. Ease development of new features in existing resources
252. Ease maintenance of existing resources
263. Make adding new resources easy
274. Make adding new types of data or data relations easy
285. Improve performance relative to existing Akonadi implementation
29
30Long-term goals:
31
321. Project view: given a query, show all items in all stores that match that query easily and quickly
33
34Implications of the above:
35
36* Local mirrors must support multi-reader, but are probably best served with single-writer semantics as this simplifies both local change recording as well as remote synchronization by keeping it in one process which can process write requests (local or remote) in sequential fashion.
37* There is no requirement for a central server if the readers can concurrently access the local mirror directly
38* A storage system which requires a schema (e.g. relational databases) are a poor fit given the desire for data agnosticism and low memory copying
39
diff --git a/docs/index.md b/docs/index.md
index 3019cfd..90d04b6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,17 +1,18 @@
1# Index 1Sink is a data access layer handling synchronization, caching and indexing.
2* Design 2
3 * Design Goals 3Discussion of the code should be done on the kde-pim at kde.org mailing list
4 * Overview 4or in #kontact on IRC.
5 * Client API 5
6 * Storage 6Note that all feature development should happen in feature branches, and that
7 * Resource 7the mainline development branch is "develop". Master is for releases. It is
8 * Facade 8recommended (though not required) to use the ["git flow" tools](https://github.com/nvie/gitflow) to make branched
9 * Logging 9development easy (and easy for others to coordinate with).
10* Extending Akoandi Next 10
11 * Steps to add support for new types 11For further information on the project see the [KDE Phabricator instance](https://phabricator.kde.org/project/view/5/).
12 * Steps for adding support for a type to a resource
13 12
14# Documentation 13# Documentation
15This documentation is built using [mkdocs.org](http://mkdocs.org). 14This documentation is built using [mkdocs.org](http://mkdocs.org).
16 15
17Use `mkdocs serve` to run a local webserver to view the docs. 16Use `mkdocs serve` to run a local webserver to view the docs.
17
18The documentation is also published at [http://api.kde.org/doc/sink/](http://api.kde.org/doc/sink/) and rebuilt nightly.
diff --git a/docs/logging.md b/docs/logging.md
index a495a7a..3d5ea61 100644
--- a/docs/logging.md
+++ b/docs/logging.md
@@ -10,13 +10,34 @@ For debugging purposes a logging framework is required. Simple qDebugs() proved
10 * logfiles 10 * logfiles
11 * a commandline monitor tool 11 * a commandline monitor tool
12 * some other developer tool 12 * some other developer tool
13This way we get complete logs also if some resource was not started from the console (i.e. because it was already running).
13 14
14## Debug levels 15## Debug levels
15* trace: trace individual codepaths. Likely outputs way to much information for all normal cases and likely is only ever temporarily enabled. Trace points are likely only inserted into code fragments that are known to be problematic. 16* trace: trace individual codepaths. Likely outputs way to much information for all normal cases and likely is only ever temporarily enabled for certain areas.
16* log: Comprehensive debug output. Enabled on demand 17* log: Comprehensive debug output. Enabled on demand
17* warning: Only warnings, should always be logged. 18* warning: Only warnings, should always be logged.
18* error: Critical messages that should never appear. Should always be logged. 19* error: Critical messages that should never appear. Should always be logged.
19 20
21## Debug areas
22Debug areas split the code into sections that can be enabled/disabled as one.
23This is supposed to give finer grained control over what is logged or displayed.
24
25Debug areas may align with classes, but don't have to, the should be made so that they are useful.
26
27Areas could be:
28
29* resource.sync.performance
30* resource.sync
31* resource.listener
32* resource.pipeline
33* resource.store
34* resource.communication
35* client.communication
36* client.communication.org.sink.resource.maildir.identifier1
37* client.queryrunner
38* client.queryrunner.performance
39* common.typeindex
40
20## Collected information 41## Collected information
21Additionally to the regular message we want: 42Additionally to the regular message we want:
22 43
@@ -24,5 +45,5 @@ Additionally to the regular message we want:
24* threadid? 45* threadid?
25* timestamp 46* timestamp
26* sourcefile + position + function name 47* sourcefile + position + function name
27* application name / resource identfier 48* application name / resource identifier
28* component identifier (i.e. resource access) 49* area (i.e. resource access)
diff --git a/docs/queries.md b/docs/queries.md
new file mode 100644
index 0000000..8676392
--- /dev/null
+++ b/docs/queries.md
@@ -0,0 +1,104 @@
1## Query System
2The query system should allow for efficient retrieval for just the amount of data required by the client. Efficient querying is supported by the indexes provided by the resources.
3
4The query always retrieves a set of entities matching the query, while not necessarily all properties of the entity need to be populated.
5
6Queries are declarative to keep the specification simple and to allow the implementation to choose the most efficient execution.
7
8Queries can be kept open (live) to receive updates as the store changes.
9
10### Query
11The query consists of:
12
13* a set of filters to match the wanted entities
14* the set of properties to retrieve for each entity
15
16Queryable properties are defined by the [[Domain Types]] above.
17
18### Query Result
19The result is returned directly after running the query in form of a QAbstractItemModel. Each row in the model represents a matching entity.
20
21The model allows to access the domain object directly, or to access individual properties directly via the rows columns.
22
23The model is always populated asynchronously. It is therefore initially empty and will then populate itself gradually, through the regular update mechanisms (rowsInserted).
24
25Tree Queries allow the application to query for i.e. a folder hierarchy in a single query. This is necessary for performance reasons to avoid recursive querying in large hierarchies. To avoid on the other hand loading large hierchies directly into memory, the model only populates the toplevel rows automatically, all other rows need to be populated by calling `QAbstractItemModel::fetchMore(QModelIndex);`. This way the resource can deal efficiently with the query (i.e. by avoiding the many roundtrips that would be necessary with recursive queries), while keeping the amount of data in memory to a minimum (i.e. if the majority of the folder tree is collapsed in the view anyways). A tree result set can therefore be seen as a set of sets, where every subset corresponds to the children of one parent.
26
27If the query is live, the model updates itself if the update applies to one of the already loaded subsets (otherwise it's currently irrelevant and will load once the subset is loaded).
28
29#### Enhancements
30* Asynchronous loading of entities/properties can be achieved by returning an invalid QVariant initially, and emitting dataChanged once the value is loaded.
31* To avoid loading a large list when not all data is necessary, a batch size could be defined to guarantee for instance that there is sufficient data to fill the screen, and the fetchMore mechanism can be used to gradually load more data as required when scrolling in the application.
32
33#### Filter
34A filter consists of:
35
36* a property to filter on as defined by the [[Domain Types]]
37* a comparator to use
38* a value
39
40The available comparators are:
41
42* equal
43* greater than
44* less than
45* inclusive range
46
47Value types include:
48
49* Null
50* Bool
51* Regular Expression
52* Substring
53* A type-specific literal value (e.g. string, number, date, ..)
54
55Filters can be combined using AND, OR, NOT.
56
57#### Example
58```
59query = {
60 offset: int
61 limit: int
62 filter = {
63 and {
64 collection = foo
65 or {
66 resource = res1
67 resource = res2
68 }
69 }
70 }
71}
72```
73
74possible API:
75
76```
77query.filter().and().property("collection") = "foo"
78query.filter().and().or().property("resource") = "res1"
79query.filter().and().or().property("resource") = "res2"
80query.filter().and().property("start-date") = InclusiveRange(QDateTime, QDateTime)
81```
82
83The problem is that it is difficult to adjust an individual resource property like that.
84
85### Usecases ###
86Mail:
87
88* All mails in folder X within date-range Y that are unread.
89* All mails (in all folders) that contain the string X in property Y.
90
91Todos:
92
93* Give me all the todos in that collection where their RELATED-TO field maps to no other todo UID field in the collection
94* Give me all the todos in that collection where their RELATED-TO field has a given value
95* Give me all the collections which have a given collection as parent and which have a descendant matching a criteria on its attributes;
96
97Events:
98
99* All events of calendar X within date-range Y.
100
101Generic:
102* entity with identifier X
103* all entities of resource X
104
diff --git a/docs/resource.md b/docs/resource.md
index defbf9a..8c87522 100644
--- a/docs/resource.md
+++ b/docs/resource.md
@@ -4,7 +4,7 @@ The resource consists of:
4* a plugin providing the client-api facade 4* a plugin providing the client-api facade
5* a configuration setting of the filters 5* a configuration setting of the filters
6 6
7# Synchronizer 7## Synchronizer
8The synchronizer process is responsible for processing all commands, executing synchronizations with the source, and replaying changes to the source. 8The synchronizer process is responsible for processing all commands, executing synchronizations with the source, and replaying changes to the source.
9 9
10Processing of commands happens in the pipeline which executes all preprocessors ebfore the entity is persisted. 10Processing of commands happens in the pipeline which executes all preprocessors ebfore the entity is persisted.
@@ -16,7 +16,15 @@ The synchronizer process has the following primary components:
16* Listener: Opens a socket and listens for incoming connections. On connection all incoming commands are read and entered into command queues. Control commands (i.e. a sync) don't require persistency and are therefore processed directly. 16* Listener: Opens a socket and listens for incoming connections. On connection all incoming commands are read and entered into command queues. Control commands (i.e. a sync) don't require persistency and are therefore processed directly.
17* Synchronization: Handles synchronization to the source, as well as change-replay to the source. The modification commands generated by the synchronization enter the command queue as well. 17* Synchronization: Handles synchronization to the source, as well as change-replay to the source. The modification commands generated by the synchronization enter the command queue as well.
18 18
19# Preprocessors 19A resource can:
20
21* provide a full mirror of the source.
22* provide metadata for efficient access to the source.
23
24In the former case the local mirror is fully functional locally and changes can be replayed to the source once a connection is established again.
25It the latter case the resource is only functional if a connection to the source is available (which is i.e. not a problem if the source is a local maildir on disk).
26
27## Preprocessors
20Preprocessors are small processors that are guaranteed to be processed before an new/modified/deleted entity reaches storage. They can therefore be used for various tasks that need to be executed on every entity. 28Preprocessors are small processors that are guaranteed to be processed before an new/modified/deleted entity reaches storage. They can therefore be used for various tasks that need to be executed on every entity.
21 29
22Usecases: 30Usecases:
@@ -33,16 +41,29 @@ The following kinds of preprocessors exist:
33 41
34Preprocessors are typically read-only, to i.e. not break signatures of emails. Extra flags that are accessible through the sink domain model, can therefore be stored in the local buffer of each resource. 42Preprocessors are typically read-only, to i.e. not break signatures of emails. Extra flags that are accessible through the sink domain model, can therefore be stored in the local buffer of each resource.
35 43
36## Requirements 44### Requirements
37* A preprocessor must work with batch processing. Because batch-processing is vital for efficient writing to the database, all preprocessors have to be included in the batch processing. 45* A preprocessor must work with batch processing. Because batch-processing is vital for efficient writing to the database, all preprocessors have to be included in the batch processing.
38* Preprocessors need to be fast, since they directly affect how fast a message is processed by the system. 46* Preprocessors need to be fast, since they directly affect how fast a message is processed by the system.
39 47
40## Design 48### Design
41Commands are processed in batches. Each preprocessor thus has the following workflow: 49Commands are processed in batches. Each preprocessor thus has the following workflow:
42* startBatch is called: The preprocessor can do necessary preparation steps to prepare for the batch (like starting a transaction on an external database) 50* startBatch is called: The preprocessor can do necessary preparation steps to prepare for the batch (like starting a transaction on an external database)
43* add/modify/remove is called for every command in the batch: The preprocessor executes the desired actions. 51* add/modify/remove is called for every command in the batch: The preprocessor executes the desired actions.
44* endBatch is called: If the preprocessor wrote to an external database it can now commit the transaction. 52* endBatch is called: If the preprocessor wrote to an external database it can now commit the transaction.
45 53
54### Generic Preprocessors
55Most preprocessors will likely be used by several resources, and are either completely generic, or domain specific (such as only for mail).
56It is therefore desirable to have default implementations for common preprocessors that are ready to be plugged in.
57
58The domain type adaptors provide a generic interface to access most properties of the entities, on top of which generic preprocessors can be implemented.
59It is that way trivial to i.e. implement a preprocessor that populates a hierarchy index of collections.
60
61### Preprocessors generating additional entities
62A preprocessor, such as an email threading preprocessors, might generate additional entities (A thread entity is a regular entity, just like the mail that spawned the thread).
63
64In such a case the preprocessor must invoke the complete pipeline for the new entity.
65
66
46## Indexes 67## Indexes
47Most indexes are implemented as preprocessors to guarantee that they are always updated together with the data. 68Most indexes are implemented as preprocessors to guarantee that they are always updated together with the data.
48 69
@@ -65,6 +86,9 @@ Index types:
65 * sort indexes (i.e. sorted by date) 86 * sort indexes (i.e. sorted by date)
66 * Could also be a lookup in the range index (increase date range until sufficient matches are available) 87 * Could also be a lookup in the range index (increase date range until sufficient matches are available)
67 88
89### Default implementations
90Since only properties of the domain types can be queried, default implementations for commonly used indexes can be provided. These indexes are populated by generic preprocessors that use the domain-type interface to extract properties from individual entites.
91
68### Example index implementations 92### Example index implementations
69* uid lookup 93* uid lookup
70 * add: 94 * add:
@@ -106,25 +130,14 @@ Building the index on-demand is a matter of replaying the relevant dataset and u
106 130
107The indexes status information can be recorded using the latest revision the index has been updated with. 131The indexes status information can be recorded using the latest revision the index has been updated with.
108 132
109## Generic Preprocessors
110Most preprocessors will likely be used by several resources, and are either completely generic, or domain specific (such as only for mail).
111It is therefore desirable to have default implementations for common preprocessors that are ready to be plugged in.
112
113The domain type adaptors provide a generic interface to access most properties of the entities, on top of which generic preprocessors can be implemented.
114It is that way trivial to i.e. implement a preprocessor that populates a hierarchy index of collections.
115
116## Preprocessors generating additional entities
117A preprocessor, such as an email threading preprocessors, might generate additional entities (A thread entity is a regular entity, just like the mail that spawned the thread).
118
119In such a case the preprocessor must invoke the complete pipeline for the new entity.
120
121# Pipeline 133# Pipeline
122A pipeline is an assembly of a set of preprocessors with a defined order. A modification is always persisted at the end of the pipeline once all preprocessors have been processed. 134A pipeline is an assembly of a set of preprocessors with a defined order. A modification is always persisted at the end of the pipeline once all preprocessors have been processed.
123 135
124# Synchronization / Change Replay 136# Synchronization
125* The synchronization can either: 137The synchronization can either:
126 * Generate a full diff directly on top of the db. The diffing process can work against a single revision/snapshot (using transactions). It then generates a necessary changeset for the store. 138
127 * If the source supports incremental changes the changeset can directly be generated from that information. 139* Generate a full diff directly on top of the db. The diffing process can work against a single revision/snapshot (using transactions). It then generates a necessary changeset for the store.
140* If the source supports incremental changes the changeset can directly be generated from that information.
128 141
129The changeset is then simply inserted in the regular modification queue and processed like all other modifications. The synchronizer has to ensure only changes are replayed to the source that didn't come from it already. This is done by marking changes that don't require changereplay to the source. 142The changeset is then simply inserted in the regular modification queue and processed like all other modifications. The synchronizer has to ensure only changes are replayed to the source that didn't come from it already. This is done by marking changes that don't require changereplay to the source.
130 143
@@ -142,8 +155,12 @@ The remoteid mapping has to be updated in two places:
142* New entities that are synchronized immediately get a localid assinged, that is then recorded together with the remoteid. This is required to be able to reference other entities directly in the command queue (i.e. for parent folders). 155* New entities that are synchronized immediately get a localid assinged, that is then recorded together with the remoteid. This is required to be able to reference other entities directly in the command queue (i.e. for parent folders).
143* Entities created by clients get a remoteid assigned during change replay, so the entity can be recognized during the next sync. 156* Entities created by clients get a remoteid assigned during change replay, so the entity can be recognized during the next sync.
144 157
158## Change Replay
159To replay local changes to the source the synchronizer replays all revisions of the store and maintains the current replay state in the synchronization store.
160Changes that already come from the source via synchronizer are not replayed to the source again.
161
145# Testing / Inspection 162# Testing / Inspection
146Resources new to be tested, which often requires inspections into the current state of the resource. This is difficult in an asynchronous system where the whole backend logic is encapsulated in a separate process without running tests in a vastly different setup from how it will be run in production. 163Resources have to be tested, which often requires inspections into the current state of the resource. This is difficult in an asynchronous system where the whole backend logic is encapsulated in a separate process without running tests in a vastly different setup from how it will be run in production.
147 164
148To alleviate this inspection commands are introduced. Inspection commands are special commands that the resource processes just like all other commands, and that have the sole purpose of inspecting the current resource state. Because the command is processed with the same mechanism as other commands we can rely on ordering of commands in a way that a prior command is guaranteed to be executed once the inspection command is processed. 165To alleviate this inspection commands are introduced. Inspection commands are special commands that the resource processes just like all other commands, and that have the sole purpose of inspecting the current resource state. Because the command is processed with the same mechanism as other commands we can rely on ordering of commands in a way that a prior command is guaranteed to be executed once the inspection command is processed.
149 166
diff --git a/docs/akonadish.md b/docs/sinksh.md
index 9884169..9884169 100644
--- a/docs/akonadish.md
+++ b/docs/sinksh.md
diff --git a/docs/storage.md b/docs/storage.md
index 4852131..afd55d8 100644
--- a/docs/storage.md
+++ b/docs/storage.md
@@ -1,17 +1,3 @@
1## Store access
2Access to the entities happens through a well defined interface that defines a property-map for each supported domain type. A property map could look like:
3```
4Event {
5 startDate: QDateTime
6 subject: QString
7 ...
8}
9```
10
11This property map can be freely extended with new properties for various features. It shouldn't adhere to any external specification and exists solely to define how to access the data.
12
13Clients will map these properties to the values of their domain object implementations, and resources will map the properties to the values in their buffers.
14
15## Storage Model 1## Storage Model
16The storage model is simple: 2The storage model is simple:
17``` 3```
@@ -42,8 +28,7 @@ Each entity can be as normalized/denormalized as useful. It is not necessary to
42 28
43Denormalized: 29Denormalized:
44 30
45* priority is that mime message stays intact (signatures/encryption) 31* priority is that the mime message stays intact (signatures/encryption)
46* could we still provide a streaming api for attachments?
47 32
48``` 33```
49Mail { 34Mail {
@@ -55,7 +40,7 @@ Mail {
55Normalized: 40Normalized:
56 41
57* priority is that we can access individual members efficiently. 42* priority is that we can access individual members efficiently.
58* we don't care about exact reproducability of e.g. ical file 43* we don't care about exact reproducability of e.g. an ical file
59``` 44```
60Event { 45Event {
61 id 46 id
@@ -101,7 +86,7 @@ The resource can be effectively removed from disk (besides configuration),
101by deleting the directories matching `$RESOURCE_IDENTIFIER*` and everything they contain. 86by deleting the directories matching `$RESOURCE_IDENTIFIER*` and everything they contain.
102 87
103#### Design Considerations 88#### Design Considerations
104* The stores are split by buffertype, so a full scan (which is done by type), doesn't require filtering by type first. The downside is that an additional lookup is required to get from revision to the data. 89The stores are split by buffertype, so a full scan (which is done by type), doesn't require filtering by type first. The downside is that an additional lookup is required to get from revision to the data.
105 90
106### Revisions 91### Revisions
107Every operation (create/delete/modify), leads to a new revision. The revision is an ever increasing number for the complete store. 92Every operation (create/delete/modify), leads to a new revision. The revision is an ever increasing number for the complete store.
@@ -167,6 +152,8 @@ Using regular files as the interface has the advantages:
167The copy is necessary to guarantee that the file remains for the client/resource even if the resource removes the file on it's side as part of a sync. 152The copy is necessary to guarantee that the file remains for the client/resource even if the resource removes the file on it's side as part of a sync.
168The copy could be optimized by using hardlinks, which is not a portable solution though. For some next-gen copy-on-write filesystems copying is a very cheap operation. 153The copy could be optimized by using hardlinks, which is not a portable solution though. For some next-gen copy-on-write filesystems copying is a very cheap operation.
169 154
155A downside of having a file based design is that it's not possible to directly stream from a remote resource i.e. into the application memory, it always has to go via a file.
156
170## Database choice 157## Database choice
171By design we're interested in key-value stores or perhaps document databases. This is because a fixed schema is not useful for this design, which makes 158By design we're interested in key-value stores or perhaps document databases. This is because a fixed schema is not useful for this design, which makes
172SQL not very useful (it would just be a very slow key-value store). While document databases would allow for indexes on certain properties (which is something we need), we did not yet find any contenders that looked like they would be useful for this system. 159SQL not very useful (it would just be a very slow key-value store). While document databases would allow for indexes on certain properties (which is something we need), we did not yet find any contenders that looked like they would be useful for this system.
diff --git a/docs/terminology.md b/docs/terminology.md
index 1826bec..5238c79 100644
--- a/docs/terminology.md
+++ b/docs/terminology.md
@@ -13,7 +13,7 @@ It is recommended to familiarize yourself with the terms before going further in
13* resource: A plugin which provides client command processing, a store facade and synchronization for a given type of store. The resource also manages the configuration for a given source including server settings, local paths, etc. 13* resource: A plugin which provides client command processing, a store facade and synchronization for a given type of store. The resource also manages the configuration for a given source including server settings, local paths, etc.
14* store facade: An object provided by resources which provides transformations between domain objects and the store. 14* store facade: An object provided by resources which provides transformations between domain objects and the store.
15* synchronizer: The operating system process responsible for overseeing the process of modifying and synchronizing a store. To accomplish this, a synchronizer loads the correct resource plugin, manages pipelines and handles client communication. One synchronizer is created for each source that is accessed by clients; these processes are shared by all clients. 15* synchronizer: The operating system process responsible for overseeing the process of modifying and synchronizing a store. To accomplish this, a synchronizer loads the correct resource plugin, manages pipelines and handles client communication. One synchronizer is created for each source that is accessed by clients; these processes are shared by all clients.
16* Preprocessor: A component that takes an entity and performs some modification of it (e.g. changes the folder an email is in) or processes it in some way (e.g. indexes it) 16* preprocessor: A component that takes an entity and performs some modification of it (e.g. changes the folder an email is in) or processes it in some way (e.g. indexes it)
17* pipeline: A run-time definable set of filters which are applied to an entity after a resource has performed a specific kind of function on it (create, modify, delete) 17* pipeline: A run-time definable set of filters which are applied to an entity after a resource has performed a specific kind of function on it (create, modify, delete)
18* query: A declarative method for requesting entities from one or more sources that match a given set of constraints 18* query: A declarative method for requesting entities from one or more sources that match a given set of constraints
19* command: Clients request modifications, additions and deletions to the store by sending commands to a synchronizer for processing 19* command: Clients request modifications, additions and deletions to the store by sending commands to a synchronizer for processing
diff --git a/docs/tradeoffs.md b/docs/tradeoffs.md
new file mode 100644
index 0000000..d0e32c1
--- /dev/null
+++ b/docs/tradeoffs.md
@@ -0,0 +1,36 @@
1# Tradeoffs/Design Decisions
2* Key-Value store instead of relational
3 * `+` Schemaless, easier to evolve
4 * `-` No need to fully normalize the data in order to make it queriable. And without full normalization SQL is not really useful and bad performance wise.
5 * `-` We need to maintain our own indexes
6
7* Individual store per resource
8 * Storage format defined by resource individually
9 * `-` Each resource needs to define it's own schema
10 * `+` Resources can adjust storage format to map well on what it has to synchronize
11 * `+` Synchronization state can directly be embedded into messages
12 * `+` Individual resources could switch to another store technology
13 * `+` Easier maintenance
14 * `+` Resource is only responsible for it's own store and doesn't accidentaly break another resources store
15 * `-` Inter`-`resource moves are both more complicated and more expensive from a client perspective
16 * `+` Inter`-`resource moves become simple additions and removals from a resource perspective
17 * `-` No system`-`wide unique id per message (only resource/id tuple identifies a message uniquely)
18 * `+` Stores can work fully concurrently (also for writing)
19
20* Indexes defined and maintained by resources
21 * `-` Relational queries accross resources are expensive (depending on the query perhaps not even feasible)
22 * `-` Each resource needs to define it's own set of indexes
23 * `+` Flexible design as it allows to change indexes on a per resource level
24 * `+` Indexes can be optimized towards resources main usecases
25 * `+` Indexes can be shared with the source (IMAP serverside threading)
26
27* Shared domain types as common interface for client applications
28 * `-` yet another abstraction layer that requires translation to other layers and maintenance
29 * `+` decoupling of domain logic from data access
30 * `+` allows to evolve types according to needs (not coupled to specific application domain types)
31
32# Risks
33* key-value store does not perform with large amounts of data
34* query performance is not sufficient
35* turnaround time for modifications is too high to feel responsive
36* design turns out similarly complex as Akonadi
diff --git a/examples/client/CMakeLists.txt b/examples/client/CMakeLists.txt
index 85840c4..ef00368 100644
--- a/examples/client/CMakeLists.txt
+++ b/examples/client/CMakeLists.txt
@@ -3,6 +3,6 @@ project(sink_client)
3include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 3include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
4 4
5add_executable(${PROJECT_NAME} main.cpp console.cpp) 5add_executable(${PROJECT_NAME} main.cpp console.cpp)
6target_link_libraries(${PROJECT_NAME} sinkcommon) 6target_link_libraries(${PROJECT_NAME} sink)
7qt5_use_modules(${PROJECT_NAME} Widgets Network) 7qt5_use_modules(${PROJECT_NAME} Widgets Network)
8install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 8install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/examples/client/main.cpp b/examples/client/main.cpp
index f437c19..07e780e 100644
--- a/examples/client/main.cpp
+++ b/examples/client/main.cpp
@@ -22,7 +22,7 @@
22#include <QCommandLineOption> 22#include <QCommandLineOption>
23#include <QTime> 23#include <QTime>
24 24
25#include "common/clientapi.h" 25#include "common/store.h"
26#include "common/log.h" 26#include "common/log.h"
27 27
28#include <QWidget> 28#include <QWidget>
@@ -38,6 +38,7 @@
38 */ 38 */
39class StoreBase { 39class StoreBase {
40public: 40public:
41 virtual ~StoreBase(){};
41 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject() = 0; 42 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject() = 0;
42 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier = QByteArray()) = 0; 43 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier = QByteArray()) = 0;
43 virtual KAsync::Job<void> create(const Sink::ApplicationDomain::ApplicationDomainType &type) = 0; 44 virtual KAsync::Job<void> create(const Sink::ApplicationDomain::ApplicationDomainType &type) = 0;
diff --git a/examples/dummyresource/CMakeLists.txt b/examples/dummyresource/CMakeLists.txt
index 6ffac9a..6400f0c 100644
--- a/examples/dummyresource/CMakeLists.txt
+++ b/examples/dummyresource/CMakeLists.txt
@@ -7,6 +7,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
7add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp dummystore.cpp) 7add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp dummystore.cpp)
8generate_flatbuffers(${PROJECT_NAME} dummycalendar) 8generate_flatbuffers(${PROJECT_NAME} dummycalendar)
9qt5_use_modules(${PROJECT_NAME} Core Network) 9qt5_use_modules(${PROJECT_NAME} Core Network)
10target_link_libraries(${PROJECT_NAME} sinkcommon) 10target_link_libraries(${PROJECT_NAME} sink)
11 11
12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) 12install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH})
diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp
index 31633d7..48858da 100644
--- a/examples/dummyresource/resourcefactory.cpp
+++ b/examples/dummyresource/resourcefactory.cpp
@@ -95,12 +95,17 @@ Sink::ApplicationDomain::Folder::Ptr DummyResource::createFolder(const QByteArra
95 95
96void DummyResource::synchronize(const QByteArray &bufferType, const QMap<QString, QMap<QString, QVariant> > &data, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<Sink::ApplicationDomain::ApplicationDomainType::Ptr(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, Sink::Storage::Transaction &)> createEntity) 96void DummyResource::synchronize(const QByteArray &bufferType, const QMap<QString, QMap<QString, QVariant> > &data, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<Sink::ApplicationDomain::ApplicationDomainType::Ptr(const QByteArray &ridBuffer, const QMap<QString, QVariant> &data, Sink::Storage::Transaction &)> createEntity)
97{ 97{
98 auto time = QSharedPointer<QTime>::create();
99 time->start();
98 //TODO find items to remove 100 //TODO find items to remove
101 int count = 0;
99 for (auto it = data.constBegin(); it != data.constEnd(); it++) { 102 for (auto it = data.constBegin(); it != data.constEnd(); it++) {
103 count++;
100 const auto remoteId = it.key().toUtf8(); 104 const auto remoteId = it.key().toUtf8();
101 auto entity = createEntity(remoteId, it.value(), synchronizationTransaction); 105 auto entity = createEntity(remoteId, it.value(), synchronizationTransaction);
102 createOrModify(transaction, synchronizationTransaction, adaptorFactory, bufferType, remoteId, *entity); 106 createOrModify(transaction, synchronizationTransaction, adaptorFactory, bufferType, remoteId, *entity);
103 } 107 }
108 Trace() << "Sync of " << count << " entities of type " << bufferType << " done." << Sink::Log::TraceTime(time->elapsed());
104} 109}
105 110
106KAsync::Job<void> DummyResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) 111KAsync::Job<void> DummyResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore)
@@ -128,6 +133,11 @@ KAsync::Job<void> DummyResource::replay(Sink::Storage &synchronizationStore, con
128 return KAsync::null<void>(); 133 return KAsync::null<void>();
129} 134}
130 135
136void DummyResource::removeDataFromDisk()
137{
138 removeFromDisk(mResourceInstanceIdentifier);
139}
140
131void DummyResource::removeFromDisk(const QByteArray &instanceIdentifier) 141void DummyResource::removeFromDisk(const QByteArray &instanceIdentifier)
132{ 142{
133 GenericResource::removeFromDisk(instanceIdentifier); 143 GenericResource::removeFromDisk(instanceIdentifier);
diff --git a/examples/dummyresource/resourcefactory.h b/examples/dummyresource/resourcefactory.h
index 240bb9f..865f6e5 100644
--- a/examples/dummyresource/resourcefactory.h
+++ b/examples/dummyresource/resourcefactory.h
@@ -39,6 +39,7 @@ public:
39 DummyResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline = QSharedPointer<Sink::Pipeline>()); 39 DummyResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline = QSharedPointer<Sink::Pipeline>());
40 KAsync::Job<void> synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) Q_DECL_OVERRIDE; 40 KAsync::Job<void> synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) Q_DECL_OVERRIDE;
41 using GenericResource::synchronizeWithSource; 41 using GenericResource::synchronizeWithSource;
42 void removeDataFromDisk() Q_DECL_OVERRIDE;
42 static void removeFromDisk(const QByteArray &instanceIdentifier); 43 static void removeFromDisk(const QByteArray &instanceIdentifier);
43 KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE; 44 KAsync::Job<void> inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) Q_DECL_OVERRIDE;
44private: 45private:
diff --git a/examples/maildirresource/CMakeLists.txt b/examples/maildirresource/CMakeLists.txt
index baacd44..efaa266 100644
--- a/examples/maildirresource/CMakeLists.txt
+++ b/examples/maildirresource/CMakeLists.txt
@@ -8,7 +8,7 @@ find_package(KF5 COMPONENTS REQUIRED Mime)
8add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp) 8add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp)
9# generate_flatbuffers(${PROJECT_NAME} dummycalendar) 9# generate_flatbuffers(${PROJECT_NAME} dummycalendar)
10qt5_use_modules(${PROJECT_NAME} Core Network) 10qt5_use_modules(${PROJECT_NAME} Core Network)
11target_link_libraries(${PROJECT_NAME} sinkcommon maildir KF5::Mime) 11target_link_libraries(${PROJECT_NAME} sink maildir KF5::Mime)
12 12
13install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) 13install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH})
14 14
diff --git a/examples/maildirresource/facade.cpp b/examples/maildirresource/facade.cpp
index 7178ab9..a7a0348 100644
--- a/examples/maildirresource/facade.cpp
+++ b/examples/maildirresource/facade.cpp
@@ -19,17 +19,47 @@
19 19
20#include "facade.h" 20#include "facade.h"
21 21
22#include <QDir>
23#include <QFileInfo>
24
22#include "domainadaptor.h" 25#include "domainadaptor.h"
26#include "queryrunner.h"
23 27
24MaildirResourceMailFacade::MaildirResourceMailFacade(const QByteArray &instanceIdentifier) 28MaildirResourceMailFacade::MaildirResourceMailFacade(const QByteArray &instanceIdentifier)
25 : Sink::GenericFacade<Sink::ApplicationDomain::Mail>(instanceIdentifier, QSharedPointer<MaildirMailAdaptorFactory>::create()) 29 : Sink::GenericFacade<Sink::ApplicationDomain::Mail>(instanceIdentifier, QSharedPointer<MaildirMailAdaptorFactory>::create())
26{ 30{
31 mResultTransformation = [](Sink::ApplicationDomain::ApplicationDomainType &value) {
32 if (value.hasProperty("mimeMessage")) {
33 const auto property = value.getProperty("mimeMessage");
34 //Transform the mime message property into the actual path on disk.
35 const auto mimeMessage = property.toString();
36 auto parts = mimeMessage.split('/');
37 auto key = parts.takeLast();
38 const auto folderPath = parts.join('/');
39 const auto path = folderPath + "/cur/";
40
41 Trace() << "Looking for mail in: " << path << key;
42 QDir dir(path);
43 const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files);
44 if (list.size() != 1) {
45 Warning() << "Failed to find message " << path << key << list.size();
46 value.setProperty("mimeMessage", QVariant());
47 } else {
48 value.setProperty("mimeMessage", list.at(0).filePath());
49 }
50 }
51 };
27} 52}
28 53
29MaildirResourceMailFacade::~MaildirResourceMailFacade() 54MaildirResourceMailFacade::~MaildirResourceMailFacade()
30{ 55{
31} 56}
32 57
58QPair<KAsync::Job<void>, Sink::ResultEmitter<Sink::ApplicationDomain::Mail::Ptr>::Ptr> MaildirResourceMailFacade::load(const Sink::Query &query)
59{
60 return Sink::GenericFacade<Sink::ApplicationDomain::Mail>::load(query);
61}
62
33 63
34MaildirResourceFolderFacade::MaildirResourceFolderFacade(const QByteArray &instanceIdentifier) 64MaildirResourceFolderFacade::MaildirResourceFolderFacade(const QByteArray &instanceIdentifier)
35 : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(instanceIdentifier, QSharedPointer<MaildirFolderAdaptorFactory>::create()) 65 : Sink::GenericFacade<Sink::ApplicationDomain::Folder>(instanceIdentifier, QSharedPointer<MaildirFolderAdaptorFactory>::create())
diff --git a/examples/maildirresource/facade.h b/examples/maildirresource/facade.h
index a243b0d..38981d0 100644
--- a/examples/maildirresource/facade.h
+++ b/examples/maildirresource/facade.h
@@ -26,6 +26,7 @@ class MaildirResourceMailFacade : public Sink::GenericFacade<Sink::ApplicationDo
26public: 26public:
27 MaildirResourceMailFacade(const QByteArray &instanceIdentifier); 27 MaildirResourceMailFacade(const QByteArray &instanceIdentifier);
28 virtual ~MaildirResourceMailFacade(); 28 virtual ~MaildirResourceMailFacade();
29 QPair<KAsync::Job<void>, Sink::ResultEmitter<Sink::ApplicationDomain::Mail::Ptr>::Ptr> load(const Sink::Query &query) Q_DECL_OVERRIDE;
29}; 30};
30 31
31class MaildirResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder> 32class MaildirResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder>
diff --git a/examples/maildirresource/libmaildir/maildir.cpp b/examples/maildirresource/libmaildir/maildir.cpp
index 3d4630f..59e7e5c 100644
--- a/examples/maildirresource/libmaildir/maildir.cpp
+++ b/examples/maildirresource/libmaildir/maildir.cpp
@@ -142,7 +142,6 @@ public:
142 { 142 {
143 // KeyCache* keyCache = KeyCache::self(); 143 // KeyCache* keyCache = KeyCache::self();
144 // if (keyCache->isNewKey(path, key)) { 144 // if (keyCache->isNewKey(path, key)) {
145 qWarning() << path + QString::fromLatin1("/new/") + key;
146 if (QFile::exists(path + QString::fromLatin1("/new/") + key)) { 145 if (QFile::exists(path + QString::fromLatin1("/new/") + key)) {
147#ifdef DEBUG_KEYCACHE_CONSITENCY 146#ifdef DEBUG_KEYCACHE_CONSITENCY
148 if (!QFile::exists(path + QString::fromLatin1("/new/") + key)) { 147 if (!QFile::exists(path + QString::fromLatin1("/new/") + key)) {
@@ -178,6 +177,13 @@ public:
178 return realKey; 177 return realKey;
179 } 178 }
180 179
180 static QString stripFlags(const QString& key)
181 {
182 const QRegExp rx = *(statusSeparatorRx());
183 const int index = key.indexOf(rx);
184 return key.mid(0, index);
185 }
186
181 static QString subDirNameForFolderName(const QString &folderName) 187 static QString subDirNameForFolderName(const QString &folderName)
182 { 188 {
183 return QString::fromLatin1(".%1.directory").arg(folderName); 189 return QString::fromLatin1(".%1.directory").arg(folderName);
@@ -560,7 +566,20 @@ QDateTime Maildir::lastModified(const QString& key) const
560 return info.lastModified(); 566 return info.lastModified();
561} 567}
562 568
563QByteArray Maildir::readEntryHeadersFromFile(const QString& file) const 569QString Maildir::getKeyFromFile(const QString& file)
570{
571 return Maildir::Private::stripFlags(file.split('/').last());
572}
573
574QString Maildir::getDirectoryFromFile( const QString& file )
575{
576 auto parts = file.split('/');
577 parts.removeLast(); //File
578 parts.removeLast(); //cur/new/tmp
579 return parts.join('/') + "/";
580}
581
582QByteArray Maildir::readEntryHeadersFromFile(const QString& file)
564{ 583{
565 QByteArray result; 584 QByteArray result;
566 585
@@ -643,9 +662,14 @@ QString Maildir::addEntry(const QByteArray& data)
643 662
644 QFile f(key); 663 QFile f(key);
645 bool result = f.open(QIODevice::WriteOnly); 664 bool result = f.open(QIODevice::WriteOnly);
665 if (!result) {
666 qWarning() << f.errorString();
667 qWarning() << "Cannot write to mail file: " << key;
668 }
646 result = result & (f.write(data) != -1); 669 result = result & (f.write(data) != -1);
647 f.close(); 670 f.close();
648 if (!result) { 671 if (!result) {
672 qWarning() << "Cannot write to mail file: " << key;
649 // d->lastError = i18n("Cannot write to mail file %1." , key); 673 // d->lastError = i18n("Cannot write to mail file %1." , key);
650 return QString(); 674 return QString();
651 } 675 }
@@ -688,88 +712,89 @@ bool Maildir::removeEntry(const QString& key)
688 // return QFile::remove(realKey); 712 // return QFile::remove(realKey);
689} 713}
690 714
691// QString Maildir::changeEntryFlags(const QString& key, const Sink::Item::Flags& flags) 715QString Maildir::changeEntryFlags(const QString& key, const Maildir::Flags& flags)
692// { 716{
693// QString realKey(d->findRealKey(key)); 717 QString realKey(d->findRealKey(key));
694// if (realKey.isEmpty()) { 718 if (realKey.isEmpty()) {
695// qWarning() << "Maildir::changeEntryFlags unable to find: " << key; 719 qWarning() << "Maildir::changeEntryFlags unable to find: " << key;
696// d->lastError = i18n("Cannot locate mail file %1." , key); 720 // d->lastError = i18n("Cannot locate mail file %1." , key);
697// return QString(); 721 return QString();
698// } 722 }
699// 723
700// const QRegExp rx = *(statusSeparatorRx()); 724 const QRegExp rx = *(statusSeparatorRx());
701// QString finalKey = key.left(key.indexOf(rx)); 725 QString finalKey = key.left(key.indexOf(rx));
702// 726
703// QStringList mailDirFlags; 727 QStringList mailDirFlags;
704// Q_FOREACH (const Sink::Item::Flag &flag, flags) { 728 if (flags & Forwarded)
705// if (flag == Sink::MessageFlags::Forwarded) 729 mailDirFlags << QLatin1String("P");
706// mailDirFlags << QLatin1String("P"); 730 if (flags & Replied)
707// if (flag == Sink::MessageFlags::Replied) 731 mailDirFlags << QLatin1String("R");
708// mailDirFlags << QLatin1String("R"); 732 if (flags & Seen)
709// if (flag == Sink::MessageFlags::Seen) 733 mailDirFlags << QLatin1String("S");
710// mailDirFlags << QLatin1String("S"); 734 if (flags & Deleted)
711// if (flag == Sink::MessageFlags::Deleted) 735 mailDirFlags << QLatin1String("T");
712// mailDirFlags << QLatin1String("T"); 736 if (flags & Flagged)
713// if (flag == Sink::MessageFlags::Flagged) 737 mailDirFlags << QLatin1String("F");
714// mailDirFlags << QLatin1String("F"); 738
715// } 739 mailDirFlags.sort();
716// mailDirFlags.sort(); 740 if (!mailDirFlags.isEmpty()) {
717// if (!mailDirFlags.isEmpty()) { 741#ifdef Q_OS_WIN
718// #ifdef Q_OS_WIN 742 finalKey.append(QLatin1String("!2,") + mailDirFlags.join(QString()));
719// finalKey.append(QLatin1String("!2,") + mailDirFlags.join(QString())); 743#else
720// #else 744 finalKey.append(QLatin1String(":2,") + mailDirFlags.join(QString()));
721// finalKey.append(QLatin1String(":2,") + mailDirFlags.join(QString())); 745#endif
722// #endif 746 }
723// } 747
724// 748 QString newUniqueKey = finalKey; //key without path
725// QString newUniqueKey = finalKey; //key without path 749 finalKey.prepend(d->path + QString::fromLatin1("/cur/"));
726// finalKey.prepend(d->path + QString::fromLatin1("/cur/")); 750
727// 751 if (realKey == finalKey) {
728// if (realKey == finalKey) { 752 // Somehow it already is named this way (e.g. migration bug -> wrong status in sink)
729// // Somehow it already is named this way (e.g. migration bug -> wrong status in sink) 753 qWarning() << "File already named that way: " << newUniqueKey << finalKey;
730// return newUniqueKey; 754 return newUniqueKey;
731// } 755 }
732// 756
733// QFile f(realKey); 757 QFile f(realKey);
734// if (QFile::exists(finalKey)) { 758 if (QFile::exists(finalKey)) {
735// QFile destFile(finalKey); 759 QFile destFile(finalKey);
736// QByteArray destContent; 760 QByteArray destContent;
737// if (destFile.open(QIODevice::ReadOnly)) { 761 if (destFile.open(QIODevice::ReadOnly)) {
738// destContent = destFile.readAll(); 762 destContent = destFile.readAll();
739// destFile.close(); 763 destFile.close();
740// } 764 }
741// QByteArray sourceContent; 765 QByteArray sourceContent;
742// if (f.open(QIODevice::ReadOnly)) { 766 if (f.open(QIODevice::ReadOnly)) {
743// sourceContent = f.readAll(); 767 sourceContent = f.readAll();
744// f.close(); 768 f.close();
745// } 769 }
746// 770
747// if (destContent != sourceContent) { 771 if (destContent != sourceContent) {
748// QString newFinalKey = QLatin1String("1-") + newUniqueKey; 772 QString newFinalKey = QLatin1String("1-") + newUniqueKey;
749// int i = 1; 773 int i = 1;
750// while (QFile::exists(d->path + QString::fromLatin1("/cur/") + newFinalKey)) { 774 while (QFile::exists(d->path + QString::fromLatin1("/cur/") + newFinalKey)) {
751// i++; 775 i++;
752// newFinalKey = QString::number(i) + QLatin1Char('-') + newUniqueKey; 776 newFinalKey = QString::number(i) + QLatin1Char('-') + newUniqueKey;
753// } 777 }
754// finalKey = d->path + QString::fromLatin1("/cur/") + newFinalKey; 778 finalKey = d->path + QString::fromLatin1("/cur/") + newFinalKey;
755// } else { 779 } else {
756// QFile::remove(finalKey); //they are the same 780 QFile::remove(finalKey); //they are the same
757// } 781 }
758// } 782 }
759// 783
760// if (!f.rename(finalKey)) { 784 if (!f.rename(finalKey)) {
761// qWarning() << "Maildir: Failed to rename entry: " << f.fileName() << " to " << finalKey << "! Error: " << f.errorString(); 785 qWarning() << "Maildir: Failed to rename entry: " << f.fileName() << " to " << finalKey << "! Error: " << f.errorString();
762// d->lastError = i18n("Failed to update the file name %1 to %2 on the disk. The error was: %3." , f.fileName(), finalKey, f.errorString()); 786 // d->lastError = i18n("Failed to update the file name %1 to %2 on the disk. The error was: %3." , f.fileName(), finalKey, f.errorString());
763// return QString(); 787 return QString();
764// } 788 }
765// 789 qWarning() << "Renamed file: " << f.fileName() << finalKey;
766// KeyCache *keyCache = KeyCache::self(); 790
767// keyCache->removeKey(d->path, key); 791 // KeyCache *keyCache = KeyCache::self();
768// keyCache->addCurKey(d->path, newUniqueKey); 792 // keyCache->removeKey(d->path, key);
769// 793 // keyCache->addCurKey(d->path, newUniqueKey);
770// return newUniqueKey; 794
771// } 795 return newUniqueKey;
772// 796}
797
773Maildir::Flags Maildir::readEntryFlags(const QString& key) 798Maildir::Flags Maildir::readEntryFlags(const QString& key)
774{ 799{
775 Flags flags; 800 Flags flags;
diff --git a/examples/maildirresource/libmaildir/maildir.h b/examples/maildirresource/libmaildir/maildir.h
index a89a832..6c68656 100644
--- a/examples/maildirresource/libmaildir/maildir.h
+++ b/examples/maildirresource/libmaildir/maildir.h
@@ -167,13 +167,14 @@ public:
167 */ 167 */
168 QByteArray readEntry( const QString& key ) const; 168 QByteArray readEntry( const QString& key ) const;
169 169
170 enum MailFlags { 170 enum Flag {
171 Forwarded, 171 Forwarded = 0x1,
172 Replied, 172 Replied = 0x2,
173 Seen, 173 Seen = 0x4,
174 Flagged 174 Flagged = 0x8,
175 Deleted = 0x10
175 }; 176 };
176 Q_DECLARE_FLAGS(Flags, MailFlags); 177 Q_DECLARE_FLAGS(Flags, Flag);
177 178
178 /** 179 /**
179 * Return the flags encoded in the maildir file name for an entry 180 * Return the flags encoded in the maildir file name for an entry
@@ -184,7 +185,7 @@ public:
184 * Return the contents of the headers section of the file the maildir with the given @p file, that 185 * Return the contents of the headers section of the file the maildir with the given @p file, that
185 * is a full path to the file. You can get it by using findRealKey(key) . 186 * is a full path to the file. You can get it by using findRealKey(key) .
186 */ 187 */
187 QByteArray readEntryHeadersFromFile( const QString& file ) const; 188 static QByteArray readEntryHeadersFromFile( const QString& file );
188 189
189 /** 190 /**
190 * Return the contents of the headers section of the file the maildir with the given @p key. 191 * Return the contents of the headers section of the file the maildir with the given @p key.
@@ -211,7 +212,7 @@ public:
211 * Change the flags for an entry specified by @p key. Returns the new key of the entry (the key might change because 212 * Change the flags for an entry specified by @p key. Returns the new key of the entry (the key might change because
212 * flags are stored in the unique filename). 213 * flags are stored in the unique filename).
213 */ 214 */
214 // QString changeEntryFlags( const QString& key, const Sink::Item::Flags& flags ); 215 QString changeEntryFlags( const QString& key, const Flags& flags );
215 216
216 /** 217 /**
217 * Moves this maildir into @p destination. 218 * Moves this maildir into @p destination.
@@ -261,6 +262,19 @@ public:
261 querying the last error string. */ 262 querying the last error string. */
262 QString lastError() const; 263 QString lastError() const;
263 264
265 /**
266 * Returns the key from the file identified by the full path @param file.
267 */
268 static QString getKeyFromFile( const QString& file );
269
270 /**
271 * Returns the directory from a file.
272 *
273 * Strips key and new/cur/tmp.
274 * The returned path is ended with a trailing slash.
275 */
276 static QString getDirectoryFromFile( const QString& file );
277
264private: 278private:
265 void swap( const Maildir& ); 279 void swap( const Maildir& );
266 class Private; 280 class Private;
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp
index 33883a7..0ddd8f8 100644
--- a/examples/maildirresource/maildirresource.cpp
+++ b/examples/maildirresource/maildirresource.cpp
@@ -46,6 +46,9 @@
46#define ENTITY_TYPE_MAIL "mail" 46#define ENTITY_TYPE_MAIL "mail"
47#define ENTITY_TYPE_FOLDER "folder" 47#define ENTITY_TYPE_FOLDER "folder"
48 48
49#undef DEBUG_AREA
50#define DEBUG_AREA "resource.maildir"
51
49MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) 52MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline)
50 : Sink::GenericResource(instanceIdentifier, pipeline), 53 : Sink::GenericResource(instanceIdentifier, pipeline),
51 mMailAdaptorFactory(QSharedPointer<MaildirMailAdaptorFactory>::create()), 54 mMailAdaptorFactory(QSharedPointer<MaildirMailAdaptorFactory>::create()),
@@ -103,7 +106,7 @@ void MaildirResource::synchronizeFolders(Sink::Storage::Transaction &transaction
103 //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, 106 //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index,
104 //but we currently fail to iterate over all entries in an index it seems. 107 //but we currently fail to iterate over all entries in an index it seems.
105 // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true); 108 // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true);
106 auto mainDatabase = transaction.openDatabase(bufferType + ".main"); 109 auto mainDatabase = Sink::Storage::mainDatabase(transaction, bufferType);
107 mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { 110 mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) {
108 callback(key); 111 callback(key);
109 return true; 112 return true;
@@ -132,6 +135,8 @@ void MaildirResource::synchronizeFolders(Sink::Storage::Transaction &transaction
132void MaildirResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path) 135void MaildirResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path)
133{ 136{
134 Trace() << "Synchronizing mails" << path; 137 Trace() << "Synchronizing mails" << path;
138 auto time = QSharedPointer<QTime>::create();
139 time->start();
135 const QByteArray bufferType = ENTITY_TYPE_MAIL; 140 const QByteArray bufferType = ENTITY_TYPE_MAIL;
136 141
137 KPIM::Maildir maildir(path, true); 142 KPIM::Maildir maildir(path, true);
@@ -162,7 +167,9 @@ void MaildirResource::synchronizeMails(Sink::Storage::Transaction &transaction,
162 } 167 }
163 ); 168 );
164 169
170 int count = 0;
165 while (entryIterator->hasNext()) { 171 while (entryIterator->hasNext()) {
172 count++;
166 const QString filePath = QDir::fromNativeSeparators(entryIterator->next()); 173 const QString filePath = QDir::fromNativeSeparators(entryIterator->next());
167 const QString fileName = entryIterator->fileName(); 174 const QString fileName = entryIterator->fileName();
168 const auto remoteId = filePath.toUtf8(); 175 const auto remoteId = filePath.toUtf8();
@@ -172,6 +179,7 @@ void MaildirResource::synchronizeMails(Sink::Storage::Transaction &transaction,
172 msg->parse(); 179 msg->parse();
173 180
174 const auto flags = maildir.readEntryFlags(fileName); 181 const auto flags = maildir.readEntryFlags(fileName);
182 const auto maildirKey = maildir.getKeyFromFile(fileName);
175 183
176 Trace() << "Found a mail " << filePath << " : " << fileName << msg->subject(true)->asUnicodeString(); 184 Trace() << "Found a mail " << filePath << " : " << fileName << msg->subject(true)->asUnicodeString();
177 185
@@ -181,12 +189,16 @@ void MaildirResource::synchronizeMails(Sink::Storage::Transaction &transaction,
181 mail.setProperty("senderName", msg->from(true)->asUnicodeString()); 189 mail.setProperty("senderName", msg->from(true)->asUnicodeString());
182 mail.setProperty("date", msg->date(true)->dateTime()); 190 mail.setProperty("date", msg->date(true)->dateTime());
183 mail.setProperty("folder", folderLocalId); 191 mail.setProperty("folder", folderLocalId);
184 mail.setProperty("mimeMessage", filePath); 192 //We only store the directory path + key, so we facade can add the changing bits (flags)
193 mail.setProperty("mimeMessage", KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey);
185 mail.setProperty("unread", !flags.testFlag(KPIM::Maildir::Seen)); 194 mail.setProperty("unread", !flags.testFlag(KPIM::Maildir::Seen));
186 mail.setProperty("important", flags.testFlag(KPIM::Maildir::Flagged)); 195 mail.setProperty("important", flags.testFlag(KPIM::Maildir::Flagged));
187 196
188 createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail); 197 createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail);
189 } 198 }
199 const auto elapsed = time->elapsed();
200 Trace() << "Synchronized " << count << " mails in " << listingPath << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]";
201
190} 202}
191 203
192KAsync::Job<void> MaildirResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) 204KAsync::Job<void> MaildirResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore)
@@ -216,7 +228,7 @@ KAsync::Job<void> MaildirResource::replay(Sink::Storage &synchronizationStore, c
216 228
217 Trace() << "Replaying " << key << type; 229 Trace() << "Replaying " << key << type;
218 if (type == ENTITY_TYPE_FOLDER) { 230 if (type == ENTITY_TYPE_FOLDER) {
219 Sink::EntityBuffer buffer(value.data(), value.size()); 231 Sink::EntityBuffer buffer(value);
220 const Sink::Entity &entity = buffer.entity(); 232 const Sink::Entity &entity = buffer.entity();
221 const auto metadataBuffer = Sink::EntityBuffer::readBuffer<Sink::Metadata>(entity.metadata()); 233 const auto metadataBuffer = Sink::EntityBuffer::readBuffer<Sink::Metadata>(entity.metadata());
222 if (metadataBuffer && !metadataBuffer->replayToSource()) { 234 if (metadataBuffer && !metadataBuffer->replayToSource()) {
@@ -248,7 +260,7 @@ KAsync::Job<void> MaildirResource::replay(Sink::Storage &synchronizationStore, c
248 Warning() << "Unkown operation" << operation; 260 Warning() << "Unkown operation" << operation;
249 } 261 }
250 } else if (type == ENTITY_TYPE_MAIL) { 262 } else if (type == ENTITY_TYPE_MAIL) {
251 Sink::EntityBuffer buffer(value.data(), value.size()); 263 Sink::EntityBuffer buffer(value);
252 const Sink::Entity &entity = buffer.entity(); 264 const Sink::Entity &entity = buffer.entity();
253 const auto metadataBuffer = Sink::EntityBuffer::readBuffer<Sink::Metadata>(entity.metadata()); 265 const auto metadataBuffer = Sink::EntityBuffer::readBuffer<Sink::Metadata>(entity.metadata());
254 if (metadataBuffer && !metadataBuffer->replayToSource()) { 266 if (metadataBuffer && !metadataBuffer->replayToSource()) {
@@ -268,10 +280,19 @@ KAsync::Job<void> MaildirResource::replay(Sink::Storage &synchronizationStore, c
268 } 280 }
269 const auto parentFolderPath = parentFolderRemoteId; 281 const auto parentFolderPath = parentFolderRemoteId;
270 KPIM::Maildir maildir(parentFolderPath, false); 282 KPIM::Maildir maildir(parentFolderPath, false);
271 //FIXME assemble the MIME message 283 if (!maildir.isValid(true)) {
272 const auto id = maildir.addEntry("foobar"); 284 return KAsync::error<void>(1, "Invalid folder " + parentFolderPath);
273 Trace() << "Creating a new mail: " << id; 285 }
274 recordRemoteId(ENTITY_TYPE_MAIL, mail.identifier(), id.toUtf8(), synchronizationTransaction); 286 //FIXME move the mime message from the mimeMessage property to the proper place.
287 Trace() << "Creating a new mail.";
288 const auto remoteId = maildir.addEntry("foobar");
289 if (remoteId.isEmpty()) {
290 Warning() << "Failed to create mail: " << remoteId;
291 return KAsync::error<void>(1, "Failed to create mail.");
292 } else {
293 Trace() << "Mail created: " << remoteId;
294 recordRemoteId(ENTITY_TYPE_MAIL, mail.identifier(), remoteId.toUtf8(), synchronizationTransaction);
295 }
275 } else if (operation == Sink::Operation_Removal) { 296 } else if (operation == Sink::Operation_Removal) {
276 const auto uid = Sink::Storage::uidFromKey(key); 297 const auto uid = Sink::Storage::uidFromKey(key);
277 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, uid, synchronizationTransaction); 298 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, uid, synchronizationTransaction);
@@ -279,7 +300,26 @@ KAsync::Job<void> MaildirResource::replay(Sink::Storage &synchronizationStore, c
279 QFile::remove(remoteId); 300 QFile::remove(remoteId);
280 removeRemoteId(ENTITY_TYPE_MAIL, uid, remoteId, synchronizationTransaction); 301 removeRemoteId(ENTITY_TYPE_MAIL, uid, remoteId, synchronizationTransaction);
281 } else if (operation == Sink::Operation_Modification) { 302 } else if (operation == Sink::Operation_Modification) {
282 Warning() << "Mail modifications are not implemented"; 303 const auto uid = Sink::Storage::uidFromKey(key);
304 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, uid, synchronizationTransaction);
305 Trace() << "Modifying a mail: " << remoteId;
306 auto parts = remoteId.split('/');
307 const auto filename = parts.takeLast(); //filename
308 parts.removeLast(); //cur/new folder
309 auto maildirPath = parts.join('/');
310
311 KPIM::Maildir maildir(maildirPath, false);
312
313 const Sink::ApplicationDomain::Mail mail(mResourceInstanceIdentifier, Sink::Storage::uidFromKey(key), revision, mMailAdaptorFactory->createAdaptor(entity));
314
315 //get flags from
316 KPIM::Maildir::Flags flags;
317 if (!mail.getProperty("unread").toBool()) {
318 flags |= KPIM::Maildir::Seen;
319 }
320
321 auto newRemoteId = maildir.changeEntryFlags(filename, flags);
322 updateRemoteId(ENTITY_TYPE_MAIL, uid, QString(maildirPath + "/cur/" + newRemoteId).toUtf8(), synchronizationTransaction);
283 } else { 323 } else {
284 Warning() << "Unkown operation" << operation; 324 Warning() << "Unkown operation" << operation;
285 } 325 }
@@ -299,20 +339,32 @@ KAsync::Job<void> MaildirResource::inspect(int inspectionType, const QByteArray
299 auto synchronizationTransaction = synchronizationStore->createTransaction(Sink::Storage::ReadOnly); 339 auto synchronizationTransaction = synchronizationStore->createTransaction(Sink::Storage::ReadOnly);
300 Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; 340 Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue;
301 if (domainType == ENTITY_TYPE_MAIL) { 341 if (domainType == ENTITY_TYPE_MAIL) {
302 if (inspectionType == Sink::Resources::Inspection::PropertyInspectionType) { 342 if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) {
303 if (property == "unread") { 343 if (property == "unread") {
304 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, entityId, synchronizationTransaction); 344 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, entityId, synchronizationTransaction);
305 const auto flags = KPIM::Maildir::readEntryFlags(remoteId.split('/').last()); 345 const auto flags = KPIM::Maildir::readEntryFlags(remoteId.split('/').last());
306 if (expectedValue.toBool() && !(flags & KPIM::Maildir::Seen)) { 346 if (expectedValue.toBool() && (flags & KPIM::Maildir::Seen)) {
307 return KAsync::error<void>(1, "Expected seen but couldn't find it."); 347 return KAsync::error<void>(1, "Expected unread but couldn't find it.");
308 } 348 }
309 if (!expectedValue.toBool() && (flags & KPIM::Maildir::Seen)) { 349 if (!expectedValue.toBool() && !(flags & KPIM::Maildir::Seen)) {
310 return KAsync::error<void>(1, "Expected seen but couldn't find it."); 350 return KAsync::error<void>(1, "Expected read but couldn't find it.");
351 }
352 return KAsync::null<void>();
353 }
354 if (property == "subject") {
355 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, entityId, synchronizationTransaction);
356
357 KMime::Message *msg = new KMime::Message;
358 msg->setHead(KMime::CRLFtoLF(KPIM::Maildir::readEntryHeadersFromFile(remoteId)));
359 msg->parse();
360
361 if (msg->subject(true)->asUnicodeString() != expectedValue.toString()) {
362 return KAsync::error<void>(1, "Subject not as expected: " + msg->subject(true)->asUnicodeString());
311 } 363 }
312 return KAsync::null<void>(); 364 return KAsync::null<void>();
313 } 365 }
314 } 366 }
315 if (inspectionType == Sink::Resources::Inspection::ExistenceInspectionType) { 367 if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) {
316 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, entityId, synchronizationTransaction); 368 const auto remoteId = resolveLocalId(ENTITY_TYPE_MAIL, entityId, synchronizationTransaction);
317 if (QFileInfo(remoteId).exists() != expectedValue.toBool()) { 369 if (QFileInfo(remoteId).exists() != expectedValue.toBool()) {
318 return KAsync::error<void>(1, "Wrong file existence: " + remoteId); 370 return KAsync::error<void>(1, "Wrong file existence: " + remoteId);
diff --git a/examples/maildirresource/maildirresource.h b/examples/maildirresource/maildirresource.h
index 9af2f39..32eb88c 100644
--- a/examples/maildirresource/maildirresource.h
+++ b/examples/maildirresource/maildirresource.h
@@ -32,12 +32,15 @@ class MaildirMailAdaptorFactory;
32class MaildirFolderAdaptorFactory; 32class MaildirFolderAdaptorFactory;
33 33
34/** 34/**
35 * A maildir resource 35 * A maildir resource.
36 * 36 *
37 * Implementation details: 37 * Implementation details:
38 * The remoteid's have the following formats: 38 * The remoteid's have the following formats:
39 * files: full file path 39 * files: full file path
40 * directories: full directory path 40 * directories: full directory path
41 *
42 * The resource moves all messages from new to cur during sync and thus expectes all messages that are in the store to always reside in cur.
43 * The tmp directory is never directly used
41 */ 44 */
42class MaildirResource : public Sink::GenericResource 45class MaildirResource : public Sink::GenericResource
43{ 46{
diff --git a/hawd_defs/facade_query b/hawd_defs/facade_query
new file mode 100644
index 0000000..8567a5d
--- /dev/null
+++ b/hawd_defs/facade_query
@@ -0,0 +1,8 @@
1{
2 "name": "Read performance",
3 "description": "Measures performance of the query system",
4 "columns": [
5 { "name": "rows", "type": "int" },
6 { "name": "queryTimePerResult", "type": "float", "unit": "result/ms" }
7 ]
8}
diff --git a/mkdocs.yml b/mkdocs.yml
index 21f9471..07299f4 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,14 +1,19 @@
1site_name: Sink 1site_name: Sink
2pages: 2pages:
3- Home: index.md 3- Home: index.md
4- Terminology: terminology.md 4- Design:
5- Overview: design.md 5 - Terminology: terminology.md
6- Resource: resource.md 6 - "Design Goals": designgoals.md
7- Storage: storage.md 7 - Overview: design.md
8- Logging: logging.md 8 - "Client API": clientapi.md
9- "Client API": clientapi.md 9 - "Application Domain Types": applicationdomaintypes.md
10- sinksh: sinksh.md 10 - Queries: queries.md
11- "Application Domain Types": applicationdomaintypes.md 11 - Resource: resource.md
12- "Extending Sink": extending.md 12 - "Store and Indexes": storage.md
13 - Logging: logging.md
14 - "Tradeoffs and Design Decisions": tradeoffs.md
15- Development:
16 - "Extending Sink": extending.md
13- Building: building.md 17- Building: building.md
18- "Sink Shell": sinksh.md
14theme: readthedocs 19theme: readthedocs
diff --git a/sinksh/CMakeLists.txt b/sinksh/CMakeLists.txt
index 1489fb3..3149347 100644
--- a/sinksh/CMakeLists.txt
+++ b/sinksh/CMakeLists.txt
@@ -2,7 +2,6 @@ project(sinksh)
2 2
3find_package(Readline REQUIRED) 3find_package(Readline REQUIRED)
4 4
5
6set(sink_cli_SRCS 5set(sink_cli_SRCS
7 main.cpp 6 main.cpp
8 syntaxtree.cpp 7 syntaxtree.cpp
@@ -24,6 +23,6 @@ set(sink_cli_SRCS
24include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 23include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
25 24
26add_executable(${PROJECT_NAME} ${sink_cli_SRCS}) 25add_executable(${PROJECT_NAME} ${sink_cli_SRCS})
27target_link_libraries(${PROJECT_NAME} Qt5::Core ${Readline_LIBRARY} sinkcommon) 26target_link_libraries(${PROJECT_NAME} Qt5::Core ${Readline_LIBRARY} sink)
28install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 27install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
29 28
diff --git a/sinksh/repl/replStates.cpp b/sinksh/repl/replStates.cpp
index 43b1353..0cd2620 100644
--- a/sinksh/repl/replStates.cpp
+++ b/sinksh/repl/replStates.cpp
@@ -168,4 +168,8 @@ static char *sink_cli_next_tab_complete_match(const char *text, int state)
168 return rl_filename_completion_function(text, state); 168 return rl_filename_completion_function(text, state);
169} 169}
170 170
171//Ignore warning I don't know how to fix in a moc file
172#pragma clang diagnostic push
173#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
171#include "moc_replStates.cpp" 174#include "moc_replStates.cpp"
175#pragma clang diagnostic pop
diff --git a/sinksh/sinksh_utils.cpp b/sinksh/sinksh_utils.cpp
index fa06b34..d5b1c22 100644
--- a/sinksh/sinksh_utils.cpp
+++ b/sinksh/sinksh_utils.cpp
@@ -20,7 +20,7 @@
20 20
21#include "sinksh_utils.h" 21#include "sinksh_utils.h"
22 22
23#include "common/clientapi.h" 23#include "common/store.h"
24 24
25#include "utils.h" 25#include "utils.h"
26 26
diff --git a/sinksh/sinksh_utils.h b/sinksh/sinksh_utils.h
index 457f644..bc4f6e5 100644
--- a/sinksh/sinksh_utils.h
+++ b/sinksh/sinksh_utils.h
@@ -24,7 +24,7 @@
24#include <QSharedPointer> 24#include <QSharedPointer>
25 25
26#include "common/query.h" 26#include "common/query.h"
27#include "common/clientapi.h" 27#include "common/store.h"
28 28
29#include "state.h" 29#include "state.h"
30 30
@@ -47,6 +47,7 @@ QMap<QString, QString> keyValueMapFromArgs(const QStringList &args);
47 */ 47 */
48class StoreBase { 48class StoreBase {
49public: 49public:
50 virtual ~StoreBase() {};
50 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject() = 0; 51 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject() = 0;
51 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier = QByteArray()) = 0; 52 virtual Sink::ApplicationDomain::ApplicationDomainType::Ptr getObject(const QByteArray &resourceInstanceIdentifier, const QByteArray &identifier = QByteArray()) = 0;
52 virtual KAsync::Job<void> create(const Sink::ApplicationDomain::ApplicationDomainType &type) = 0; 53 virtual KAsync::Job<void> create(const Sink::ApplicationDomain::ApplicationDomainType &type) = 0;
diff --git a/sinksh/syntax_modules/core_syntax.cpp b/sinksh/syntax_modules/core_syntax.cpp
index f5b6274..e90d894 100644
--- a/sinksh/syntax_modules/core_syntax.cpp
+++ b/sinksh/syntax_modules/core_syntax.cpp
@@ -25,6 +25,7 @@
25#include "state.h" 25#include "state.h"
26#include "syntaxtree.h" 26#include "syntaxtree.h"
27#include "utils.h" 27#include "utils.h"
28#include "common/log.h"
28 29
29namespace CoreSyntax 30namespace CoreSyntax
30{ 31{
@@ -32,7 +33,6 @@ namespace CoreSyntax
32bool exit(const QStringList &, State &) 33bool exit(const QStringList &, State &)
33{ 34{
34 ::exit(0); 35 ::exit(0);
35 return true;
36} 36}
37 37
38bool showHelp(const QStringList &commands, State &state) 38bool showHelp(const QStringList &commands, State &state)
@@ -164,6 +164,49 @@ bool printLoggingLevel(const QStringList &commands, State &state)
164 return true; 164 return true;
165} 165}
166 166
167bool setLoggingAreas(const QStringList &commands, State &state)
168{
169 if (commands.isEmpty()) {
170 state.printError(QObject::tr("Wrong number of arguments; expected logging areas."));
171 return false;
172 }
173
174 QByteArrayList areas;
175 for (const auto &c : commands) {
176 areas << c.toLatin1();
177 }
178
179 Sink::Log::setDebugOutputFilter(Sink::Log::Area, areas);
180 return true;
181}
182
183bool setLoggingFilter(const QStringList &commands, State &state)
184{
185 if (commands.isEmpty()) {
186 state.printError(QObject::tr("Wrong number of arguments; expected resource identifier or application names."));
187 return false;
188 }
189
190 QByteArrayList filter;
191 for (const auto &c : commands) {
192 filter << c.toLatin1();
193 }
194
195 Sink::Log::setDebugOutputFilter(Sink::Log::ApplicationName, filter);
196 return true;
197}
198
199bool setLoggingFields(const QStringList &commands, State &state)
200{
201 QByteArrayList output;
202 for (const auto &c : commands) {
203 output << c.toLatin1();
204 }
205
206 Sink::Log::setDebugOutputFields(output);
207 return true;
208}
209
167Syntax::List syntax() 210Syntax::List syntax()
168{ 211{
169 Syntax::List syntax; 212 Syntax::List syntax;
@@ -187,6 +230,16 @@ Syntax::List syntax()
187 logging.completer = [](const QStringList &, const QString &fragment, State &state) -> QStringList { return Utils::filteredCompletions(QStringList() << "trace" << "log" << "warning" << "error", fragment, Qt::CaseInsensitive); }; 230 logging.completer = [](const QStringList &, const QString &fragment, State &state) -> QStringList { return Utils::filteredCompletions(QStringList() << "trace" << "log" << "warning" << "error", fragment, Qt::CaseInsensitive); };
188 set.children << logging; 231 set.children << logging;
189 232
233 Syntax loggingAreas("loggingAreas", QObject::tr("Set logging areas."), &CoreSyntax::setLoggingAreas);
234 set.children << loggingAreas;
235
236 Syntax loggingFilter("loggingFilter", QObject::tr("Set logging filter."), &CoreSyntax::setLoggingFilter);
237 set.children << loggingFilter;
238
239 Syntax loggingFields("loggingFields", QObject::tr("Set logging fields."), &CoreSyntax::setLoggingFields);
240 loggingFields.completer = [](const QStringList &, const QString &fragment, State &state) -> QStringList { return Utils::filteredCompletions(QStringList() << "name" << "function" << "location" << "", fragment, Qt::CaseInsensitive); };
241 set.children << loggingFields;
242
190 syntax << set; 243 syntax << set;
191 244
192 Syntax get("get", QObject::tr("Gets settings for the session")); 245 Syntax get("get", QObject::tr("Gets settings for the session"));
diff --git a/sinksh/syntax_modules/sink_clear.cpp b/sinksh/syntax_modules/sink_clear.cpp
index d02c638..72d9a14 100644
--- a/sinksh/syntax_modules/sink_clear.cpp
+++ b/sinksh/syntax_modules/sink_clear.cpp
@@ -41,7 +41,7 @@ bool clear(const QStringList &args, State &state)
41{ 41{
42 for (const auto &resource : args) { 42 for (const auto &resource : args) {
43 state.print(QObject::tr("Removing local cache for '%1' ...").arg(resource)); 43 state.print(QObject::tr("Removing local cache for '%1' ...").arg(resource));
44 Sink::Store::removeFromDisk(resource.toLatin1()); 44 Sink::Store::removeDataFromDisk(resource.toLatin1()).exec().waitForFinished();
45 state.printLine(QObject::tr("done")); 45 state.printLine(QObject::tr("done"));
46 } 46 }
47 47
diff --git a/synchronizer/CMakeLists.txt b/synchronizer/CMakeLists.txt
index f2802a4..9e9d8d4 100644
--- a/synchronizer/CMakeLists.txt
+++ b/synchronizer/CMakeLists.txt
@@ -7,6 +7,6 @@ set(sinksynchronizer_SRCS
7) 7)
8 8
9add_executable(${PROJECT_NAME} ${sinksynchronizer_SRCS}) 9add_executable(${PROJECT_NAME} ${sinksynchronizer_SRCS})
10target_link_libraries(${PROJECT_NAME} sinkcommon KF5::Async) 10target_link_libraries(${PROJECT_NAME} sink KF5::Async)
11qt5_use_modules(${PROJECT_NAME} Widgets Network) 11qt5_use_modules(${PROJECT_NAME} Widgets Network)
12install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 12install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/synchronizer/main.cpp b/synchronizer/main.cpp
index e9c17e7..d7a18e2 100644
--- a/synchronizer/main.cpp
+++ b/synchronizer/main.cpp
@@ -26,6 +26,9 @@
26#include "listener.h" 26#include "listener.h"
27#include "log.h" 27#include "log.h"
28 28
29#undef DEBUG_AREA
30#define DEBUG_AREA "resource"
31
29void crashHandler(int sig) { 32void crashHandler(int sig) {
30 std::fprintf(stderr, "Error: signal %d\n", sig); 33 std::fprintf(stderr, "Error: signal %d\n", sig);
31 34
@@ -70,6 +73,7 @@ int main(int argc, char *argv[])
70 } 73 }
71 74
72 const QByteArray instanceIdentifier = argv[1]; 75 const QByteArray instanceIdentifier = argv[1];
76 app.setApplicationName(instanceIdentifier);
73 77
74 QLockFile lockfile(instanceIdentifier + ".lock"); 78 QLockFile lockfile(instanceIdentifier + ".lock");
75 lockfile.setStaleLockTime(500); 79 lockfile.setStaleLockTime(500);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b01a329..b8ee5a5 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -15,7 +15,7 @@ macro(manual_tests)
15 add_executable(${_testname} ${_testname}.cpp testimplementations.cpp getrssusage.cpp) 15 add_executable(${_testname} ${_testname}.cpp testimplementations.cpp getrssusage.cpp)
16 generate_flatbuffers(${_testname} calendar) 16 generate_flatbuffers(${_testname} calendar)
17 qt5_use_modules(${_testname} Core Test Concurrent) 17 qt5_use_modules(${_testname} Core Test Concurrent)
18 target_link_libraries(${_testname} sinkcommon libhawd) 18 target_link_libraries(${_testname} sink libhawd)
19 endforeach(_testname) 19 endforeach(_testname)
20endmacro(manual_tests) 20endmacro(manual_tests)
21 21
@@ -25,7 +25,7 @@ macro(auto_tests)
25 generate_flatbuffers(${_testname} calendar) 25 generate_flatbuffers(${_testname} calendar)
26 add_test(${_testname} ${_testname}) 26 add_test(${_testname} ${_testname})
27 qt5_use_modules(${_testname} Core Test Concurrent) 27 qt5_use_modules(${_testname} Core Test Concurrent)
28 target_link_libraries(${_testname} sinkcommon libhawd) 28 target_link_libraries(${_testname} sink libhawd)
29 endforeach(_testname) 29 endforeach(_testname)
30endmacro(auto_tests) 30endmacro(auto_tests)
31 31
diff --git a/tests/clientapitest.cpp b/tests/clientapitest.cpp
index a85b03b..0e21ca6 100644
--- a/tests/clientapitest.cpp
+++ b/tests/clientapitest.cpp
@@ -2,7 +2,7 @@
2#include <QDebug> 2#include <QDebug>
3#include <functional> 3#include <functional>
4 4
5#include "clientapi.h" 5#include "store.h"
6#include "facade.h" 6#include "facade.h"
7#include "resourceconfig.h" 7#include "resourceconfig.h"
8#include "modelresult.h" 8#include "modelresult.h"
diff --git a/tests/databasepopulationandfacadequerybenchmark.cpp b/tests/databasepopulationandfacadequerybenchmark.cpp
index 8581c49..63daebc 100644
--- a/tests/databasepopulationandfacadequerybenchmark.cpp
+++ b/tests/databasepopulationandfacadequerybenchmark.cpp
@@ -10,7 +10,10 @@
10#include <common/synclistresult.h> 10#include <common/synclistresult.h>
11#include <common/definitions.h> 11#include <common/definitions.h>
12#include <common/query.h> 12#include <common/query.h>
13#include <common/clientapi.h> 13#include <common/store.h>
14
15#include "hawd/dataset.h"
16#include "hawd/formatter.h"
14 17
15#include <iostream> 18#include <iostream>
16#include <math.h> 19#include <math.h>
@@ -31,6 +34,7 @@ class DatabasePopulationAndFacadeQueryBenchmark : public QObject
31 QByteArray identifier; 34 QByteArray identifier;
32 QList<double> mRssGrowthPerEntity; 35 QList<double> mRssGrowthPerEntity;
33 QList<double> mTimePerEntity; 36 QList<double> mTimePerEntity;
37 HAWD::State mHawdState;
34 38
35 void populateDatabase(int count) 39 void populateDatabase(int count)
36 { 40 {
@@ -40,7 +44,7 @@ class DatabasePopulationAndFacadeQueryBenchmark : public QObject
40 { 44 {
41 Sink::Storage storage(Sink::storageLocation(), identifier, Sink::Storage::ReadWrite); 45 Sink::Storage storage(Sink::storageLocation(), identifier, Sink::Storage::ReadWrite);
42 auto transaction = storage.createTransaction(Sink::Storage::ReadWrite); 46 auto transaction = storage.createTransaction(Sink::Storage::ReadWrite);
43 auto db = transaction.openDatabase("event.main"); 47 auto db = Sink::Storage::mainDatabase(transaction, "event");
44 48
45 int bufferSizeTotal = 0; 49 int bufferSizeTotal = 0;
46 int keysSizeTotal = 0; 50 int keysSizeTotal = 0;
@@ -129,6 +133,13 @@ class DatabasePopulationAndFacadeQueryBenchmark : public QObject
129 std::cout << "Rss without db [kb]: " << rssWithoutDb/1024 << std::endl; 133 std::cout << "Rss without db [kb]: " << rssWithoutDb/1024 << std::endl;
130 std::cout << "Percentage error: " << percentageRssError << std::endl; 134 std::cout << "Percentage error: " << percentageRssError << std::endl;
131 135
136 HAWD::Dataset dataset("facade_query", mHawdState);
137 HAWD::Dataset::Row row = dataset.row();
138 row.setValue("rows", list.size());
139 row.setValue("queryTimePerResult", (qreal)list.size()/elapsed);
140 dataset.insertRow(row);
141 HAWD::Formatter::print(dataset);
142
132 mTimePerEntity << static_cast<double>(elapsed)/static_cast<double>(count); 143 mTimePerEntity << static_cast<double>(elapsed)/static_cast<double>(count);
133 mRssGrowthPerEntity << rssGrowthPerEntity; 144 mRssGrowthPerEntity << rssGrowthPerEntity;
134 145
diff --git a/tests/domainadaptortest.cpp b/tests/domainadaptortest.cpp
index ff8f639..f523173 100644
--- a/tests/domainadaptortest.cpp
+++ b/tests/domainadaptortest.cpp
@@ -5,7 +5,7 @@
5#include <QDebug> 5#include <QDebug>
6 6
7#include "dummyresource/resourcefactory.h" 7#include "dummyresource/resourcefactory.h"
8#include "clientapi.h" 8#include "store.h"
9 9
10#include "common/domainadaptor.h" 10#include "common/domainadaptor.h"
11#include "common/entitybuffer.h" 11#include "common/entitybuffer.h"
diff --git a/tests/dummyresourcebenchmark.cpp b/tests/dummyresourcebenchmark.cpp
index e511613..9afc775 100644
--- a/tests/dummyresourcebenchmark.cpp
+++ b/tests/dummyresourcebenchmark.cpp
@@ -4,12 +4,15 @@
4 4
5#include "dummyresource/resourcefactory.h" 5#include "dummyresource/resourcefactory.h"
6#include "dummyresource/domainadaptor.h" 6#include "dummyresource/domainadaptor.h"
7#include "clientapi.h" 7#include "store.h"
8#include "notifier.h"
9#include "resourcecontrol.h"
8#include "commands.h" 10#include "commands.h"
9#include "entitybuffer.h" 11#include "entitybuffer.h"
10#include "pipeline.h" 12#include "pipeline.h"
11#include "log.h" 13#include "log.h"
12#include "resourceconfig.h" 14#include "resourceconfig.h"
15#include "notification_generated.h"
13 16
14#include "hawd/dataset.h" 17#include "hawd/dataset.h"
15#include "hawd/formatter.h" 18#include "hawd/formatter.h"
@@ -71,9 +74,43 @@ private Q_SLOTS:
71 }); 74 });
72 } 75 }
73 76
77 //Ensure we can process a command in less than 0.1s
78 void testCommandResponsiveness()
79 {
80 //Test responsiveness including starting the process.
81 Sink::Store::removeDataFromDisk("org.kde.dummy.instance1").exec().waitForFinished();
82
83 QTime time;
84 time.start();
85
86 Sink::ApplicationDomain::Event event("org.kde.dummy.instance1");
87 event.setProperty("uid", "testuid");
88 QCOMPARE(event.getProperty("uid").toByteArray(), QByteArray("testuid"));
89 event.setProperty("summary", "summaryValue");
90
91 auto notifier = QSharedPointer<Sink::Notifier>::create("org.kde.dummy.instance1");
92 bool gotNotification = false;
93 int duration = 0;
94 notifier->registerHandler([&gotNotification, &duration, &time](const Sink::Notification &notification) {
95 if (notification.type == Sink::Commands::NotificationType::NotificationType_RevisionUpdate) {
96 gotNotification = true;
97 duration = time.elapsed();
98 }
99 });
100
101 Sink::Store::create<Sink::ApplicationDomain::Event>(event).exec();
102
103 //Wait for notification
104 QTRY_VERIFY(gotNotification);
105
106 QVERIFY2(duration < 100, QString::fromLatin1("Processing a create command took more than 100ms: %1").arg(duration).toLatin1());
107 Sink::ResourceControl::shutdown("org.kde.dummy.instance1").exec().waitForFinished();
108 qDebug() << "Single command took [ms]: " << duration;
109 }
110
74 void testWriteToFacade() 111 void testWriteToFacade()
75 { 112 {
76 DummyResource::removeFromDisk("org.kde.dummy.instance1"); 113 Sink::Store::removeDataFromDisk("org.kde.dummy.instance1").exec().waitForFinished();
77 114
78 QTime time; 115 QTime time;
79 time.start(); 116 time.start();
@@ -92,7 +129,7 @@ private Q_SLOTS:
92 { 129 {
93 Sink::Query query; 130 Sink::Query query;
94 query.resources << "org.kde.dummy.instance1"; 131 query.resources << "org.kde.dummy.instance1";
95 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 132 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
96 } 133 }
97 auto allProcessedTime = time.elapsed(); 134 auto allProcessedTime = time.elapsed();
98 135
@@ -138,7 +175,7 @@ private Q_SLOTS:
138 175
139 void testWriteInProcess() 176 void testWriteInProcess()
140 { 177 {
141 DummyResource::removeFromDisk("org.kde.dummy.instance1"); 178 Sink::Store::removeDataFromDisk("org.kde.dummy.instance1").exec().waitForFinished();
142 QTime time; 179 QTime time;
143 time.start(); 180 time.start();
144 181
@@ -226,7 +263,7 @@ private Q_SLOTS:
226 //This allows to run individual parts without doing a cleanup, but still cleaning up normally 263 //This allows to run individual parts without doing a cleanup, but still cleaning up normally
227 void testCleanupForCompleteTest() 264 void testCleanupForCompleteTest()
228 { 265 {
229 DummyResource::removeFromDisk("org.kde.dummy.instance1"); 266 Sink::Store::removeDataFromDisk("org.kde.dummy.instance1").exec().waitForFinished();
230 } 267 }
231 268
232private: 269private:
diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp
index 82c713d..7d9ad24 100644
--- a/tests/dummyresourcetest.cpp
+++ b/tests/dummyresourcetest.cpp
@@ -3,10 +3,11 @@
3#include <QString> 3#include <QString>
4 4
5#include "dummyresource/resourcefactory.h" 5#include "dummyresource/resourcefactory.h"
6#include "clientapi.h" 6#include "store.h"
7#include "commands.h" 7#include "commands.h"
8#include "entitybuffer.h" 8#include "entitybuffer.h"
9#include "resourceconfig.h" 9#include "resourceconfig.h"
10#include "resourcecontrol.h"
10#include "modelresult.h" 11#include "modelresult.h"
11#include "pipeline.h" 12#include "pipeline.h"
12#include "log.h" 13#include "log.h"
@@ -19,6 +20,9 @@
19class DummyResourceTest : public QObject 20class DummyResourceTest : public QObject
20{ 21{
21 Q_OBJECT 22 Q_OBJECT
23
24 QTime time;
25
22private Q_SLOTS: 26private Q_SLOTS:
23 void initTestCase() 27 void initTestCase()
24 { 28 {
@@ -29,20 +33,18 @@ private Q_SLOTS:
29 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy"); 33 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy");
30 } 34 }
31 35
32 void cleanup()
33 {
34 Sink::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
35 DummyResource::removeFromDisk("org.kde.dummy.instance1");
36 auto factory = Sink::ResourceFactory::load("org.kde.dummy");
37 QVERIFY(factory);
38 Sink::Store::start(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
39 }
40
41 void init() 36 void init()
42 { 37 {
43 qDebug(); 38 qDebug();
44 qDebug() << "-----------------------------------------"; 39 qDebug() << "-----------------------------------------";
45 qDebug(); 40 qDebug();
41 time.start();
42 }
43
44 void cleanup()
45 {
46 qDebug() << "Test took " << time.elapsed();
47 Sink::Store::removeDataFromDisk(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
46 } 48 }
47 49
48 void testProperty() 50 void testProperty()
@@ -64,7 +66,7 @@ private Q_SLOTS:
64 query.resources << "org.kde.dummy.instance1"; 66 query.resources << "org.kde.dummy.instance1";
65 67
66 //Ensure all local data is processed 68 //Ensure all local data is processed
67 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 69 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
68 70
69 query.propertyFilter.insert("uid", "testuid"); 71 query.propertyFilter.insert("uid", "testuid");
70 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query); 72 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query);
@@ -88,7 +90,7 @@ private Q_SLOTS:
88 query.resources << "org.kde.dummy.instance1"; 90 query.resources << "org.kde.dummy.instance1";
89 91
90 //Ensure all local data is processed 92 //Ensure all local data is processed
91 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 93 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
92 94
93 query.propertyFilter.insert("uid", "testuid"); 95 query.propertyFilter.insert("uid", "testuid");
94 96
@@ -116,7 +118,7 @@ private Q_SLOTS:
116 query.resources << "org.kde.dummy.instance1"; 118 query.resources << "org.kde.dummy.instance1";
117 119
118 //Ensure all local data is processed 120 //Ensure all local data is processed
119 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 121 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
120 122
121 query.propertyFilter.insert("summary", "summaryValue2"); 123 query.propertyFilter.insert("summary", "summaryValue2");
122 124
@@ -150,7 +152,7 @@ private Q_SLOTS:
150 152
151 //Ensure all local data is processed 153 //Ensure all local data is processed
152 Sink::Store::synchronize(query).exec().waitForFinished(); 154 Sink::Store::synchronize(query).exec().waitForFinished();
153 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 155 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
154 156
155 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query); 157 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query);
156 QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1); 158 QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1);
@@ -167,7 +169,7 @@ private Q_SLOTS:
167 169
168 //Ensure all local data is processed 170 //Ensure all local data is processed
169 Sink::Store::synchronize(query).exec().waitForFinished(); 171 Sink::Store::synchronize(query).exec().waitForFinished();
170 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 172 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
171 173
172 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 174 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
173 QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1); 175 QTRY_VERIFY(model->rowCount(QModelIndex()) >= 1);
@@ -190,7 +192,7 @@ private Q_SLOTS:
190 query.propertyFilter.insert("uid", "testuid"); 192 query.propertyFilter.insert("uid", "testuid");
191 193
192 //Ensure all local data is processed 194 //Ensure all local data is processed
193 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 195 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
194 196
195 //Test create 197 //Test create
196 Sink::ApplicationDomain::Event event2; 198 Sink::ApplicationDomain::Event event2;
@@ -209,7 +211,7 @@ private Q_SLOTS:
209 Sink::Store::modify<Sink::ApplicationDomain::Event>(event2).exec().waitForFinished(); 211 Sink::Store::modify<Sink::ApplicationDomain::Event>(event2).exec().waitForFinished();
210 212
211 //Ensure all local data is processed 213 //Ensure all local data is processed
212 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 214 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
213 215
214 //Test modify 216 //Test modify
215 { 217 {
@@ -224,7 +226,7 @@ private Q_SLOTS:
224 Sink::Store::remove<Sink::ApplicationDomain::Event>(event2).exec().waitForFinished(); 226 Sink::Store::remove<Sink::ApplicationDomain::Event>(event2).exec().waitForFinished();
225 227
226 //Ensure all local data is processed 228 //Ensure all local data is processed
227 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 229 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
228 230
229 //Test remove 231 //Test remove
230 { 232 {
diff --git a/tests/dummyresourcewritebenchmark.cpp b/tests/dummyresourcewritebenchmark.cpp
index 1a8893b..6c05a1e 100644
--- a/tests/dummyresourcewritebenchmark.cpp
+++ b/tests/dummyresourcewritebenchmark.cpp
@@ -6,7 +6,7 @@
6 6
7#include "dummyresource/resourcefactory.h" 7#include "dummyresource/resourcefactory.h"
8#include "dummyresource/domainadaptor.h" 8#include "dummyresource/domainadaptor.h"
9#include "clientapi.h" 9#include "store.h"
10#include "commands.h" 10#include "commands.h"
11#include "entitybuffer.h" 11#include "entitybuffer.h"
12#include "pipeline.h" 12#include "pipeline.h"
@@ -120,6 +120,7 @@ class DummyResourceWriteBenchmark : public QObject
120 resource.processCommand(Sink::Commands::CreateEntityCommand, command); 120 resource.processCommand(Sink::Commands::CreateEntityCommand, command);
121 } 121 }
122 auto appendTime = time.elapsed(); 122 auto appendTime = time.elapsed();
123 Q_UNUSED(appendTime);
123 auto bufferSizeTotal = bufferSize * num; 124 auto bufferSizeTotal = bufferSize * num;
124 125
125 //Wait until all messages have been processed 126 //Wait until all messages have been processed
diff --git a/tests/hawd/CMakeLists.txt b/tests/hawd/CMakeLists.txt
index 268e7a9..d754726 100644
--- a/tests/hawd/CMakeLists.txt
+++ b/tests/hawd/CMakeLists.txt
@@ -26,7 +26,7 @@ set(SRCS
26add_library(lib${PROJECT_NAME} SHARED ${lib_SRCS}) 26add_library(lib${PROJECT_NAME} SHARED ${lib_SRCS})
27generate_export_header(lib${PROJECT_NAME} BASE_NAME HAWD EXPORT_FILE_NAME hawd_export.h) 27generate_export_header(lib${PROJECT_NAME} BASE_NAME HAWD EXPORT_FILE_NAME hawd_export.h)
28qt5_use_modules(lib${PROJECT_NAME} Core) 28qt5_use_modules(lib${PROJECT_NAME} Core)
29target_link_libraries(lib${PROJECT_NAME} sinkcommon) 29target_link_libraries(lib${PROJECT_NAME} sink)
30if (LIBGIT2_FOUND) 30if (LIBGIT2_FOUND)
31 target_link_libraries(lib${PROJECT_NAME} ${LIBGIT2_LIBRARIES}) 31 target_link_libraries(lib${PROJECT_NAME} ${LIBGIT2_LIBRARIES})
32endif(LIBGIT2_FOUND) 32endif(LIBGIT2_FOUND)
diff --git a/tests/hawd/dataset.cpp b/tests/hawd/dataset.cpp
index fd931e8..372a4b6 100644
--- a/tests/hawd/dataset.cpp
+++ b/tests/hawd/dataset.cpp
@@ -216,8 +216,8 @@ QString Dataset::Row::toString(const QStringList &cols, int standardCols, const
216Dataset::Dataset(const QString &name, const State &state) 216Dataset::Dataset(const QString &name, const State &state)
217 : m_definition(state.datasetDefinition(name)), 217 : m_definition(state.datasetDefinition(name)),
218 m_storage(state.resultsPath(), name, Sink::Storage::ReadWrite), 218 m_storage(state.resultsPath(), name, Sink::Storage::ReadWrite),
219 m_commitHash(state.commitHash()), 219 m_transaction(std::move(m_storage.createTransaction())),
220 m_transaction(std::move(m_storage.createTransaction())) 220 m_commitHash(state.commitHash())
221{ 221{
222} 222}
223 223
diff --git a/tests/indextest.cpp b/tests/indextest.cpp
index 7ec95ce..a435e97 100644
--- a/tests/indextest.cpp
+++ b/tests/indextest.cpp
@@ -3,7 +3,7 @@
3#include <QString> 3#include <QString>
4#include <QQueue> 4#include <QQueue>
5 5
6#include "clientapi.h" 6#include "store.h"
7#include "storage.h" 7#include "storage.h"
8#include "index.h" 8#include "index.h"
9 9
diff --git a/tests/inspectiontest.cpp b/tests/inspectiontest.cpp
index c876aa9..79e5863 100644
--- a/tests/inspectiontest.cpp
+++ b/tests/inspectiontest.cpp
@@ -3,7 +3,8 @@
3#include <QString> 3#include <QString>
4 4
5#include "dummyresource/resourcefactory.h" 5#include "dummyresource/resourcefactory.h"
6#include "clientapi.h" 6#include "store.h"
7#include "resourcecontrol.h"
7#include "resourceconfig.h" 8#include "resourceconfig.h"
8#include "log.h" 9#include "log.h"
9 10
@@ -21,17 +22,8 @@ private Q_SLOTS:
21 Sink::Log::setDebugOutputLevel(Sink::Log::Trace); 22 Sink::Log::setDebugOutputLevel(Sink::Log::Trace);
22 auto factory = Sink::ResourceFactory::load("org.kde.dummy"); 23 auto factory = Sink::ResourceFactory::load("org.kde.dummy");
23 QVERIFY(factory); 24 QVERIFY(factory);
24 DummyResource::removeFromDisk("org.kde.dummy.instance1");
25 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy"); 25 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy");
26 } 26 Sink::Store::removeDataFromDisk(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
27
28 void cleanup()
29 {
30 Sink::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
31 DummyResource::removeFromDisk("org.kde.dummy.instance1");
32 auto factory = Sink::ResourceFactory::load("org.kde.dummy");
33 QVERIFY(factory);
34 Sink::Store::start(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
35 } 27 }
36 28
37 void testInspection_data() 29 void testInspection_data()
@@ -50,8 +42,8 @@ private Q_SLOTS:
50 Mail mail(QByteArray("org.kde.dummy.instance1"), QByteArray("identifier"), 0, QSharedPointer<MemoryBufferAdaptor::MemoryBufferAdaptor>::create()); 42 Mail mail(QByteArray("org.kde.dummy.instance1"), QByteArray("identifier"), 0, QSharedPointer<MemoryBufferAdaptor::MemoryBufferAdaptor>::create());
51 43
52 //testInspection is a magic property that the dummyresource supports 44 //testInspection is a magic property that the dummyresource supports
53 auto inspectionCommand = Resources::Inspection::PropertyInspection(mail, "testInspection", success); 45 auto inspectionCommand = ResourceControl::Inspection::PropertyInspection(mail, "testInspection", success);
54 auto result = Resources::inspect<Mail>(inspectionCommand).exec(); 46 auto result = ResourceControl::inspect<Mail>(inspectionCommand).exec();
55 result.waitForFinished(); 47 result.waitForFinished();
56 if (success) { 48 if (success) {
57 QVERIFY(!result.errorCode()); 49 QVERIFY(!result.errorCode());
diff --git a/tests/maildirresourcetest.cpp b/tests/maildirresourcetest.cpp
index 6ad6ca6..28a1d44 100644
--- a/tests/maildirresourcetest.cpp
+++ b/tests/maildirresourcetest.cpp
@@ -3,7 +3,8 @@
3#include <QString> 3#include <QString>
4 4
5#include "maildirresource/maildirresource.h" 5#include "maildirresource/maildirresource.h"
6#include "clientapi.h" 6#include "store.h"
7#include "resourcecontrol.h"
7#include "commands.h" 8#include "commands.h"
8#include "entitybuffer.h" 9#include "entitybuffer.h"
9#include "resourceconfig.h" 10#include "resourceconfig.h"
@@ -68,7 +69,7 @@ private Q_SLOTS:
68 69
69 void cleanup() 70 void cleanup()
70 { 71 {
71 Sink::Store::shutdown(QByteArray("org.kde.maildir.instance1")).exec().waitForFinished(); 72 Sink::ResourceControl::shutdown(QByteArray("org.kde.maildir.instance1")).exec().waitForFinished();
72 MaildirResource::removeFromDisk("org.kde.maildir.instance1"); 73 MaildirResource::removeFromDisk("org.kde.maildir.instance1");
73 QDir dir(targetPath); 74 QDir dir(targetPath);
74 dir.removeRecursively(); 75 dir.removeRecursively();
@@ -80,7 +81,7 @@ private Q_SLOTS:
80 qDebug() << "-----------------------------------------"; 81 qDebug() << "-----------------------------------------";
81 qDebug(); 82 qDebug();
82 copyRecursively(TESTDATAPATH "/maildir1", targetPath); 83 copyRecursively(TESTDATAPATH "/maildir1", targetPath);
83 Sink::Store::start(QByteArray("org.kde.maildir.instance1")).exec().waitForFinished(); 84 Sink::ResourceControl::start(QByteArray("org.kde.maildir.instance1")).exec().waitForFinished();
84 } 85 }
85 86
86 void testListFolders() 87 void testListFolders()
@@ -90,7 +91,7 @@ private Q_SLOTS:
90 91
91 //Ensure all local data is processed 92 //Ensure all local data is processed
92 Sink::Store::synchronize(query).exec().waitForFinished(); 93 Sink::Store::synchronize(query).exec().waitForFinished();
93 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 94 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
94 95
95 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query); 96 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
96 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 97 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -105,7 +106,7 @@ private Q_SLOTS:
105 106
106 //Ensure all local data is processed 107 //Ensure all local data is processed
107 Sink::Store::synchronize(query).exec().waitForFinished(); 108 Sink::Store::synchronize(query).exec().waitForFinished();
108 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 109 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
109 110
110 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query); 111 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
111 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 112 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -123,7 +124,7 @@ private Q_SLOTS:
123 //Ensure all local data is processed 124 //Ensure all local data is processed
124 auto query = Query::ResourceFilter("org.kde.maildir.instance1"); 125 auto query = Query::ResourceFilter("org.kde.maildir.instance1");
125 Store::synchronize(query).exec().waitForFinished(); 126 Store::synchronize(query).exec().waitForFinished();
126 Store::flushMessageQueue(query.resources).exec().waitForFinished(); 127 ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
127 auto result = Store::fetchOne<Folder>( 128 auto result = Store::fetchOne<Folder>(
128 Query::ResourceFilter("org.kde.maildir.instance1") + Query::RequestedProperties(QByteArrayList() << "name") 129 Query::ResourceFilter("org.kde.maildir.instance1") + Query::RequestedProperties(QByteArrayList() << "name")
129 ) 130 )
@@ -149,7 +150,7 @@ private Q_SLOTS:
149 150
150 //Ensure all local data is processed 151 //Ensure all local data is processed
151 Sink::Store::synchronize(query).exec().waitForFinished(); 152 Sink::Store::synchronize(query).exec().waitForFinished();
152 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 153 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
153 154
154 auto mailModel = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 155 auto mailModel = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
155 QTRY_VERIFY(mailModel->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 156 QTRY_VERIFY(mailModel->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -158,6 +159,9 @@ private Q_SLOTS:
158 QVERIFY(!mail->getProperty("subject").toString().isEmpty()); 159 QVERIFY(!mail->getProperty("subject").toString().isEmpty());
159 QVERIFY(!mail->getProperty("mimeMessage").toString().isEmpty()); 160 QVERIFY(!mail->getProperty("mimeMessage").toString().isEmpty());
160 QVERIFY(mail->getProperty("date").toDateTime().isValid()); 161 QVERIFY(mail->getProperty("date").toDateTime().isValid());
162
163 QFileInfo info(mail->getProperty("mimeMessage").toString());
164 QVERIFY(info.exists());
161 } 165 }
162 166
163 167
@@ -169,7 +173,7 @@ private Q_SLOTS:
169 173
170 //Ensure all local data is processed 174 //Ensure all local data is processed
171 Sink::Store::synchronize(query).exec().waitForFinished(); 175 Sink::Store::synchronize(query).exec().waitForFinished();
172 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 176 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
173 177
174 auto targetPath = tempDir.path() + "/maildir1/"; 178 auto targetPath = tempDir.path() + "/maildir1/";
175 QDir dir(targetPath); 179 QDir dir(targetPath);
@@ -177,7 +181,7 @@ private Q_SLOTS:
177 181
178 //Ensure all local data is processed 182 //Ensure all local data is processed
179 Sink::Store::synchronize(query).exec().waitForFinished(); 183 Sink::Store::synchronize(query).exec().waitForFinished();
180 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 184 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
181 185
182 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query); 186 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
183 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 187 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -193,11 +197,11 @@ private Q_SLOTS:
193 197
194 //Ensure all local data is processed 198 //Ensure all local data is processed
195 Sink::Store::synchronize(query).exec().waitForFinished(); 199 Sink::Store::synchronize(query).exec().waitForFinished();
196 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 200 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
197 201
198 //Ensure all local data is processed 202 //Ensure all local data is processed
199 Sink::Store::synchronize(query).exec().waitForFinished(); 203 Sink::Store::synchronize(query).exec().waitForFinished();
200 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 204 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
201 205
202 auto mailModel = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 206 auto mailModel = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
203 QTRY_VERIFY(mailModel->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 207 QTRY_VERIFY(mailModel->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -212,7 +216,7 @@ private Q_SLOTS:
212 216
213 //Ensure all local data is processed 217 //Ensure all local data is processed
214 Sink::Store::synchronize(query).exec().waitForFinished(); 218 Sink::Store::synchronize(query).exec().waitForFinished();
215 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 219 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
216 220
217 auto targetPath = tempDir.path() + "/maildir1/cur/1365777830.R28.localhost.localdomain:2,S"; 221 auto targetPath = tempDir.path() + "/maildir1/cur/1365777830.R28.localhost.localdomain:2,S";
218 QFile file(targetPath); 222 QFile file(targetPath);
@@ -220,7 +224,7 @@ private Q_SLOTS:
220 224
221 //Ensure all local data is processed 225 //Ensure all local data is processed
222 Sink::Store::synchronize(query).exec().waitForFinished(); 226 Sink::Store::synchronize(query).exec().waitForFinished();
223 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 227 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
224 228
225 auto mailModel = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 229 auto mailModel = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
226 QTRY_VERIFY(mailModel->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 230 QTRY_VERIFY(mailModel->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -233,7 +237,7 @@ private Q_SLOTS:
233 query.resources << "org.kde.maildir.instance1"; 237 query.resources << "org.kde.maildir.instance1";
234 238
235 //Ensure all local data is processed 239 //Ensure all local data is processed
236 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 240 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
237 241
238 Sink::ApplicationDomain::Folder folder("org.kde.maildir.instance1"); 242 Sink::ApplicationDomain::Folder folder("org.kde.maildir.instance1");
239 folder.setProperty("name", "testCreateFolder"); 243 folder.setProperty("name", "testCreateFolder");
@@ -241,7 +245,7 @@ private Q_SLOTS:
241 Sink::Store::create(folder).exec().waitForFinished(); 245 Sink::Store::create(folder).exec().waitForFinished();
242 246
243 //Ensure all local data is processed 247 //Ensure all local data is processed
244 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 248 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
245 249
246 auto targetPath = tempDir.path() + "/maildir1/testCreateFolder"; 250 auto targetPath = tempDir.path() + "/maildir1/testCreateFolder";
247 QFileInfo file(targetPath); 251 QFileInfo file(targetPath);
@@ -259,7 +263,7 @@ private Q_SLOTS:
259 Sink::ApplicationDomain::Folder folder("org.kde.maildir.instance1"); 263 Sink::ApplicationDomain::Folder folder("org.kde.maildir.instance1");
260 folder.setProperty("name", "testCreateFolder"); 264 folder.setProperty("name", "testCreateFolder");
261 Sink::Store::create(folder).exec().waitForFinished(); 265 Sink::Store::create(folder).exec().waitForFinished();
262 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 266 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
263 QTRY_VERIFY(QFileInfo(targetPath).exists()); 267 QTRY_VERIFY(QFileInfo(targetPath).exists());
264 268
265 Sink::Query folderQuery; 269 Sink::Query folderQuery;
@@ -271,7 +275,7 @@ private Q_SLOTS:
271 auto createdFolder = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>(); 275 auto createdFolder = model->index(0, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>();
272 276
273 Sink::Store::remove(*createdFolder).exec().waitForFinished(); 277 Sink::Store::remove(*createdFolder).exec().waitForFinished();
274 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 278 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
275 QTRY_VERIFY(!QFileInfo(targetPath).exists()); 279 QTRY_VERIFY(!QFileInfo(targetPath).exists());
276 } 280 }
277 281
@@ -281,15 +285,16 @@ private Q_SLOTS:
281 query.resources << "org.kde.maildir.instance1"; 285 query.resources << "org.kde.maildir.instance1";
282 286
283 //Ensure all local data is processed 287 //Ensure all local data is processed
284 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 288 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
285 289
286 Sink::ApplicationDomain::Mail mail("org.kde.maildir.instance1"); 290 Sink::ApplicationDomain::Mail mail("org.kde.maildir.instance1");
287 mail.setProperty("name", "testCreateMail"); 291 mail.setProperty("name", "testCreateMail");
292 //FIXME instead of properties, ensure the mimeMessage property is used and the file is moved as expected
288 293
289 Sink::Store::create(mail).exec().waitForFinished(); 294 Sink::Store::create(mail).exec().waitForFinished();
290 295
291 //Ensure all local data is processed 296 //Ensure all local data is processed
292 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 297 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
293 298
294 auto targetPath = tempDir.path() + "/maildir1/new"; 299 auto targetPath = tempDir.path() + "/maildir1/new";
295 QDir dir(targetPath); 300 QDir dir(targetPath);
@@ -304,29 +309,28 @@ private Q_SLOTS:
304 309
305 auto query = Query::ResourceFilter("org.kde.maildir.instance1"); 310 auto query = Query::ResourceFilter("org.kde.maildir.instance1");
306 Store::synchronize(query).exec().waitForFinished(); 311 Store::synchronize(query).exec().waitForFinished();
307 Store::flushMessageQueue(query.resources).exec().waitForFinished(); 312 ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
308 313
309 auto result = Store::fetchOne<Folder>( 314 auto result = Store::fetchOne<Folder>(
310 Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("name", "maildir1") + Query::RequestedProperties(QByteArrayList() << "name") 315 Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("name", "maildir1") + Query::RequestedProperties(QByteArrayList() << "name")
316 )
317 .then<void, KAsync::Job<void>, Folder>([query](const Folder &folder) {
318 return Store::fetchAll<Mail>(
319 Query::PropertyFilter("folder", folder) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject")
311 ) 320 )
312 .then<void, KAsync::Job<void>, Folder>([query](const Folder &folder) { 321 .then<void, KAsync::Job<void>, QList<Mail::Ptr> >([query](const QList<Mail::Ptr> &mails) {
313 return Store::fetchAll<Mail>( 322 //Can't use QCOMPARE because it tries to return FIXME Implement ASYNCCOMPARE
314 Query::PropertyFilter("folder", folder) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject") 323 if (mails.size() != 1) {
315 ) 324 return KAsync::error<void>(1, "Wrong number of mails.");
316 .then<void, KAsync::Job<void>, QList<Mail::Ptr> >([query](const QList<Mail::Ptr> &mails) { 325 }
317 //Can't use QCOMPARE because it tries to return FIXME Implement ASYNCCOMPARE 326 auto mail = mails.first();
318 if (mails.size() != 1) { 327
319 return KAsync::error<void>(1, "Wrong number of mails."); 328 return Store::remove(*mail)
320 } 329 .then(ResourceControl::flushReplayQueue(query.resources)) //The change needs to be replayed already
321 auto mail = mails.first(); 330 .then(ResourceControl::inspect<Mail>(ResourceControl::Inspection::ExistenceInspection(*mail, false)));
322 331 });
323 return Store::remove(*mail) 332 })
324 .then(Store::flushReplayQueue(query.resources)) //The change needs to be replayed already 333 .exec();
325 .then(Resources::inspect<Mail>(Resources::Inspection::ExistenceInspection(*mail, false)));
326 })
327 .then<void>([](){});
328 })
329 .exec();
330 result.waitForFinished(); 334 result.waitForFinished();
331 QVERIFY(!result.errorCode()); 335 QVERIFY(!result.errorCode());
332 } 336 }
@@ -338,33 +342,64 @@ private Q_SLOTS:
338 342
339 auto query = Query::ResourceFilter("org.kde.maildir.instance1"); 343 auto query = Query::ResourceFilter("org.kde.maildir.instance1");
340 Store::synchronize(query).exec().waitForFinished(); 344 Store::synchronize(query).exec().waitForFinished();
341 Store::flushMessageQueue(query.resources).exec().waitForFinished(); 345 ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
346
347 Folder f;
342 348
343 auto result = Store::fetchOne<Folder>( 349 auto result = Store::fetchOne<Folder>(
344 Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("name", "maildir1") + Query::RequestedProperties(QByteArrayList() << "name") 350 Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("name", "maildir1") + Query::RequestedProperties(QByteArrayList() << "name")
351 )
352 .then<void, KAsync::Job<void>, Folder>([query, &f](const Folder &folder) {
353 f = folder;
354 return Store::fetchAll<Mail>(
355 Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("folder", folder) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject")
345 ) 356 )
346 .then<void, KAsync::Job<void>, Folder>([query](const Folder &folder) { 357 .then<void, KAsync::Job<void>, QList<Mail::Ptr> >([query](const QList<Mail::Ptr> &mails) {
347 Trace() << "Found a folder" << folder.identifier(); 358 //Can't use QCOMPARE because it tries to return FIXME Implement ASYNCCOMPARE
348 return Store::fetchAll<Mail>( 359 if (mails.size() != 1) {
349 Query::PropertyFilter("folder", folder) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject") 360 return KAsync::error<void>(1, "Wrong number of mails.");
350 ) 361 }
351 .then<void, KAsync::Job<void>, QList<Mail::Ptr> >([query](const QList<Mail::Ptr> &mails) { 362 auto mail = mails.first();
352 //Can't use QCOMPARE because it tries to return FIXME Implement ASYNCCOMPARE 363 mail->setProperty("unread", true);
353 if (mails.size() != 1) { 364 return Store::modify(*mail)
354 return KAsync::error<void>(1, "Wrong number of mails."); 365 .then<void>(ResourceControl::flushReplayQueue(query.resources)) //The change needs to be replayed already
355 } 366 .then(ResourceControl::inspect<Mail>(ResourceControl::Inspection::PropertyInspection(*mail, "unread", true)))
356 auto mail = mails.first(); 367 .then(ResourceControl::inspect<Mail>(ResourceControl::Inspection::PropertyInspection(*mail, "subject", mail->getProperty("subject"))));
357 mail->setProperty("unread", true); 368 });
358 auto inspectionCommand = Resources::Inspection::PropertyInspection(*mail, "unread", true); 369 })
359 return Store::modify(*mail) 370 .exec();
360 .then<void>(Store::flushReplayQueue(query.resources)) //The change needs to be replayed already
361 .then(Resources::inspect<Mail>(inspectionCommand));
362 })
363 .then<void>([](){});
364 })
365 .exec();
366 result.waitForFinished(); 371 result.waitForFinished();
367 QVERIFY(!result.errorCode()); 372 QVERIFY(!result.errorCode());
373
374 //Verify that we can still query for all relevant information
375 auto result2 = Store::fetchAll<Mail>(
376 Query::ResourceFilter("org.kde.maildir.instance1") + Query::PropertyFilter("folder", f) + Query::RequestedProperties(QByteArrayList() << "folder" << "subject" << "mimeMessage" << "unread")
377 )
378 .then<void, KAsync::Job<void>, QList<Mail::Ptr> >([](const QList<Mail::Ptr> &mails) {
379 //Can't use QCOMPARE because it tries to return FIXME Implement ASYNCCOMPARE
380 if (mails.size() != 1) {
381 qWarning() << "Wrong number of mails";
382 return KAsync::error<void>(1, "Wrong number of mails.");
383 }
384 auto mail = mails.first();
385 if (mail->getProperty("subject").toString().isEmpty()) {
386 qWarning() << "Wrong subject";
387 return KAsync::error<void>(1, "Wrong subject.");
388 }
389 if (mail->getProperty("unread").toBool() != true) {
390 qWarning() << "Not unread";
391 return KAsync::error<void>(1, "Not unread.");
392 }
393 QFileInfo info(mail->getProperty("mimeMessage").toString());
394 if (!info.exists()) {
395 qWarning() << "Wrong subject";
396 return KAsync::error<void>(1, "Can't find mime message.");
397 }
398 return KAsync::null<void>();
399 })
400 .exec();
401 result2.waitForFinished();
402 QVERIFY(!result2.errorCode());
368 } 403 }
369 404
370}; 405};
diff --git a/tests/messagequeuetest.cpp b/tests/messagequeuetest.cpp
index c835b53..454ace2 100644
--- a/tests/messagequeuetest.cpp
+++ b/tests/messagequeuetest.cpp
@@ -3,7 +3,7 @@
3#include <QString> 3#include <QString>
4#include <QQueue> 4#include <QQueue>
5 5
6#include "clientapi.h" 6#include "store.h"
7#include "storage.h" 7#include "storage.h"
8#include "messagequeue.h" 8#include "messagequeue.h"
9#include "log.h" 9#include "log.h"
diff --git a/tests/modelinteractivitytest.cpp b/tests/modelinteractivitytest.cpp
index 14c9fd0..3f6e697 100644
--- a/tests/modelinteractivitytest.cpp
+++ b/tests/modelinteractivitytest.cpp
@@ -4,7 +4,8 @@
4#include <iostream> 4#include <iostream>
5 5
6#include "dummyresource/resourcefactory.h" 6#include "dummyresource/resourcefactory.h"
7#include "clientapi.h" 7#include "store.h"
8#include "resourcecontrol.h"
8#include "commands.h" 9#include "commands.h"
9#include "resourceconfig.h" 10#include "resourceconfig.h"
10#include "log.h" 11#include "log.h"
@@ -46,15 +47,13 @@ private Q_SLOTS:
46 void initTestCase() 47 void initTestCase()
47 { 48 {
48 Sink::Log::setDebugOutputLevel(Sink::Log::Warning); 49 Sink::Log::setDebugOutputLevel(Sink::Log::Warning);
49 DummyResource::removeFromDisk("org.kde.dummy.instance1");
50 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy"); 50 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy");
51 Sink::Store::removeDataFromDisk(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
51 } 52 }
52 53
53 void cleanup() 54 void cleanup()
54 { 55 {
55 Sink::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished(); 56 Sink::Store::removeDataFromDisk(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
56 DummyResource::removeFromDisk("org.kde.dummy.instance1");
57 Sink::Store::start(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
58 } 57 }
59 58
60 void init() 59 void init()
@@ -75,7 +74,7 @@ private Q_SLOTS:
75 query.resources << "org.kde.dummy.instance1"; 74 query.resources << "org.kde.dummy.instance1";
76 query.liveQuery = true; 75 query.liveQuery = true;
77 76
78 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 77 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
79 78
80 //Test 79 //Test
81 QTime time; 80 QTime time;
diff --git a/tests/pipelinetest.cpp b/tests/pipelinetest.cpp
index c74d319..d6073dc 100644
--- a/tests/pipelinetest.cpp
+++ b/tests/pipelinetest.cpp
@@ -11,7 +11,7 @@
11#include "modifyentity_generated.h" 11#include "modifyentity_generated.h"
12#include "deleteentity_generated.h" 12#include "deleteentity_generated.h"
13#include "dummyresource/resourcefactory.h" 13#include "dummyresource/resourcefactory.h"
14#include "clientapi.h" 14#include "store.h"
15#include "commands.h" 15#include "commands.h"
16#include "entitybuffer.h" 16#include "entitybuffer.h"
17#include "resourceconfig.h" 17#include "resourceconfig.h"
@@ -52,7 +52,7 @@ static QByteArray getEntity(const QByteArray &dbEnv, const QByteArray &name, con
52 return result; 52 return result;
53} 53}
54 54
55flatbuffers::FlatBufferBuilder &createEvent(flatbuffers::FlatBufferBuilder &entityFbb, const QString &s = QString("summary")) 55flatbuffers::FlatBufferBuilder &createEvent(flatbuffers::FlatBufferBuilder &entityFbb, const QString &s = QString("summary"), const QString &d = QString())
56{ 56{
57 flatbuffers::FlatBufferBuilder eventFbb; 57 flatbuffers::FlatBufferBuilder eventFbb;
58 eventFbb.Clear(); 58 eventFbb.Clear();
@@ -66,9 +66,13 @@ flatbuffers::FlatBufferBuilder &createEvent(flatbuffers::FlatBufferBuilder &enti
66 { 66 {
67 auto uid = localFbb.CreateString("testuid"); 67 auto uid = localFbb.CreateString("testuid");
68 auto summary = localFbb.CreateString(s.toStdString()); 68 auto summary = localFbb.CreateString(s.toStdString());
69 auto description = localFbb.CreateString(d.toStdString());
69 auto localBuilder = Sink::ApplicationDomain::Buffer::EventBuilder(localFbb); 70 auto localBuilder = Sink::ApplicationDomain::Buffer::EventBuilder(localFbb);
70 localBuilder.add_uid(uid); 71 localBuilder.add_uid(uid);
71 localBuilder.add_summary(summary); 72 localBuilder.add_summary(summary);
73 if (!d.isEmpty()) {
74 localBuilder.add_description(description);
75 }
72 auto location = localBuilder.Finish(); 76 auto location = localBuilder.Finish();
73 Sink::ApplicationDomain::Buffer::FinishEventBuffer(localFbb, location); 77 Sink::ApplicationDomain::Buffer::FinishEventBuffer(localFbb, location);
74 } 78 }
@@ -203,7 +207,7 @@ private Q_SLOTS:
203 void testModify() 207 void testModify()
204 { 208 {
205 flatbuffers::FlatBufferBuilder entityFbb; 209 flatbuffers::FlatBufferBuilder entityFbb;
206 auto command = createEntityCommand(createEvent(entityFbb)); 210 auto command = createEntityCommand(createEvent(entityFbb, "summary", "description"));
207 211
208 Sink::Pipeline pipeline("org.kde.pipelinetest.instance1"); 212 Sink::Pipeline pipeline("org.kde.pipelinetest.instance1");
209 213
@@ -233,7 +237,9 @@ private Q_SLOTS:
233 QVERIFY(!buffer.isEmpty()); 237 QVERIFY(!buffer.isEmpty());
234 Sink::EntityBuffer entityBuffer(buffer.data(), buffer.size()); 238 Sink::EntityBuffer entityBuffer(buffer.data(), buffer.size());
235 auto adaptor = adaptorFactory->createAdaptor(entityBuffer.entity()); 239 auto adaptor = adaptorFactory->createAdaptor(entityBuffer.entity());
236 QCOMPARE(adaptor->getProperty("summary").toString(), QString("summary2")); 240 QVERIFY2(adaptor->getProperty("summary").toString() == QString("summary2"), "The modification isn't applied.");
241 //Ensure we didn't modify anything else
242 QVERIFY2(adaptor->getProperty("description").toString() == QString("description"), "The modification has sideeffects.");
237 243
238 //Both revisions are in the store at this point 244 //Both revisions are in the store at this point
239 QCOMPARE(getKeys("org.kde.pipelinetest.instance1", "event.main").size(), 2); 245 QCOMPARE(getKeys("org.kde.pipelinetest.instance1", "event.main").size(), 2);
diff --git a/tests/querytest.cpp b/tests/querytest.cpp
index 62db15b..16376b9 100644
--- a/tests/querytest.cpp
+++ b/tests/querytest.cpp
@@ -3,7 +3,8 @@
3#include <QString> 3#include <QString>
4 4
5#include "dummyresource/resourcefactory.h" 5#include "dummyresource/resourcefactory.h"
6#include "clientapi.h" 6#include "store.h"
7#include "resourcecontrol.h"
7#include "commands.h" 8#include "commands.h"
8#include "resourceconfig.h" 9#include "resourceconfig.h"
9#include "log.h" 10#include "log.h"
@@ -23,17 +24,13 @@ private Q_SLOTS:
23 Sink::Log::setDebugOutputLevel(Sink::Log::Trace); 24 Sink::Log::setDebugOutputLevel(Sink::Log::Trace);
24 auto factory = Sink::ResourceFactory::load("org.kde.dummy"); 25 auto factory = Sink::ResourceFactory::load("org.kde.dummy");
25 QVERIFY(factory); 26 QVERIFY(factory);
26 DummyResource::removeFromDisk("org.kde.dummy.instance1");
27 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy"); 27 ResourceConfig::addResource("org.kde.dummy.instance1", "org.kde.dummy");
28 Sink::Store::removeDataFromDisk(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
28 } 29 }
29 30
30 void cleanup() 31 void cleanup()
31 { 32 {
32 Sink::Store::shutdown(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished(); 33 Sink::Store::removeDataFromDisk(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
33 DummyResource::removeFromDisk("org.kde.dummy.instance1");
34 auto factory = Sink::ResourceFactory::load("org.kde.dummy");
35 QVERIFY(factory);
36 Sink::Store::start(QByteArray("org.kde.dummy.instance1")).exec().waitForFinished();
37 } 34 }
38 35
39 void init() 36 void init()
@@ -90,7 +87,7 @@ private Q_SLOTS:
90 query.liveQuery = false; 87 query.liveQuery = false;
91 88
92 //Ensure all local data is processed 89 //Ensure all local data is processed
93 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 90 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
94 91
95 //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data 92 //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data
96 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 93 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
@@ -165,7 +162,7 @@ private Q_SLOTS:
165 query.resources << "org.kde.dummy.instance1"; 162 query.resources << "org.kde.dummy.instance1";
166 163
167 //Ensure all local data is processed 164 //Ensure all local data is processed
168 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 165 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
169 166
170 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query); 167 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
171 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 168 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -185,7 +182,7 @@ private Q_SLOTS:
185 query.parentProperty = "parent"; 182 query.parentProperty = "parent";
186 183
187 //Ensure all local data is processed 184 //Ensure all local data is processed
188 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 185 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
189 186
190 //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data 187 //We fetch after the data is available and don't rely on the live query mechanism to deliver the actual data
191 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query); 188 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
@@ -214,7 +211,7 @@ private Q_SLOTS:
214 query.propertyFilter.insert("uid", "test1"); 211 query.propertyFilter.insert("uid", "test1");
215 212
216 //Ensure all local data is processed 213 //Ensure all local data is processed
217 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 214 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
218 215
219 //We fetch before the data is available and rely on the live query mechanism to deliver the actual data 216 //We fetch before the data is available and rely on the live query mechanism to deliver the actual data
220 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 217 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);
@@ -234,7 +231,7 @@ private Q_SLOTS:
234 query.resources << "org.kde.dummy.instance1"; 231 query.resources << "org.kde.dummy.instance1";
235 232
236 //Ensure all local data is processed 233 //Ensure all local data is processed
237 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 234 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
238 235
239 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query); 236 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
240 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); 237 QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
@@ -255,7 +252,7 @@ private Q_SLOTS:
255 query.propertyFilter.insert("folder", folderEntity->identifier()); 252 query.propertyFilter.insert("folder", folderEntity->identifier());
256 253
257 //Ensure all local data is processed 254 //Ensure all local data is processed
258 Sink::Store::flushMessageQueue(query.resources).exec().waitForFinished(); 255 Sink::ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
259 256
260 //We fetch before the data is available and rely on the live query mechanism to deliver the actual data 257 //We fetch before the data is available and rely on the live query mechanism to deliver the actual data
261 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query); 258 auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Mail>(query);