diff options
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 | ||
3 | option(BUILD_MAILDIR "BUILD_MAILDIR" ON) | 3 | option(BUILD_MAILDIR "BUILD_MAILDIR" ON) |
4 | option(AVOID_BINDING_REBUILD "AVOID_BINDING_REBUILD" OFF) | 4 | option(AVOID_BINDING_REBUILD "AVOID_BINDING_REBUILD" OFF) |
5 | option(CATCH_ERRORS "CATCH_ERRORS" OFF) | ||
5 | 6 | ||
6 | # ECM setup | 7 | # ECM setup |
7 | find_package(ECM 0.0.10 REQUIRED NO_MODULE) | 8 | find_package(ECM 0.0.10 REQUIRED NO_MODULE) |
@@ -41,8 +42,27 @@ function(generate_flatbuffers _target) | |||
41 | endforeach(fbs) | 42 | endforeach(fbs) |
42 | endfunction(generate_flatbuffers) | 43 | endfunction(generate_flatbuffers) |
43 | 44 | ||
45 | add_custom_target(analyze) | ||
46 | function(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) | ||
59 | endfunction() | ||
60 | |||
44 | set(CMAKE_AUTOMOC ON) | 61 | set(CMAKE_AUTOMOC ON) |
45 | add_definitions("-Wall -std=c++0x -g") | 62 | if (${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") | ||
64 | endif() | ||
65 | add_definitions("-std=c++0x -g") | ||
46 | include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${FLATBUFFERS_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/common) | 66 | include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${FLATBUFFERS_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/common) |
47 | include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/common/domain) | 67 | include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/common/domain) |
48 | 68 | ||
@@ -1,7 +1,8 @@ | |||
1 | This repository contains a draft of a possible successor to Akonadi 1. | 1 | # Sink |
2 | 2 | ||
3 | It is currently entirely experimental and should not be considered a definite | 3 | Sink is a data access layer handling synchronization, caching and indexing. |
4 | replacement for Akonadi 1 at this point in time. | 4 | |
5 | To build it's documentation please run the builddocs.sh script or see the docs/ subdirectory. | ||
5 | 6 | ||
6 | Discussion of the code should be done on the kde-pim at kde.org mailing list | 7 | Discussion of the code should be done on the kde-pim at kde.org mailing list |
7 | or in #kontact on IRC. | 8 | or 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 | ||
16 | You can find information about the overall design here: | 17 | For 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 | ||
2 | mkdocs build | ||
3 | echo "The HTML files for the documentation can be found in the site/ subdirectory." | ||
4 | echo "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 @@ | |||
1 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) | 1 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) |
2 | include_directories(domain) | 2 | include_directories(domain) |
3 | 3 | ||
4 | project(sinkcommon) | 4 | project(sink) |
5 | 5 | ||
6 | ecm_setup_version("0.1" VARIABLE_PREFIX SinkCommon | 6 | ecm_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 ########### |
13 | set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/SinkCommon") | 13 | set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/Sink") |
14 | 14 | ||
15 | ecm_configure_package_config_file( | 15 | ecm_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 | ||
21 | install(FILES | 21 | install(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 | ||
28 | install(EXPORT SinkCommonTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE SinkCommonTargets.cmake) | 28 | add_definitions("-fvisibility=hidden") |
29 | |||
30 | install(EXPORT SinkTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE SinkTargets.cmake) | ||
29 | 31 | ||
30 | set(storage_SRCS storage_lmdb.cpp) | 32 | set(storage_SRCS storage_lmdb.cpp) |
31 | set(storage_LIBS lmdb) | 33 | set(storage_LIBS lmdb) |
32 | 34 | ||
33 | set(command_SRCS | 35 | set(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 | ||
88 | generate_export_header(${PROJECT_NAME} BASE_NAME SinkCommon EXPORT_FILE_NAME sinkcommon_export.h) | 92 | generate_export_header(${PROJECT_NAME} BASE_NAME Sink EXPORT_FILE_NAME sink_export.h) |
89 | SET_TARGET_PROPERTIES(${PROJECT_NAME} | 93 | SET_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} | |||
95 | qt5_use_modules(${PROJECT_NAME} Network) | 99 | qt5_use_modules(${PROJECT_NAME} Network) |
96 | target_link_libraries(${PROJECT_NAME} ${storage_LIBS} KF5::Async) | 100 | target_link_libraries(${PROJECT_NAME} ${storage_LIBS} KF5::Async) |
97 | install(TARGETS ${PROJECT_NAME} | 101 | install(TARGETS ${PROJECT_NAME} |
98 | EXPORT SinkCommonTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) | 102 | EXPORT SinkTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) |
103 | |||
104 | add_clang_static_analysis(${PROJECT_NAME}) | ||
99 | 105 | ||
100 | install(FILES | 106 | install(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 | ||
3 | find_dependency(KF5Mime "@KMIME_LIB_VERSION@") | 3 | find_dependency(KF5Mime "@KMIME_LIB_VERSION@") |
4 | 4 | ||
5 | include("${CMAKE_CURRENT_LIST_DIR}/SinkCommonTargets.cmake") | 5 | include("${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 | |||
32 | class QAbstractItemModel; | ||
33 | |||
34 | namespace Sink { | ||
35 | class ResourceAccess; | ||
36 | class Notification; | ||
37 | |||
38 | /** | ||
39 | * Store interface used in the client API. | ||
40 | */ | ||
41 | class Store { | ||
42 | public: | ||
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 | |||
122 | namespace Resources { | ||
123 | template <class DomainType> | ||
124 | KAsync::Job<void> inspect(const Inspection &inspectionCommand); | ||
125 | } | ||
126 | |||
127 | class Notifier { | ||
128 | public: | ||
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 | |||
134 | private: | ||
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 | ||
55 | QByteArray name(int commandId); | 56 | QByteArray name(int commandId); |
56 | 57 | ||
57 | int SINKCOMMON_EXPORT headerSize(); | 58 | int SINK_EXPORT headerSize(); |
58 | void SINKCOMMON_EXPORT write(QIODevice *device, int messageId, int commandId); | 59 | void SINK_EXPORT write(QIODevice *device, int messageId, int commandId); |
59 | void SINKCOMMON_EXPORT write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size); | 60 | void SINK_EXPORT write(QIODevice *device, int messageId, int commandId, const char *buffer, uint size); |
60 | void SINKCOMMON_EXPORT write(QIODevice *device, int messageId, int commandId, flatbuffers::FlatBufferBuilder &fbb); | 61 | void 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 @@ | |||
1 | namespace Sink.Commands; | 1 | namespace Sink.Commands; |
2 | 2 | ||
3 | enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress, Inspection } | 3 | enum NotificationType : byte { Shutdown = 1, Status, Warning, Progress, Inspection, RevisionUpdate } |
4 | enum NotificationCode : byte { Success = 0, Failure = 1, UserCode } | 4 | enum NotificationCode : byte { Success = 0, Failure = 1, UserCode } |
5 | 5 | ||
6 | table Notification { | 6 | table 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 | ||
26 | namespace Sink { | 27 | namespace 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 | ||
67 | bool ApplicationDomainType::hasProperty(const QByteArray &key) const | ||
68 | { | ||
69 | Q_ASSERT(mAdaptor); | ||
70 | return mAdaptor->availableProperties().contains(key); | ||
71 | } | ||
72 | |||
67 | QVariant ApplicationDomainType::getProperty(const QByteArray &key) const | 73 | QVariant 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 | |||
76 | void ApplicationDomainType::setProperty(const QByteArray &key, const QVariant &value) | 82 | void 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 | ||
89 | void ApplicationDomainType::setChangedProperties(const QSet<QByteArray> &changeset) | ||
90 | { | ||
91 | mChangeSet = changeset; | ||
92 | } | ||
93 | |||
83 | QByteArrayList ApplicationDomainType::changedProperties() const | 94 | QByteArrayList ApplicationDomainType::changedProperties() const |
84 | { | 95 | { |
85 | return mChangeSet.keys(); | 96 | return mChangeSet.toList(); |
86 | } | 97 | } |
87 | 98 | ||
88 | qint64 ApplicationDomainType::revision() const | 99 | qint64 ApplicationDomainType::revision() const |
@@ -100,6 +111,36 @@ QByteArray ApplicationDomainType::identifier() const | |||
100 | return mIdentifier; | 111 | return mIdentifier; |
101 | } | 112 | } |
102 | 113 | ||
114 | Entity::~Entity() | ||
115 | { | ||
116 | |||
117 | } | ||
118 | |||
119 | Event::~Event() | ||
120 | { | ||
121 | |||
122 | } | ||
123 | |||
124 | Todo::~Todo() | ||
125 | { | ||
126 | |||
127 | } | ||
128 | |||
129 | Mail::~Mail() | ||
130 | { | ||
131 | |||
132 | } | ||
133 | |||
134 | Folder::~Folder() | ||
135 | { | ||
136 | |||
137 | } | ||
138 | |||
139 | SinkResource::~SinkResource() | ||
140 | { | ||
141 | |||
142 | } | ||
143 | |||
103 | template<> | 144 | template<> |
104 | QByteArray getTypeName<Event>() | 145 | QByteArray 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 | ||
27 | namespace Sink { | 29 | namespace 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 | */ |
38 | class ApplicationDomainType { | 40 | class SINK_EXPORT ApplicationDomainType { |
39 | public: | 41 | public: |
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 | ||
65 | private: | 69 | private: |
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 | ||
85 | struct Entity : public ApplicationDomainType { | 90 | inline 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 | |||
100 | struct 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 | ||
90 | struct Event : public Entity { | 106 | struct 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 | ||
95 | struct Todo : public Entity { | 112 | struct 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 | ||
100 | struct Calendar : public Entity { | 118 | struct 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 | ||
105 | struct Mail : public Entity { | 124 | struct 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 | ||
110 | struct Folder : public Entity { | 130 | struct 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 | */ |
121 | struct SinkResource : public ApplicationDomainType { | 142 | struct 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 | */ |
131 | template<class DomainType> | 153 | template<class DomainType> |
132 | QByteArray getTypeName(); | 154 | QByteArray SINK_EXPORT getTypeName(); |
133 | 155 | ||
134 | template<> | 156 | template<> |
135 | QByteArray getTypeName<Event>(); | 157 | QByteArray SINK_EXPORT getTypeName<Event>(); |
136 | 158 | ||
137 | template<> | 159 | template<> |
138 | QByteArray getTypeName<Todo>(); | 160 | QByteArray SINK_EXPORT getTypeName<Todo>(); |
139 | 161 | ||
140 | template<> | 162 | template<> |
141 | QByteArray getTypeName<SinkResource>(); | 163 | QByteArray SINK_EXPORT getTypeName<SinkResource>(); |
142 | 164 | ||
143 | template<> | 165 | template<> |
144 | QByteArray getTypeName<Mail>(); | 166 | QByteArray SINK_EXPORT getTypeName<Mail>(); |
145 | 167 | ||
146 | template<> | 168 | template<> |
147 | QByteArray getTypeName<Folder>(); | 169 | QByteArray 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 | */ |
155 | template<typename DomainType> | 177 | template<typename DomainType> |
156 | class TypeImplementation; | 178 | class 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 | */ |
123 | template<typename DomainType, typename ResourceBuffer, typename ResourceBuilder> | 124 | template<typename DomainType, typename ResourceBuffer, typename ResourceBuilder> |
124 | class DomainTypeAdaptorFactory : public DomainTypeAdaptorFactoryInterface | 125 | class 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 | ||
21 | EntityBuffer::EntityBuffer(const QByteArray &data) | ||
22 | : EntityBuffer(data.constData(), data.size()) | ||
23 | { | ||
24 | |||
25 | } | ||
26 | |||
21 | bool EntityBuffer::isValid() const | 27 | bool EntityBuffer::isValid() const |
22 | { | 28 | { |
23 | return mEntity; | 29 | return mEntity; |
@@ -25,6 +31,7 @@ bool EntityBuffer::isValid() const | |||
25 | 31 | ||
26 | const Sink::Entity &EntityBuffer::entity() | 32 | const 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 | ||
6 | namespace Sink { | 8 | namespace Sink { |
7 | struct Entity; | 9 | struct Entity; |
8 | 10 | ||
9 | class EntityBuffer { | 11 | class SINK_EXPORT EntityBuffer { |
10 | public: | 12 | public: |
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 | ||
30 | using namespace Sink; | 30 | using 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 | */ | ||
32 | class ResourceAccessFactory { | 40 | class ResourceAccessFactory { |
33 | public: | 41 | public: |
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 | */ |
45 | template <typename DomainType> | 46 | template <typename DomainType> |
46 | class GenericFacade: public Sink::StoreFacade<DomainType> | 47 | class SINK_EXPORT GenericFacade: public Sink::StoreFacade<DomainType> |
47 | { | 48 | { |
48 | public: | 49 | public: |
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 | ||
64 | protected: | 65 | protected: |
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 | */ |
40 | class FacadeFactory { | 41 | class SINK_EXPORT FacadeFactory { |
41 | public: | 42 | public: |
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 | ||
21 | static int sBatchSize = 100; | 22 | static int sBatchSize = 100; |
23 | //This interval directly affects the roundtrip time of single commands | ||
24 | static int sCommitInterval = 10; | ||
22 | 25 | ||
23 | using namespace Sink; | 26 | using 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 | ||
291 | GenericResource::GenericResource(const QByteArray &resourceInstanceIdentifier, const QSharedPointer<Pipeline> &pipeline) | 309 | GenericResource::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 | ||
404 | void GenericResource::removeDataFromDisk() | ||
405 | { | ||
406 | removeFromDisk(mResourceInstanceIdentifier); | ||
407 | } | ||
408 | |||
384 | void GenericResource::removeFromDisk(const QByteArray &instanceIdentifier) | 409 | void 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 | ||
393 | qint64 GenericResource::diskUsage(const QByteArray &instanceIdentifier) | 418 | qint64 GenericResource::diskUsage(const QByteArray &instanceIdentifier) |
@@ -556,38 +581,39 @@ void GenericResource::deleteEntity(const QByteArray &sinkId, qint64 revision, co | |||
556 | 581 | ||
557 | void GenericResource::recordRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction) | 582 | void 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 | ||
565 | void GenericResource::removeRemoteId(const QByteArray &bufferType, const QByteArray &localId, const QByteArray &remoteId, Sink::Storage::Transaction &transaction) | 588 | void 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 | |
594 | void 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 | ||
573 | QByteArray GenericResource::resolveRemoteId(const QByteArray &bufferType, const QByteArray &remoteId, Sink::Storage::Transaction &transaction) | 601 | QByteArray 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 | ||
587 | QByteArray GenericResource::resolveLocalId(const QByteArray &bufferType, const QByteArray &localId, Sink::Storage::Transaction &transaction) | 614 | QByteArray 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 | ||
634 | void GenericResource::createOrModify(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, DomainTypeAdaptorFactoryInterface &adaptorFactory, const QByteArray &bufferType, const QByteArray &remoteId, const Sink::ApplicationDomain::ApplicationDomainType &entity) | 660 | void 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 | */ |
40 | class SINKCOMMON_EXPORT GenericResource : public Resource | 40 | class SINK_EXPORT GenericResource : public Resource |
41 | { | 41 | { |
42 | public: | 42 | public: |
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 | */ |
11 | class Index | 12 | class SINK_EXPORT Index |
12 | { | 13 | { |
13 | public: | 14 | public: |
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 | ||
26 | namespace Sink { | 26 | namespace Sink { |
27 | namespace Resources { | 27 | namespace ResourceControl { |
28 | 28 | ||
29 | struct Inspection { | 29 | struct 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 | |||
42 | Listener::Listener(const QByteArray &resourceInstanceIdentifier, QObject *parent) | 45 | Listener::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 | ||
59 | class Listener : public QObject | 60 | class 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 | |||
27 | enum Roles { | ||
28 | DomainObjectRole = Qt::UserRole + 1 | ||
29 | }; | ||
30 | |||
31 | template<class T> | ||
32 | class ListModelResult : public QAbstractListModel | ||
33 | { | ||
34 | public: | ||
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 | |||
120 | private: | ||
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 | ||
9 | using namespace Sink::Log; | 12 | using namespace Sink::Log; |
10 | 13 | ||
14 | static QSharedPointer<QSettings> config() | ||
15 | { | ||
16 | return QSharedPointer<QSettings>::create(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/sink/log.ini", QSettings::IniFormat); | ||
17 | } | ||
18 | |||
11 | class DebugStream: public QIODevice | 19 | class DebugStream: public QIODevice |
12 | { | 20 | { |
13 | public: | 21 | public: |
@@ -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 | } |
34 | private: | 38 | private: |
35 | Q_DISABLE_COPY(DebugStream) | 39 | Q_DISABLE_COPY(DebugStream) |
36 | }; | 40 | }; |
37 | 41 | ||
42 | //Virtual method anchor | ||
43 | DebugStream::~DebugStream() | ||
44 | {} | ||
45 | |||
38 | class NullStream: public QIODevice | 46 | class NullStream: public QIODevice |
39 | { | 47 | { |
40 | public: | 48 | public: |
@@ -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 | ||
68 | NullStream::~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 | ||
131 | void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel) | 143 | void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel) |
132 | { | 144 | { |
133 | qputenv("SINKDEBUGLEVEL", debugLevelName(debugLevel)); | 145 | config()->setValue("level", debugLevel); |
134 | } | 146 | } |
135 | 147 | ||
136 | Sink::Log::DebugLevel Sink::Log::debugOutputLevel() | 148 | Sink::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 | |||
153 | void 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 | |||
165 | QByteArrayList 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 | |||
175 | void Sink::Log::setDebugOutputFields(const QByteArrayList &output) | ||
176 | { | ||
177 | config()->setValue("outputfields", QVariant::fromValue(output)); | ||
178 | } | ||
179 | |||
180 | QByteArrayList Sink::Log::debugOutputFields() | ||
181 | { | ||
182 | return config()->value("outputfields").value<QByteArrayList>(); | ||
183 | } | ||
184 | |||
185 | static QByteArray getProgramName() | ||
186 | { | ||
187 | if (QCoreApplication::instance()) { | ||
188 | return QCoreApplication::instance()->applicationName().toLocal8Bit(); | ||
189 | } else { | ||
190 | return "<unknown program name>"; | ||
191 | } | ||
192 | } | ||
193 | |||
194 | static 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 | |||
204 | static 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 | ||
141 | QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea) | 214 | QDebug 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 | ||
5 | namespace Sink { | 6 | namespace Sink { |
@@ -12,19 +13,75 @@ enum DebugLevel { | |||
12 | Error | 13 | Error |
13 | }; | 14 | }; |
14 | 15 | ||
15 | QByteArray debugLevelName(DebugLevel debugLevel); | 16 | QByteArray SINK_EXPORT debugLevelName(DebugLevel debugLevel); |
16 | DebugLevel debugLevelFromName(const QByteArray &name); | 17 | DebugLevel SINK_EXPORT debugLevelFromName(const QByteArray &name); |
17 | 18 | ||
18 | void setDebugOutputLevel(DebugLevel); | 19 | /** |
19 | DebugLevel debugOutputLevel(); | 20 | * Sets the debug output level. |
21 | * | ||
22 | * Everything below is ignored. | ||
23 | */ | ||
24 | void SINK_EXPORT setDebugOutputLevel(DebugLevel); | ||
25 | DebugLevel SINK_EXPORT debugOutputLevel(); | ||
20 | 26 | ||
21 | QDebug debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea = 0); | 27 | enum 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 | */ | ||
40 | void SINK_EXPORT setDebugOutputFilter(FilterType, const QByteArrayList &filter); | ||
41 | QByteArrayList 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 | */ | ||
53 | void SINK_EXPORT setDebugOutputFields(const QByteArrayList &filter); | ||
54 | QByteArrayList SINK_EXPORT debugOutputFields(); | ||
55 | |||
56 | QDebug SINK_EXPORT debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea = 0); | ||
57 | |||
58 | struct SINK_EXPORT TraceTime | ||
59 | { | ||
60 | TraceTime(int i) : time(i){}; | ||
61 | const int time; | ||
62 | }; | ||
63 | |||
64 | inline 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 | */ |
14 | class MessageQueue : public QObject | 15 | class SINK_EXPORT MessageQueue : public QObject |
15 | { | 16 | { |
16 | Q_OBJECT | 17 | Q_OBJECT |
17 | public: | 18 | public: |
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 | |||
27 | static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type) | 30 | static 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 | ||
25 | namespace Sink | 25 | namespace Sink |
@@ -28,7 +28,7 @@ namespace Sink | |||
28 | /** | 28 | /** |
29 | * A notification | 29 | * A notification |
30 | */ | 30 | */ |
31 | class SINKCOMMON_EXPORT Notification | 31 | class SINK_EXPORT Notification |
32 | { | 32 | { |
33 | public: | 33 | public: |
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 | |||
28 | using namespace Sink; | ||
29 | |||
30 | class Sink::Notifier::Private { | ||
31 | public: | ||
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 | |||
42 | Notifier::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 ¬ification) { | ||
46 | for (const auto &handler : d->handler) { | ||
47 | handler(notification); | ||
48 | } | ||
49 | }); | ||
50 | d->resourceAccess << resourceAccess; | ||
51 | } | ||
52 | |||
53 | Notifier::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 ¬ification) { | ||
59 | for (const auto &handler : d->handler) { | ||
60 | handler(notification); | ||
61 | } | ||
62 | }); | ||
63 | d->resourceAccess << resourceAccess; | ||
64 | } | ||
65 | |||
66 | void 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 | |||
29 | class QAbstractItemModel; | ||
30 | |||
31 | namespace Sink { | ||
32 | class ResourceAccess; | ||
33 | class Notification; | ||
34 | |||
35 | class SINK_EXPORT Notifier { | ||
36 | public: | ||
37 | Notifier(const QSharedPointer<ResourceAccess> &resourceAccess); | ||
38 | Notifier(const QByteArray &resourceInstanceIdentifier); | ||
39 | void registerHandler(std::function<void(const Notification &)>); | ||
40 | |||
41 | private: | ||
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 | |||
39 | namespace Sink | 43 | namespace 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 | ||
65 | void 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 | |||
58 | Pipeline::Pipeline(const QString &resourceName, QObject *parent) | 78 | Pipeline::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 | ||
92 | void Pipeline::commit() | 115 | void 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 | ||
121 | void 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 | |||
133 | KAsync::Job<qint64> Pipeline::newEntity(void const *command, size_t size) | 145 | KAsync::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) | |||
212 | KAsync::Job<qint64> Pipeline::modifiedEntity(void const *command, size_t size) | 225 | KAsync::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, [¤t, adaptorFactory](const QByteArray &key, const QByteArray &data) -> bool { | 271 | Storage::mainDatabase(d->transaction, bufferType).findLatest(key, [¤t, 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) | |||
322 | KAsync::Job<qint64> Pipeline::deletedEntity(void const *command, size_t size) | 346 | KAsync::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, ¤t](const QByteArray &, const QByteArray &data) -> bool { | 412 | Storage::mainDatabase(d->transaction, bufferType).findLatest(key, [this, bufferType, newRevision, adaptorFactory, key, ¤t](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 | ||
412 | void Pipeline::cleanupRevision(qint64 revision) | 436 | void 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 | ||
438 | qint64 Pipeline::cleanedUpRevision() | 462 | qint64 Pipeline::cleanedUpRevision() |
439 | { | 463 | { |
440 | return Sink::Storage::cleanedUpRevision(d->transaction); | 464 | return Storage::cleanedUpRevision(d->transaction); |
441 | } | 465 | } |
442 | 466 | ||
443 | Preprocessor::Preprocessor() | 467 | Preprocessor::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 | ||
38 | class Preprocessor; | 38 | class Preprocessor; |
39 | 39 | ||
40 | class SINKCOMMON_EXPORT Pipeline : public QObject | 40 | class 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 | ||
75 | private: | 75 | private: |
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 | ||
82 | class SINKCOMMON_EXPORT Preprocessor | 80 | class SINK_EXPORT Preprocessor |
83 | { | 81 | { |
84 | public: | 82 | public: |
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 | */ |
30 | template <class T> | 31 | template <class T> |
31 | flatbuffers::uoffset_t variantToProperty(const QVariant &, flatbuffers::FlatBufferBuilder &fbb); | 32 | flatbuffers::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 | */ |
36 | template <typename T> | 37 | template <typename T> |
37 | QVariant propertyToVariant(const flatbuffers::String *); | 38 | QVariant SINK_EXPORT propertyToVariant(const flatbuffers::String *); |
38 | template <typename T> | 39 | template <typename T> |
39 | QVariant propertyToVariant(uint8_t); | 40 | QVariant SINK_EXPORT propertyToVariant(uint8_t); |
40 | template <typename T> | 41 | template <typename T> |
41 | QVariant propertyToVariant(const flatbuffers::Vector<uint8_t> *); | 42 | QVariant SINK_EXPORT propertyToVariant(const flatbuffers::Vector<uint8_t> *); |
42 | 43 | ||
43 | 44 | ||
44 | /** | 45 | /** |
@@ -52,6 +53,8 @@ template<typename BufferType> | |||
52 | class ReadPropertyMapper | 53 | class ReadPropertyMapper |
53 | { | 54 | { |
54 | public: | 55 | public: |
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> | |||
106 | class WritePropertyMapper | 109 | class WritePropertyMapper |
107 | { | 110 | { |
108 | public: | 111 | public: |
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 | |||
29 | using namespace Sink; | 32 | using namespace Sink; |
30 | 33 | ||
31 | /* | 34 | /* |
@@ -38,14 +41,14 @@ template<typename DomainType> | |||
38 | class QueryWorker : public QObject | 41 | class QueryWorker : public QObject |
39 | { | 42 | { |
40 | public: | 43 | public: |
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 | ||
47 | private: | 50 | private: |
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 | ||
59 | private: | 62 | private: |
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 | ||
120 | template<class DomainType> | 123 | template<class DomainType> |
124 | void QueryRunner<DomainType>::setResultTransformation(const ResultTransformation &transformation) | ||
125 | { | ||
126 | mResultTransformation = transformation; | ||
127 | } | ||
128 | |||
129 | template<class DomainType> | ||
121 | typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr QueryRunner<DomainType>::emitter() | 130 | typename 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 | ||
149 | template<class DomainType> | 158 | template<class DomainType> |
150 | QueryWorker<DomainType>::QueryWorker(const Sink::Query &query, const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &factory, const QByteArray &bufferType) | 159 | QueryWorker<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> | |||
167 | void QueryWorker<DomainType>::replaySet(ResultSet &resultSet, Sink::ResultProviderInterface<typename DomainType::Ptr> &resultProvider, const QList<QByteArray> &properties) | 177 | void 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 @@ | |||
32 | class QueryRunnerBase : public QObject | 32 | class QueryRunnerBase : public QObject |
33 | { | 33 | { |
34 | Q_OBJECT | 34 | Q_OBJECT |
35 | public: | ||
36 | typedef std::function<void(Sink::ApplicationDomain::ApplicationDomainType &domainObject)> ResultTransformation; | ||
37 | |||
35 | protected: | 38 | protected: |
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 | |||
47 | protected slots: | 49 | protected 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 | ||
87 | private: | 95 | private: |
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 | ||
40 | Resource::~Resource() | 40 | Resource::~Resource() |
@@ -63,6 +63,11 @@ void Resource::setLowerBoundRevision(qint64 revision) | |||
63 | Q_UNUSED(revision) | 63 | Q_UNUSED(revision) |
64 | } | 64 | } |
65 | 65 | ||
66 | void Resource::removeDataFromDisk() | ||
67 | { | ||
68 | } | ||
69 | |||
70 | |||
66 | class ResourceFactory::Private | 71 | class ResourceFactory::Private |
67 | { | 72 | { |
68 | public: | 73 | public: |
@@ -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 | ||
81 | ResourceFactory::~ResourceFactory() | 86 | ResourceFactory::~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 | */ |
34 | class SINKCOMMON_EXPORT Resource : public QObject | 34 | class SINK_EXPORT Resource : public QObject |
35 | { | 35 | { |
36 | Q_OBJECT | 36 | Q_OBJECT |
37 | public: | 37 | public: |
@@ -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 | |||
58 | Q_SIGNALS: | 63 | Q_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 | */ |
70 | class ResourceFactory : public QObject | 75 | class SINK_EXPORT ResourceFactory : public QObject |
71 | { | 76 | { |
72 | public: | 77 | public: |
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 | ||
50 | static void queuedInvoke(const std::function<void()> &f, QObject *context = 0) | 52 | static 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 | ||
192 | KAsync::Job<void> ResourceAccess::Private::initializeSocket() | 197 | KAsync::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 | ||
238 | ResourceAccess::~ResourceAccess() | 243 | ResourceAccess::~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 | ||
401 | void ResourceAccess::close() | 408 | void 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 | ||
473 | void ResourceAccess::disconnected() | 480 | void 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() | |||
480 | void ResourceAccess::connectionError(QLocalSocket::LocalSocketError error) | 487 | void 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 | ||
583 | void 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 | ||
35 | struct QueuedCommand; | 36 | struct QueuedCommand; |
36 | 37 | ||
37 | class ResourceAccessInterface : public QObject | 38 | class SINK_EXPORT ResourceAccessInterface : public QObject |
38 | { | 39 | { |
39 | Q_OBJECT | 40 | Q_OBJECT |
40 | public: | 41 | public: |
@@ -62,7 +63,7 @@ public Q_SLOTS: | |||
62 | virtual void close() = 0; | 63 | virtual void close() = 0; |
63 | }; | 64 | }; |
64 | 65 | ||
65 | class ResourceAccess : public ResourceAccessInterface | 66 | class SINK_EXPORT ResourceAccess : public ResourceAccessInterface |
66 | { | 67 | { |
67 | Q_OBJECT | 68 | Q_OBJECT |
68 | public: | 69 | public: |
@@ -100,7 +101,6 @@ private Q_SLOTS: | |||
100 | 101 | ||
101 | private: | 102 | private: |
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 | ||
27 | class ResourceConfig | 28 | class SINK_EXPORT ResourceConfig |
28 | { | 29 | { |
29 | public: | 30 | public: |
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 | |||
35 | namespace Sink | ||
36 | { | ||
37 | |||
38 | KAsync::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 | |||
59 | KAsync::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 | |||
71 | KAsync::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 | |||
85 | KAsync::Job<void> ResourceControl::flushReplayQueue(const QByteArrayList &resourceIdentifier) | ||
86 | { | ||
87 | return flushMessageQueue(resourceIdentifier); | ||
88 | } | ||
89 | |||
90 | template <class DomainType> | ||
91 | KAsync::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 ¬ification) { | ||
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 | |||
119 | REGISTER_TYPE(ApplicationDomain::Event); | ||
120 | REGISTER_TYPE(ApplicationDomain::Mail); | ||
121 | REGISTER_TYPE(ApplicationDomain::Folder); | ||
122 | REGISTER_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 | |||
30 | namespace Sink { | ||
31 | namespace ResourceControl { | ||
32 | |||
33 | template <class DomainType> | ||
34 | KAsync::Job<void> SINK_EXPORT inspect(const Inspection &inspectionCommand); | ||
35 | |||
36 | /** | ||
37 | * Shutdown resource. | ||
38 | */ | ||
39 | KAsync::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 | */ | ||
48 | KAsync::Job<void> SINK_EXPORT start(const QByteArray &resourceIdentifier); | ||
49 | |||
50 | /** | ||
51 | * Flushes any pending messages to disk | ||
52 | */ | ||
53 | KAsync::Job<void> SINK_EXPORT flushMessageQueue(const QByteArrayList &resourceIdentifier); | ||
54 | |||
55 | /** | ||
56 | * Flushes any pending messages that haven't been replayed to the source. | ||
57 | */ | ||
58 | KAsync::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 | ||
30 | using namespace async; | ||
31 | |||
32 | namespace Sink { | 30 | namespace 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 | ||
333 | template<class DomainType> | 336 | template<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 @@ | |||
29 | namespace Sink | 29 | namespace Sink |
30 | { | 30 | { |
31 | 31 | ||
32 | class SINKCOMMON_EXPORT Storage { | 32 | class SINK_EXPORT Storage { |
33 | public: | 33 | public: |
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 | |||
203 | private: | 212 | private: |
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 | ||
159 | Storage::NamedDatabase Storage::mainDatabase(const Sink::Storage::Transaction &t, const QByteArray &type) | ||
160 | { | ||
161 | return t.openDatabase(type + ".main"); | ||
162 | } | ||
163 | |||
159 | bool Storage::NamedDatabase::contains(const QByteArray &uid) | 164 | bool 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 | ||
618 | void 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 | |||
42 | namespace Sink | 41 | namespace Sink |
43 | { | 42 | { |
44 | 43 | ||
@@ -47,11 +46,6 @@ QString Store::storageLocation() | |||
47 | return Sink::storageLocation(); | 46 | return Sink::storageLocation(); |
48 | } | 47 | } |
49 | 48 | ||
50 | QByteArray Store::resourceName(const QByteArray &instanceIdentifier) | ||
51 | { | ||
52 | return Sink::resourceName(instanceIdentifier); | ||
53 | } | ||
54 | |||
55 | static QList<QByteArray> getResources(const QList<QByteArray> &resourceFilter, const QByteArray &type) | 49 | static 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 | ||
159 | KAsync::Job<void> Store::shutdown(const QByteArray &identifier) | 153 | KAsync::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 | |||
180 | KAsync::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 | ||
190 | void 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 | |||
200 | KAsync::Job<void> Store::synchronize(const Sink::Query &query) | 168 | KAsync::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 | |||
216 | KAsync::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 | |||
232 | KAsync::Job<void> Store::flushReplayQueue(const QByteArrayList &resourceIdentifier) | ||
233 | { | ||
234 | return flushMessageQueue(resourceIdentifier); | ||
235 | } | 180 | } |
236 | 181 | ||
237 | template <class DomainType> | 182 | template <class DomainType> |
@@ -295,63 +240,10 @@ KAsync::Job<QList<typename DomainType::Ptr> > Store::fetch(const Sink::Query &qu | |||
295 | }); | 240 | }); |
296 | } | 241 | } |
297 | 242 | ||
298 | template <class DomainType> | ||
299 | KAsync::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 ¬ification) { | ||
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 | |||
322 | class Sink::Notifier::Private { | ||
323 | public: | ||
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 | |||
334 | Notifier::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 ¬ification) { | ||
338 | for (const auto &handler : d->handler) { | ||
339 | handler(notification); | ||
340 | } | ||
341 | }); | ||
342 | d->resourceAccess << resourceAccess; | ||
343 | } | ||
344 | |||
345 | void 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 | |||
32 | class QAbstractItemModel; | ||
33 | |||
34 | namespace 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 | */ | ||
42 | namespace Store { | ||
43 | |||
44 | QString SINK_EXPORT storageLocation(); | ||
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 | QSharedPointer<QAbstractItemModel> SINK_EXPORT loadModel(Query query); | ||
57 | |||
58 | /** | ||
59 | * Create a new entity. | ||
60 | */ | ||
61 | template <class DomainType> | ||
62 | KAsync::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 | */ | ||
69 | template <class DomainType> | ||
70 | KAsync::Job<void> SINK_EXPORT modify(const DomainType &domainObject); | ||
71 | |||
72 | /** | ||
73 | * Remove an entity. | ||
74 | */ | ||
75 | template <class DomainType> | ||
76 | KAsync::Job<void> SINK_EXPORT remove(const DomainType &domainObject); | ||
77 | |||
78 | /** | ||
79 | * Synchronize data to local cache. | ||
80 | */ | ||
81 | KAsync::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 | */ | ||
88 | KAsync::Job<void> SINK_EXPORT removeDataFromDisk(const QByteArray &resourceIdentifier); | ||
89 | |||
90 | template <class DomainType> | ||
91 | KAsync::Job<DomainType> SINK_EXPORT fetchOne(const Sink::Query &query); | ||
92 | |||
93 | template <class DomainType> | ||
94 | KAsync::Job<QList<typename DomainType::Ptr> > SINK_EXPORT fetchAll(const Sink::Query &query); | ||
95 | |||
96 | template <class DomainType> | ||
97 | KAsync::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 | */ |
32 | class ThreadBoundary : public QObject { | 34 | class SINK_EXPORT ThreadBoundary : public QObject { |
33 | Q_OBJECT | 35 | Q_OBJECT |
34 | public: | 36 | public: |
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 | |||
25 | TypeIndex::TypeIndex(const QByteArray &type) | 28 | TypeIndex::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 | |||
85 | cmake .. | 85 | cmake .. |
86 | make install | 86 | make 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 | ||
17 | A 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 | |||
19 | The 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 | |||
24 | The 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 |
27 | The store is always accessed through a store specific facade, which hides: | 17 | The 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 |
53 | Conflicts can occur at two points: | 43 | Conflicts 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 | ||
58 | In the first case the client is repsonsible to resolve the conflict, in the latter case it's the synchronizer's responsibility. | 48 | In the first case the client is repsonsible to resolve the conflict, in the latter case it's the synchronizer's responsibility. |
59 | A 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. | 49 | A 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 | ||
61 | This 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 | ||
64 | The 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 | |||
66 | The query always retrieves a set of entities matching the query, while not necessarily all properties of the entity need to be populated. | ||
67 | |||
68 | Queries should are declarative to keep the specification simple and to allow the implementation to choose the most efficient execution. | ||
69 | |||
70 | Queries can be kept open (live) to receive updates as the store changes. | ||
71 | |||
72 | ### Query | ||
73 | The 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 | |||
78 | Queryable properties are defined by the [[Domain Types]] above. | ||
79 | |||
80 | ### Query Result | ||
81 | The result is returned directly after running the query in form of a QAbstractItemModel. Each row in the model represents a matching entity. | ||
82 | |||
83 | The model allows to access the domain object directly, or to access individual properties directly via the rows columns. | ||
84 | |||
85 | The model is always populated asynchronously. It is therefore initially empty and will then populate itself gradually, through the regular update mechanisms (rowsInserted). | ||
86 | |||
87 | Tree 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 | |||
89 | If 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 | ||
96 | A 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 | |||
102 | The available comparators are: | ||
103 | |||
104 | * equal | ||
105 | * greater than | ||
106 | * less than | ||
107 | * inclusive range | ||
108 | |||
109 | Value types include: | ||
110 | |||
111 | * Null | ||
112 | * Bool | ||
113 | * Regular Expression | ||
114 | * Substring | ||
115 | * A type-specific literal value (e.g. string, number, date, ..) | ||
116 | |||
117 | Filters can be combined using AND, OR, NOT. | ||
118 | |||
119 | #### Example | ||
120 | ``` | ||
121 | query = { | ||
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 | |||
136 | possible API: | ||
137 | |||
138 | ``` | ||
139 | query.filter().and().property("collection") = "foo" | ||
140 | query.filter().and().or().property("resource") = "res1" | ||
141 | query.filter().and().or().property("resource") = "res2" | ||
142 | query.filter().and().property("start-date") = InclusiveRange(QDateTime, QDateTime) | ||
143 | ``` | ||
144 | |||
145 | The problem is that it is difficult to adjust an individual resource property like that. | ||
146 | |||
147 | ### Usecases ### | ||
148 | Mail: | ||
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 | |||
153 | Todos: | ||
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 | |||
159 | Events: | ||
160 | |||
161 | * All events of calendar X within date-range Y. | ||
162 | |||
163 | Generic: | ||
164 | * entity with identifier X | ||
165 | * all entities of resource X | ||
166 | |||
167 | ### Lazy Loading ### | 51 | ### Lazy Loading ### |
168 | The 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). | 52 | The 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 ### |
175 | Large 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. | 59 | Large 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 ### | ||
178 | Since 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 ## | ||
181 | A notification mechanism is required to inform clients about changes. Running queries will automatically update the result-set if a notification is received. | ||
182 | |||
183 | Note: A notification could supply a hint on what changed, allowing clients to ignore revisions with irrelevant changes. | ||
184 | A 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 | ||
3 | 1. Personal information is stored in multiple sources (address books, email stores, calendar files, ...) | ||
4 | 2. These sources may local, remote or a mix of local and remote | ||
5 | |||
6 | ## Requirements | ||
7 | 1. Local mirrors of these sources must be available to 1..N local clients simultaneously | ||
8 | 2. Local clients must be able to make (or at least request) changes to the data in the local mirrors | ||
9 | 3. Local mirrors must be usable without network, even if the source is remote | ||
10 | 4. Local mirrors must be able to syncronoize local changes to their sources (local or remote) | ||
11 | 5. Local mirrors must be able to syncronize remote changes and propagate those to local clients | ||
12 | 6. Content must be searchable by a number of terms (dates, identities, body text ...) | ||
13 | 7. This must all run with acceptable performance on a moderate consumer-grade desktop system | ||
14 | |||
15 | Nice to haves: | ||
16 | |||
17 | 1. As-close-to-zero-copy-as-possible for data | ||
18 | 2. Simple change notification semantics | ||
19 | 3. Resource-specific syncronization techniques | ||
20 | 4. Data agnostic storage | ||
21 | |||
22 | Immediate goals: | ||
23 | |||
24 | 1. Ease development of new features in existing resources | ||
25 | 2. Ease maintenance of existing resources | ||
26 | 3. Make adding new resources easy | ||
27 | 4. Make adding new types of data or data relations easy | ||
28 | 5. Improve performance relative to existing Akonadi implementation | ||
29 | |||
30 | Long-term goals: | ||
31 | |||
32 | 1. Project view: given a query, show all items in all stores that match that query easily and quickly | ||
33 | |||
34 | Implications 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 | 3 | Sink is a data access layer that additionally handles synchronization with external sources and indexing of data for efficient queries. |
43 | The 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 | ||
6 | The client facing Store API hides all Sink internals from the applications and emulates a unified store that provides data through a standardized interface. | ||
44 | This allows applications to transparently use various data sources with various data source formats. | 7 | This allows applications to transparently use various data sources with various data source formats. |
45 | 8 | ||
46 | ## Resource | 9 | ## Resource |
47 | A 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. | 10 | A 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 |
50 | Each 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. | 13 | Each 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. |
14 | The store consists of revisions with every revision containing one entity. | ||
15 | |||
16 | The store additionally contains various secondary indexes for efficient lookups. | ||
51 | 17 | ||
52 | ## Types | 18 | ## Types |
53 | ### Domain Type | 19 | ### Domain Type |
54 | The domain types exposed in the public interface. | 20 | The 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 |
57 | The 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. | 23 | The 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. |
24 | This 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. | ||
25 | The individual buffer types are specified by the resource and internal to it. Default buffer types exist of all domain types. | ||
26 | |||
27 | ### Commands | ||
28 | Commands are used to modify the store. The resource processes commands that are generated by clients and the synchronizer. | ||
29 | |||
30 | ### Notifications | ||
31 | The resource emits notifications to inform clients of new revisions and other changes. | ||
58 | 32 | ||
59 | ## Mechanisms | 33 | ## Mechanisms |
60 | ### Change Replay | 34 | ### Change Replay |
61 | The 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. | 35 | The 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 |
64 | Each 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. | 38 | The synchronizer executes a periodic synchronization that results in change commands to synchronize the store with the source. |
65 | 39 | The 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) | 42 | The 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 | 44 | Each 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 | ||
3 | 1. Personal information is stored in multiple sources (address books, email stores, calendar files, ...) | ||
4 | 2. These sources may local, remote or a mix of local and remote | ||
5 | |||
6 | ## Requirements | ||
7 | 1. Local mirrors of these sources must be available to 1..N local clients simultaneously | ||
8 | 2. Local clients must be able to make (or at least request) changes to the data in the local mirrors | ||
9 | 3. Local mirrors must be usable without network, even if the source is remote | ||
10 | 4. Local mirrors must be able to syncronoize local changes to their sources (local or remote) | ||
11 | 5. Local mirrors must be able to syncronize remote changes and propagate those to local clients | ||
12 | 6. Content must be searchable by a number of terms (dates, identities, body text ...) | ||
13 | 7. This must all run with acceptable performance on a moderate consumer-grade desktop system | ||
14 | |||
15 | Nice to haves: | ||
16 | |||
17 | 1. As-close-to-zero-copy-as-possible for data | ||
18 | 2. Simple change notification semantics | ||
19 | 3. Resource-specific syncronization techniques | ||
20 | 4. Data agnostic storage | ||
21 | |||
22 | Immediate goals: | ||
23 | |||
24 | 1. Ease development of new features in existing resources | ||
25 | 2. Ease maintenance of existing resources | ||
26 | 3. Make adding new resources easy | ||
27 | 4. Make adding new types of data or data relations easy | ||
28 | 5. Improve performance relative to existing Akonadi implementation | ||
29 | |||
30 | Long-term goals: | ||
31 | |||
32 | 1. Project view: given a query, show all items in all stores that match that query easily and quickly | ||
33 | |||
34 | Implications 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 | 1 | Sink is a data access layer handling synchronization, caching and indexing. |
2 | * Design | 2 | |
3 | * Design Goals | 3 | Discussion of the code should be done on the kde-pim at kde.org mailing list |
4 | * Overview | 4 | or in #kontact on IRC. |
5 | * Client API | 5 | |
6 | * Storage | 6 | Note that all feature development should happen in feature branches, and that |
7 | * Resource | 7 | the mainline development branch is "develop". Master is for releases. It is |
8 | * Facade | 8 | recommended (though not required) to use the ["git flow" tools](https://github.com/nvie/gitflow) to make branched |
9 | * Logging | 9 | development easy (and easy for others to coordinate with). |
10 | * Extending Akoandi Next | 10 | |
11 | * Steps to add support for new types | 11 | For 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 |
15 | This documentation is built using [mkdocs.org](http://mkdocs.org). | 14 | This documentation is built using [mkdocs.org](http://mkdocs.org). |
16 | 15 | ||
17 | Use `mkdocs serve` to run a local webserver to view the docs. | 16 | Use `mkdocs serve` to run a local webserver to view the docs. |
17 | |||
18 | The 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 |
13 | This 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 | ||
22 | Debug areas split the code into sections that can be enabled/disabled as one. | ||
23 | This is supposed to give finer grained control over what is logged or displayed. | ||
24 | |||
25 | Debug areas may align with classes, but don't have to, the should be made so that they are useful. | ||
26 | |||
27 | Areas 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 |
21 | Additionally to the regular message we want: | 42 | Additionally 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 | ||
2 | The 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 | |||
4 | The query always retrieves a set of entities matching the query, while not necessarily all properties of the entity need to be populated. | ||
5 | |||
6 | Queries are declarative to keep the specification simple and to allow the implementation to choose the most efficient execution. | ||
7 | |||
8 | Queries can be kept open (live) to receive updates as the store changes. | ||
9 | |||
10 | ### Query | ||
11 | The 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 | |||
16 | Queryable properties are defined by the [[Domain Types]] above. | ||
17 | |||
18 | ### Query Result | ||
19 | The result is returned directly after running the query in form of a QAbstractItemModel. Each row in the model represents a matching entity. | ||
20 | |||
21 | The model allows to access the domain object directly, or to access individual properties directly via the rows columns. | ||
22 | |||
23 | The model is always populated asynchronously. It is therefore initially empty and will then populate itself gradually, through the regular update mechanisms (rowsInserted). | ||
24 | |||
25 | Tree 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 | |||
27 | If 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 | ||
34 | A 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 | |||
40 | The available comparators are: | ||
41 | |||
42 | * equal | ||
43 | * greater than | ||
44 | * less than | ||
45 | * inclusive range | ||
46 | |||
47 | Value types include: | ||
48 | |||
49 | * Null | ||
50 | * Bool | ||
51 | * Regular Expression | ||
52 | * Substring | ||
53 | * A type-specific literal value (e.g. string, number, date, ..) | ||
54 | |||
55 | Filters can be combined using AND, OR, NOT. | ||
56 | |||
57 | #### Example | ||
58 | ``` | ||
59 | query = { | ||
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 | |||
74 | possible API: | ||
75 | |||
76 | ``` | ||
77 | query.filter().and().property("collection") = "foo" | ||
78 | query.filter().and().or().property("resource") = "res1" | ||
79 | query.filter().and().or().property("resource") = "res2" | ||
80 | query.filter().and().property("start-date") = InclusiveRange(QDateTime, QDateTime) | ||
81 | ``` | ||
82 | |||
83 | The problem is that it is difficult to adjust an individual resource property like that. | ||
84 | |||
85 | ### Usecases ### | ||
86 | Mail: | ||
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 | |||
91 | Todos: | ||
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 | |||
97 | Events: | ||
98 | |||
99 | * All events of calendar X within date-range Y. | ||
100 | |||
101 | Generic: | ||
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 |
8 | The synchronizer process is responsible for processing all commands, executing synchronizations with the source, and replaying changes to the source. | 8 | The synchronizer process is responsible for processing all commands, executing synchronizations with the source, and replaying changes to the source. |
9 | 9 | ||
10 | Processing of commands happens in the pipeline which executes all preprocessors ebfore the entity is persisted. | 10 | Processing 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 | 19 | A resource can: |
20 | |||
21 | * provide a full mirror of the source. | ||
22 | * provide metadata for efficient access to the source. | ||
23 | |||
24 | In the former case the local mirror is fully functional locally and changes can be replayed to the source once a connection is established again. | ||
25 | It 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 | ||
20 | Preprocessors 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. | 28 | Preprocessors 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 | ||
22 | Usecases: | 30 | Usecases: |
@@ -33,16 +41,29 @@ The following kinds of preprocessors exist: | |||
33 | 41 | ||
34 | Preprocessors 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. | 42 | Preprocessors 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 |
41 | Commands are processed in batches. Each preprocessor thus has the following workflow: | 49 | Commands 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 | ||
55 | Most preprocessors will likely be used by several resources, and are either completely generic, or domain specific (such as only for mail). | ||
56 | It is therefore desirable to have default implementations for common preprocessors that are ready to be plugged in. | ||
57 | |||
58 | The domain type adaptors provide a generic interface to access most properties of the entities, on top of which generic preprocessors can be implemented. | ||
59 | It is that way trivial to i.e. implement a preprocessor that populates a hierarchy index of collections. | ||
60 | |||
61 | ### Preprocessors generating additional entities | ||
62 | A 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 | |||
64 | In such a case the preprocessor must invoke the complete pipeline for the new entity. | ||
65 | |||
66 | |||
46 | ## Indexes | 67 | ## Indexes |
47 | Most indexes are implemented as preprocessors to guarantee that they are always updated together with the data. | 68 | Most 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 | ||
90 | Since 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 | ||
107 | The indexes status information can be recorded using the latest revision the index has been updated with. | 131 | The indexes status information can be recorded using the latest revision the index has been updated with. |
108 | 132 | ||
109 | ## Generic Preprocessors | ||
110 | Most preprocessors will likely be used by several resources, and are either completely generic, or domain specific (such as only for mail). | ||
111 | It is therefore desirable to have default implementations for common preprocessors that are ready to be plugged in. | ||
112 | |||
113 | The domain type adaptors provide a generic interface to access most properties of the entities, on top of which generic preprocessors can be implemented. | ||
114 | It is that way trivial to i.e. implement a preprocessor that populates a hierarchy index of collections. | ||
115 | |||
116 | ## Preprocessors generating additional entities | ||
117 | A 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 | |||
119 | In such a case the preprocessor must invoke the complete pipeline for the new entity. | ||
120 | |||
121 | # Pipeline | 133 | # Pipeline |
122 | A 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. | 134 | A 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: | 137 | The 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 | ||
129 | The 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. | 142 | The 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 | ||
159 | To replay local changes to the source the synchronizer replays all revisions of the store and maintains the current replay state in the synchronization store. | ||
160 | Changes that already come from the source via synchronizer are not replayed to the source again. | ||
161 | |||
145 | # Testing / Inspection | 162 | # Testing / Inspection |
146 | Resources 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. | 163 | Resources 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 | ||
148 | To 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. | 165 | To 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 | ||
2 | Access 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 | ``` | ||
4 | Event { | ||
5 | startDate: QDateTime | ||
6 | subject: QString | ||
7 | ... | ||
8 | } | ||
9 | ``` | ||
10 | |||
11 | This 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 | |||
13 | Clients 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 |
16 | The storage model is simple: | 2 | The 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 | ||
43 | Denormalized: | 29 | Denormalized: |
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 | ``` |
49 | Mail { | 34 | Mail { |
@@ -55,7 +40,7 @@ Mail { | |||
55 | Normalized: | 40 | Normalized: |
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 | ``` |
60 | Event { | 45 | Event { |
61 | id | 46 | id |
@@ -101,7 +86,7 @@ The resource can be effectively removed from disk (besides configuration), | |||
101 | by deleting the directories matching `$RESOURCE_IDENTIFIER*` and everything they contain. | 86 | by 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. | 89 | 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. |
105 | 90 | ||
106 | ### Revisions | 91 | ### Revisions |
107 | Every operation (create/delete/modify), leads to a new revision. The revision is an ever increasing number for the complete store. | 92 | Every 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: | |||
167 | The 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. | 152 | The 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. |
168 | The 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. | 153 | The 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 | ||
155 | A 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 |
171 | By 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 | 158 | By 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 |
172 | SQL 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. | 159 | SQL 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) | |||
3 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | 3 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) |
4 | 4 | ||
5 | add_executable(${PROJECT_NAME} main.cpp console.cpp) | 5 | add_executable(${PROJECT_NAME} main.cpp console.cpp) |
6 | target_link_libraries(${PROJECT_NAME} sinkcommon) | 6 | target_link_libraries(${PROJECT_NAME} sink) |
7 | qt5_use_modules(${PROJECT_NAME} Widgets Network) | 7 | qt5_use_modules(${PROJECT_NAME} Widgets Network) |
8 | install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) | 8 | install(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 | */ |
39 | class StoreBase { | 39 | class StoreBase { |
40 | public: | 40 | public: |
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}) | |||
7 | add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp dummystore.cpp) | 7 | add_library(${PROJECT_NAME} SHARED facade.cpp resourcefactory.cpp domainadaptor.cpp dummystore.cpp) |
8 | generate_flatbuffers(${PROJECT_NAME} dummycalendar) | 8 | generate_flatbuffers(${PROJECT_NAME} dummycalendar) |
9 | qt5_use_modules(${PROJECT_NAME} Core Network) | 9 | qt5_use_modules(${PROJECT_NAME} Core Network) |
10 | target_link_libraries(${PROJECT_NAME} sinkcommon) | 10 | target_link_libraries(${PROJECT_NAME} sink) |
11 | 11 | ||
12 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) | 12 | install(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 | ||
96 | void 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) | 96 | void 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 | ||
106 | KAsync::Job<void> DummyResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) | 111 | KAsync::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 | ||
136 | void DummyResource::removeDataFromDisk() | ||
137 | { | ||
138 | removeFromDisk(mResourceInstanceIdentifier); | ||
139 | } | ||
140 | |||
131 | void DummyResource::removeFromDisk(const QByteArray &instanceIdentifier) | 141 | void 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; |
44 | private: | 45 | private: |
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) | |||
8 | add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp) | 8 | add_library(${PROJECT_NAME} SHARED facade.cpp maildirresource.cpp domainadaptor.cpp) |
9 | # generate_flatbuffers(${PROJECT_NAME} dummycalendar) | 9 | # generate_flatbuffers(${PROJECT_NAME} dummycalendar) |
10 | qt5_use_modules(${PROJECT_NAME} Core Network) | 10 | qt5_use_modules(${PROJECT_NAME} Core Network) |
11 | target_link_libraries(${PROJECT_NAME} sinkcommon maildir KF5::Mime) | 11 | target_link_libraries(${PROJECT_NAME} sink maildir KF5::Mime) |
12 | 12 | ||
13 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${SINK_RESOURCE_PLUGINS_PATH}) | 13 | install(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 | ||
24 | MaildirResourceMailFacade::MaildirResourceMailFacade(const QByteArray &instanceIdentifier) | 28 | MaildirResourceMailFacade::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 | ||
29 | MaildirResourceMailFacade::~MaildirResourceMailFacade() | 54 | MaildirResourceMailFacade::~MaildirResourceMailFacade() |
30 | { | 55 | { |
31 | } | 56 | } |
32 | 57 | ||
58 | QPair<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 | ||
34 | MaildirResourceFolderFacade::MaildirResourceFolderFacade(const QByteArray &instanceIdentifier) | 64 | MaildirResourceFolderFacade::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 | |||
26 | public: | 26 | public: |
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 | ||
31 | class MaildirResourceFolderFacade : public Sink::GenericFacade<Sink::ApplicationDomain::Folder> | 32 | class 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 | ||
563 | QByteArray Maildir::readEntryHeadersFromFile(const QString& file) const | 569 | QString Maildir::getKeyFromFile(const QString& file) |
570 | { | ||
571 | return Maildir::Private::stripFlags(file.split('/').last()); | ||
572 | } | ||
573 | |||
574 | QString 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 | |||
582 | QByteArray 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) | 715 | QString 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 | |||
773 | Maildir::Flags Maildir::readEntryFlags(const QString& key) | 798 | Maildir::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 | |||
264 | private: | 278 | private: |
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 | |||
49 | MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) | 52 | MaildirResource::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 | |||
132 | void MaildirResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path) | 135 | void 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 | ||
192 | KAsync::Job<void> MaildirResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) | 204 | KAsync::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; | |||
32 | class MaildirFolderAdaptorFactory; | 32 | class 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 | */ |
42 | class MaildirResource : public Sink::GenericResource | 45 | class 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 | } | ||
@@ -1,14 +1,19 @@ | |||
1 | site_name: Sink | 1 | site_name: Sink |
2 | pages: | 2 | pages: |
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 | ||
14 | theme: readthedocs | 19 | theme: 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 | ||
3 | find_package(Readline REQUIRED) | 3 | find_package(Readline REQUIRED) |
4 | 4 | ||
5 | |||
6 | set(sink_cli_SRCS | 5 | set(sink_cli_SRCS |
7 | main.cpp | 6 | main.cpp |
8 | syntaxtree.cpp | 7 | syntaxtree.cpp |
@@ -24,6 +23,6 @@ set(sink_cli_SRCS | |||
24 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) | 23 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) |
25 | 24 | ||
26 | add_executable(${PROJECT_NAME} ${sink_cli_SRCS}) | 25 | add_executable(${PROJECT_NAME} ${sink_cli_SRCS}) |
27 | target_link_libraries(${PROJECT_NAME} Qt5::Core ${Readline_LIBRARY} sinkcommon) | 26 | target_link_libraries(${PROJECT_NAME} Qt5::Core ${Readline_LIBRARY} sink) |
28 | install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) | 27 | install(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 | */ |
48 | class StoreBase { | 48 | class StoreBase { |
49 | public: | 49 | public: |
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 | ||
29 | namespace CoreSyntax | 30 | namespace CoreSyntax |
30 | { | 31 | { |
@@ -32,7 +33,6 @@ namespace CoreSyntax | |||
32 | bool exit(const QStringList &, State &) | 33 | bool exit(const QStringList &, State &) |
33 | { | 34 | { |
34 | ::exit(0); | 35 | ::exit(0); |
35 | return true; | ||
36 | } | 36 | } |
37 | 37 | ||
38 | bool showHelp(const QStringList &commands, State &state) | 38 | bool 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 | ||
167 | bool 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 | |||
183 | bool 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 | |||
199 | bool 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 | |||
167 | Syntax::List syntax() | 210 | Syntax::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 | ||
9 | add_executable(${PROJECT_NAME} ${sinksynchronizer_SRCS}) | 9 | add_executable(${PROJECT_NAME} ${sinksynchronizer_SRCS}) |
10 | target_link_libraries(${PROJECT_NAME} sinkcommon KF5::Async) | 10 | target_link_libraries(${PROJECT_NAME} sink KF5::Async) |
11 | qt5_use_modules(${PROJECT_NAME} Widgets Network) | 11 | qt5_use_modules(${PROJECT_NAME} Widgets Network) |
12 | install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) | 12 | install(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 | |||
29 | void crashHandler(int sig) { | 32 | void 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) |
20 | endmacro(manual_tests) | 20 | endmacro(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) |
30 | endmacro(auto_tests) | 30 | endmacro(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 ¬ification) { | ||
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 | ||
232 | private: | 269 | private: |
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 @@ | |||
19 | class DummyResourceTest : public QObject | 20 | class DummyResourceTest : public QObject |
20 | { | 21 | { |
21 | Q_OBJECT | 22 | Q_OBJECT |
23 | |||
24 | QTime time; | ||
25 | |||
22 | private Q_SLOTS: | 26 | private 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 | |||
26 | add_library(lib${PROJECT_NAME} SHARED ${lib_SRCS}) | 26 | add_library(lib${PROJECT_NAME} SHARED ${lib_SRCS}) |
27 | generate_export_header(lib${PROJECT_NAME} BASE_NAME HAWD EXPORT_FILE_NAME hawd_export.h) | 27 | generate_export_header(lib${PROJECT_NAME} BASE_NAME HAWD EXPORT_FILE_NAME hawd_export.h) |
28 | qt5_use_modules(lib${PROJECT_NAME} Core) | 28 | qt5_use_modules(lib${PROJECT_NAME} Core) |
29 | target_link_libraries(lib${PROJECT_NAME} sinkcommon) | 29 | target_link_libraries(lib${PROJECT_NAME} sink) |
30 | if (LIBGIT2_FOUND) | 30 | if (LIBGIT2_FOUND) |
31 | target_link_libraries(lib${PROJECT_NAME} ${LIBGIT2_LIBRARIES}) | 31 | target_link_libraries(lib${PROJECT_NAME} ${LIBGIT2_LIBRARIES}) |
32 | endif(LIBGIT2_FOUND) | 32 | endif(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 | |||
216 | Dataset::Dataset(const QString &name, const State &state) | 216 | Dataset::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 | ||
55 | flatbuffers::FlatBufferBuilder &createEvent(flatbuffers::FlatBufferBuilder &entityFbb, const QString &s = QString("summary")) | 55 | flatbuffers::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); |