summaryrefslogtreecommitdiffstats
path: root/common/entitystorage.h
blob: 6a41e0eca30e1ea69add42768135bef099cb3645 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
 * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 */
#pragma once

#include "clientapi.h"

#include <QByteArray>

#include "domainadaptor.h"
#include "entitybuffer.h"
#include "log.h"
#include "storage.h"
#include "resultset.h"

/**
 * Wraps storage, entity adaptor factory and indexes into one.
 */
template <typename DomainType>
class EntityStorage
{

public:
    EntityStorage(const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory)
        : mResourceInstanceIdentifier(instanceIdentifier),
        mDomainTypeAdaptorFactory(adaptorFactory)
    {

    }

private:
    static void scan(const QSharedPointer<Akonadi2::Storage> &storage, const QByteArray &key, std::function<bool(const QByteArray &key, const Akonadi2::Entity &entity)> callback)
    {
        storage->scan(key, [=](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool {
            //Skip internals
            if (Akonadi2::Storage::isInternalKey(keyValue, keySize)) {
                return true;
            }

            //Extract buffers
            Akonadi2::EntityBuffer buffer(dataValue, dataSize);

            //FIXME implement buffer.isValid()
            // const auto resourceBuffer = Akonadi2::EntityBuffer::readBuffer<DummyEvent>(buffer.entity().resource());
            // const auto localBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::ApplicationDomain::Buffer::Event>(buffer.entity().local());
            // const auto metadataBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::Metadata>(buffer.entity().metadata());

            // if ((!resourceBuffer && !localBuffer) || !metadataBuffer) {
            //     qWarning() << "invalid buffer " << QByteArray::fromRawData(static_cast<char*>(keyValue), keySize);
            //     return true;
            // }
            return callback(QByteArray::fromRawData(static_cast<char*>(keyValue), keySize), buffer.entity());
        },
        [](const Akonadi2::Storage::Error &error) {
            qWarning() << "Error during query: " << error.message;
        });
    }

    static void readValue(const QSharedPointer<Akonadi2::Storage> &storage, const QByteArray &key, const std::function<void(const typename DomainType::Ptr &)> &resultCallback, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory, const QByteArray &instanceIdentifier)
    {
        scan(storage, key, [=](const QByteArray &key, const Akonadi2::Entity &entity) {
            const auto metadataBuffer = Akonadi2::EntityBuffer::readBuffer<Akonadi2::Metadata>(entity.metadata());
            qint64 revision = metadataBuffer ? metadataBuffer->revision() : -1;
            //This only works for a 1:1 mapping of resource to domain types.
            //Not i.e. for tags that are stored as flags in each entity of an imap store.
            //additional properties that don't have a 1:1 mapping (such as separately stored tags),
            //could be added to the adaptor
            auto domainObject = QSharedPointer<DomainType>::create(instanceIdentifier, key, revision, adaptorFactory->createAdaptor(entity));
            resultCallback(domainObject);
            return true;
        });
    }

    static ResultSet fullScan(const QSharedPointer<Akonadi2::Storage> &storage)
    {
        //TODO use a result set with an iterator, to read values on demand
        QVector<QByteArray> keys;
        scan(storage, QByteArray(), [=, &keys](const QByteArray &key, const Akonadi2::Entity &) {
            keys << key;
            return true;
        });
        Trace() << "Full scan found " << keys.size() << " results";
        return ResultSet(keys);
    }

    static ResultSet filteredSet(const ResultSet &resultSet, const std::function<bool(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject)> &filter, const QSharedPointer<Akonadi2::Storage> &storage, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory, qint64 baseRevision, qint64 topRevision, const QByteArray &instanceIdentifier)
    {
        auto resultSetPtr = QSharedPointer<ResultSet>::create(resultSet);

        //Read through the source values and return whatever matches the filter
        std::function<bool(std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &)>)> generator = [resultSetPtr, storage, adaptorFactory, filter, instanceIdentifier](std::function<void(const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &)> callback) -> bool {
            while (resultSetPtr->next()) {
                readValue(storage, resultSetPtr->id(), [filter, callback](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject) {
                    if (filter(domainObject)) {
                        callback(domainObject);
                    }
                }, adaptorFactory, instanceIdentifier);
            }
            return false;
        };
        return ResultSet(generator);
    }

    static ResultSet getResultSet(const Akonadi2::Query &query, const QSharedPointer<Akonadi2::Storage> &storage, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory, const QByteArray &resourceInstanceIdentifier, qint64 baseRevision, qint64 topRevision)
    {
        QSet<QByteArray> appliedFilters;
        ResultSet resultSet = Akonadi2::ApplicationDomain::TypeImplementation<DomainType>::queryIndexes(query, resourceInstanceIdentifier, appliedFilters, qMakePair(baseRevision, topRevision));
        const auto remainingFilters = query.propertyFilter.keys().toSet() - appliedFilters;

        //We do a full scan if there were no indexes available to create the initial set.
        if (appliedFilters.isEmpty()) {
            resultSet = fullScan(storage);
        }

        auto filter = [remainingFilters, query, baseRevision, topRevision](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject) -> bool {
            if (topRevision > 0) {
                Trace() << "filtering by revision " << domainObject->revision();
                if (domainObject->revision() < baseRevision || domainObject->revision() > topRevision) {
                    return false;
                }
            }
            for (const auto &filterProperty : remainingFilters) {
                //TODO implement other comparison operators than equality
                if (domainObject->getProperty(filterProperty) != query.propertyFilter.value(filterProperty)) {
                    return false;
                }
            }
            return true;
        };

        return filteredSet(resultSet, filter, storage, adaptorFactory, baseRevision, topRevision, resourceInstanceIdentifier);
    }

public:

    void read(const Akonadi2::Query &query, const QPair<qint64, qint64> &revisionRange, const QSharedPointer<Akonadi2::ResultProvider<typename DomainType::Ptr> > &resultProvider)
    {
        auto storage = QSharedPointer<Akonadi2::Storage>::create(Akonadi2::Store::storageLocation(), mResourceInstanceIdentifier);
        storage->setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) {
            Warning() << "Error during query: " << error.store << error.message;
        });

        storage->startTransaction(Akonadi2::Storage::ReadOnly);
        //TODO start transaction on indexes as well

        Log() << "Querying" << revisionRange.first << revisionRange.second;
        auto resultSet = getResultSet(query, storage, mDomainTypeAdaptorFactory, mResourceInstanceIdentifier, revisionRange.first, revisionRange.second);
        auto resultCallback = std::bind(&Akonadi2::ResultProvider<typename DomainType::Ptr>::add, resultProvider, std::placeholders::_1);
        while(resultSet.next([resultCallback](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &value) -> bool {
            resultCallback(Akonadi2::ApplicationDomain::ApplicationDomainType::getInMemoryRepresentation<DomainType>(*value));
            return true;
        })){};
        //TODO replay removals and modifications
        storage->abortTransaction();
    }

private:
    DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory;
    QByteArray mResourceInstanceIdentifier;
};