/* * Copyright (C) 2015 Christian Mollekopf * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "store.h" #include #include #include #include #include #include "resourceaccess.h" #include "commands.h" #include "resourcefacade.h" #include "definitions.h" #include "resourceconfig.h" #include "facadefactory.h" #include "modelresult.h" #include "storage.h" #include "log.h" #undef DEBUG_AREA #define DEBUG_AREA "client.store" namespace Sink { QString Store::storageLocation() { return Sink::storageLocation(); } QString Store::getTemporaryFilePath() { return Sink::temporaryFileLocation() + "/" + QUuid::createUuid().toString(); } /* * Returns a map of resource instance identifiers and resource type */ static QMap getResources(const QList &resourceFilter, const QByteArray &type = QByteArray()) { QMap resources; // Return the global resource (signified by an empty name) for types that don't belong to a specific resource if (type == "sinkresource" || type == "sinkaccount" || type == "identity") { resources.insert("", ""); return resources; } const auto configuredResources = ResourceConfig::getResources(); if (resourceFilter.isEmpty()) { for (const auto &res : configuredResources.keys()) { // TODO filter by entity type resources.insert(res, configuredResources.value(res)); } } else { for (const auto &res : resourceFilter) { if (configuredResources.contains(res)) { resources.insert(res, configuredResources.value(res)); } else { qWarning() << "Resource is not existing: " << res; } } } Trace() << "Found resources: " << resources; return resources; } template QSharedPointer Store::loadModel(Query query) { Trace() << "Query: " << ApplicationDomain::getTypeName(); Trace() << " Requested: " << query.requestedProperties; Trace() << " Filter: " << query.propertyFilter; Trace() << " Parent: " << query.parentProperty; Trace() << " Ids: " << query.ids; Trace() << " IsLive: " << query.liveQuery; Trace() << " Sorting: " << query.sortProperty; auto model = QSharedPointer>::create(query, query.requestedProperties); //* Client defines lifetime of model //* The model lifetime defines the duration of live-queries //* The facade needs to life for the duration of any calls being made (assuming we get rid of any internal callbacks //* The emitter needs to live or the duration of query (respectively, the model) //* The result provider needs to live for as long as results are provided (until the last thread exits). // Query all resources and aggregate results auto resources = getResources(query.resources, ApplicationDomain::getTypeName()); auto aggregatingEmitter = AggregatingResultEmitter::Ptr::create(); model->setEmitter(aggregatingEmitter); KAsync::iterate(resources.keys()) .template each([query, aggregatingEmitter, resources](const QByteArray &resourceInstanceIdentifier, KAsync::Future &future) { const auto resourceType = resources.value(resourceInstanceIdentifier); auto facade = FacadeFactory::instance().getFacade(resourceType, resourceInstanceIdentifier); if (facade) { Trace() << "Trying to fetch from resource " << resourceInstanceIdentifier; auto result = facade->load(query); aggregatingEmitter->addEmitter(result.second); result.first.template then([&future]() { future.setFinished(); }).exec(); } else { Trace() << "Couldn' find a facade for " << resourceInstanceIdentifier; // Ignore the error and carry on future.setFinished(); } }) .exec(); model->fetchMore(QModelIndex()); return model; } template static std::shared_ptr> getFacade(const QByteArray &resourceInstanceIdentifier) { const auto type = ApplicationDomain::getTypeName(); if (type == "sinkresource" || type == "sinkaccount") { if (auto facade = FacadeFactory::instance().getFacade("", "")) { return facade; } } if (auto facade = FacadeFactory::instance().getFacade(ResourceConfig::getResourceType(resourceInstanceIdentifier), resourceInstanceIdentifier)) { return facade; } return std::make_shared>(); } template KAsync::Job Store::create(const DomainType &domainObject) { // Potentially move to separate thread as well auto facade = getFacade(domainObject.resourceInstanceIdentifier()); return facade->create(domainObject).template then([facade]() {}, [](int errorCode, const QString &error) { Warning() << "Failed to create"; }); } template KAsync::Job Store::modify(const DomainType &domainObject) { // Potentially move to separate thread as well auto facade = getFacade(domainObject.resourceInstanceIdentifier()); return facade->modify(domainObject).template then([facade]() {}, [](int errorCode, const QString &error) { Warning() << "Failed to modify"; }); } template KAsync::Job Store::remove(const DomainType &domainObject) { // Potentially move to separate thread as well auto facade = getFacade(domainObject.resourceInstanceIdentifier()); return facade->remove(domainObject).template then([facade]() {}, [](int errorCode, const QString &error) { Warning() << "Failed to remove"; }); } KAsync::Job Store::removeDataFromDisk(const QByteArray &identifier) { // All databases are going to become invalid, nuke the environments // TODO: all clients should react to a notification the resource Sink::Storage::clearEnv(); Trace() << "Remove data from disk " << identifier; auto time = QSharedPointer::create(); time->start(); auto resourceAccess = ResourceAccessFactory::instance().getAccess(identifier, ResourceConfig::getResourceType(identifier)); resourceAccess->open(); return resourceAccess->sendCommand(Sink::Commands::RemoveFromDiskCommand) .then([resourceAccess, time]() { Trace() << "Remove from disk complete." << Log::TraceTime(time->elapsed()); }); } KAsync::Job Store::synchronize(const Sink::Query &query) { Trace() << "synchronize" << query.resources; auto resources = getResources(query.resources).keys(); return KAsync::iterate(resources) .template each([query](const QByteArray &resource, KAsync::Future &future) { Trace() << "Synchronizing " << resource; auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource, ResourceConfig::getResourceType(resource)); resourceAccess->open(); resourceAccess->synchronizeResource(true, false).then([&future, resourceAccess]() { future.setFinished(); }).exec(); }); } template KAsync::Job Store::fetchOne(const Sink::Query &query) { return KAsync::start([query](KAsync::Future &future) { // FIXME We could do this more elegantly if composed jobs would have the correct type (In that case we'd simply return the value from then continuation, and could avoid the // outer job entirely) fetch(query, 1) .template then>( [&future](const QList &list) { future.setValue(*list.first()); future.setFinished(); }, [&future](int errorCode, const QString &errorMessage) { future.setError(errorCode, errorMessage); future.setFinished(); }) .exec(); }); } template KAsync::Job> Store::fetchAll(const Sink::Query &query) { return fetch(query); } template KAsync::Job> Store::fetch(const Sink::Query &query, int minimumAmount) { auto model = loadModel(query); auto list = QSharedPointer>::create(); auto context = QSharedPointer::create(); return KAsync::start>([model, list, context, minimumAmount](KAsync::Future> &future) { if (model->rowCount() >= 1) { for (int i = 0; i < model->rowCount(); i++) { list->append(model->index(i, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).template value()); } } else { QObject::connect(model.data(), &QAbstractItemModel::rowsInserted, context.data(), [model, &future, list](const QModelIndex &index, int start, int end) { for (int i = start; i <= end; i++) { list->append(model->index(i, 0, QModelIndex()).data(Sink::Store::DomainObjectRole).template value()); } }); QObject::connect(model.data(), &QAbstractItemModel::dataChanged, context.data(), [model, &future, list, minimumAmount](const QModelIndex &, const QModelIndex &, const QVector &roles) { if (roles.contains(ModelResult::ChildrenFetchedRole)) { if (list->size() < minimumAmount) { future.setError(1, "Not enough values."); } else { future.setValue(*list); } future.setFinished(); } }); } if (model->data(QModelIndex(), ModelResult::ChildrenFetchedRole).toBool()) { if (list->size() < minimumAmount) { future.setError(1, "Not enough values."); } else { future.setValue(*list); } future.setFinished(); } }); } #define REGISTER_TYPE(T) \ template KAsync::Job Store::remove(const T &domainObject); \ template KAsync::Job Store::create(const T &domainObject); \ template KAsync::Job Store::modify(const T &domainObject); \ template QSharedPointer Store::loadModel(Query query); \ template KAsync::Job Store::fetchOne(const Query &); \ template KAsync::Job> Store::fetchAll(const Query &); \ template KAsync::Job> Store::fetch(const Query &, int); REGISTER_TYPE(ApplicationDomain::Event); REGISTER_TYPE(ApplicationDomain::Mail); REGISTER_TYPE(ApplicationDomain::Folder); REGISTER_TYPE(ApplicationDomain::SinkResource); REGISTER_TYPE(ApplicationDomain::SinkAccount); REGISTER_TYPE(ApplicationDomain::Identity); } // namespace Sink