File indexing completed on 2024-05-12 05:26:04

0001 /*
0002  * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
0003  *
0004  *   This program is free software; you can redistribute it and/or modify
0005  *   it under the terms of the GNU General Public License as published by
0006  *   the Free Software Foundation; either version 2 of the License, or
0007  *   (at your option) any later version.
0008  *
0009  *   This program is distributed in the hope that it will be useful,
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *   GNU General Public License for more details.
0013  *
0014  *   You should have received a copy of the GNU General Public License
0015  *   along with this program; if not, write to the
0016  *   Free Software Foundation, Inc.,
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
0018  */
0019 #include "resourcefacade.h"
0020 
0021 #include "resourceconfig.h"
0022 #include "query.h"
0023 #include "definitions.h"
0024 #include "store.h"
0025 #include "resourceaccess.h"
0026 #include "resource.h"
0027 #include "facadefactory.h"
0028 
0029 using namespace Sink;
0030 
0031 template<typename DomainType>
0032 ConfigNotifier LocalStorageFacade<DomainType>::sConfigNotifier;
0033 
0034 static void applyConfig(ConfigStore &configStore, const QByteArray &id, ApplicationDomain::ApplicationDomainType &object, const QByteArrayList &requestedProperties)
0035 {
0036     const auto configurationValues = configStore.get(id);
0037     for (auto it = configurationValues.constBegin(); it != configurationValues.constEnd(); it++) {
0038         object.setProperty(it.key(), it.value());
0039     }
0040     //Populate the object with dummy values for non-available but requested properties.
0041     //This avoid a warning about non-existing properties in bufferadaptor.h
0042     if (!requestedProperties.isEmpty()) {
0043         for (const auto &requested: requestedProperties) {
0044             if (!object.hasProperty(requested)) {
0045                 object.setProperty(requested, QVariant{});
0046             }
0047         }
0048     }
0049 }
0050 
0051 template <typename DomainType>
0052 static typename DomainType::Ptr readFromConfig(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
0053 {
0054     auto object = DomainType::Ptr::create(id);
0055     applyConfig(configStore, id, *object, requestedProperties);
0056     return object;
0057 }
0058 
0059 template <>
0060 typename ApplicationDomain::SinkAccount::Ptr readFromConfig<ApplicationDomain::SinkAccount>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
0061 {
0062     auto object = ApplicationDomain::SinkAccount::Ptr::create(id);
0063     object->setProperty(ApplicationDomain::SinkAccount::AccountType::name, type);
0064     applyConfig(configStore, id, *object, requestedProperties);
0065     return object;
0066 }
0067 
0068 template <>
0069 typename ApplicationDomain::SinkResource::Ptr readFromConfig<ApplicationDomain::SinkResource>(ConfigStore &configStore, const QByteArray &id, const QByteArray &type, const QByteArrayList &requestedProperties)
0070 {
0071     auto object = ApplicationDomain::SinkResource::Ptr::create(id);
0072     object->setProperty(ApplicationDomain::SinkResource::ResourceType::name, type);
0073     //Apply the capabilities where we have capabilities
0074     if (!ApplicationDomain::isGlobalType(type)) {
0075         if (auto res = ResourceFactory::load(type)) {
0076             object->setCapabilities(res->capabilities());
0077         }
0078     }
0079     applyConfig(configStore, id, *object, requestedProperties);
0080     return object;
0081 }
0082 
0083 static bool matchesFilter(const QHash<QByteArrayList, Query::Comparator> &filter, const ApplicationDomain::ApplicationDomainType &entity)
0084 {
0085     for (const auto &filterProperty : filter.keys()) {
0086         if (filterProperty[0] == ApplicationDomain::SinkResource::ResourceType::name) {
0087             continue;
0088         }
0089         if (!filter.value(filterProperty).matches(entity.getProperty(filterProperty[0]))) {
0090             return false;
0091         }
0092     }
0093     return true;
0094 }
0095 
0096 template<typename DomainType>
0097 LocalStorageQueryRunner<DomainType>::LocalStorageQueryRunner(const Query &query, const QByteArray &identifier, const QByteArray &typeName, ConfigNotifier &configNotifier, const Sink::Log::Context &ctx)
0098     : mResultProvider(new ResultProvider<typename DomainType::Ptr>), mConfigStore(identifier, typeName), mGuard(new QObject), mLogCtx(ctx.subContext("config"))
0099 {
0100 
0101     auto matchesTypeAndIds = [query, this] (const QByteArray &type, const QByteArray &id) {
0102         if (query.hasFilter(ApplicationDomain::SinkResource::ResourceType::name) && query.getFilter(ApplicationDomain::SinkResource::ResourceType::name).value.toByteArray() != type) {
0103             SinkTraceCtx(mLogCtx) << "Skipping due to type.";
0104             return false;
0105         }
0106         if (!query.ids().isEmpty() && !query.ids().contains(id)) {
0107             return false;
0108         }
0109         return true;
0110     };
0111 
0112     QObject *guard = new QObject;
0113     mResultProvider->setFetcher([this, query, matchesTypeAndIds]() {
0114         const auto entries = mConfigStore.getEntries();
0115         for (const auto &res : entries.keys()) {
0116             const auto type = entries.value(res);
0117             if (!matchesTypeAndIds(type, res)){
0118                 continue;
0119             }
0120             auto entity = readFromConfig<DomainType>(mConfigStore, res, type, query.requestedProperties);
0121             if (!matchesFilter(query.getBaseFilters(), *entity)){
0122                 SinkTraceCtx(mLogCtx) << "Skipping due to filter." << res;
0123                 continue;
0124             }
0125             SinkTraceCtx(mLogCtx) << "Found match " << res;
0126             updateStatus(*entity);
0127             mResultProvider->add(entity);
0128         }
0129         // TODO initialResultSetComplete should be implicit
0130         mResultProvider->initialResultSetComplete(true);
0131         mResultProvider->complete();
0132     });
0133     if (query.liveQuery()) {
0134         {
0135             auto ret = QObject::connect(&configNotifier, &ConfigNotifier::added, guard, [this, query, matchesTypeAndIds](const ApplicationDomain::ApplicationDomainType::Ptr &entry, const QByteArray &type) {
0136                 auto entity = entry.staticCast<DomainType>();
0137                 if (!matchesTypeAndIds(type, entity->identifier())){
0138                     return;
0139                 }
0140                 if (!matchesFilter(query.getBaseFilters(), *entity)){
0141                     return;
0142                 }
0143                 SinkTraceCtx(mLogCtx) << "A new resource has been added: " << entity->identifier();
0144                 updateStatus(*entity);
0145                 mResultProvider->add(entity);
0146             });
0147             Q_ASSERT(ret);
0148         }
0149         {
0150             auto ret = QObject::connect(&configNotifier, &ConfigNotifier::modified, guard, [this, query, matchesTypeAndIds](const ApplicationDomain::ApplicationDomainType::Ptr &entry, const QByteArray &type) {
0151                 auto entity = entry.staticCast<DomainType>();
0152                 if (!matchesTypeAndIds(type, entity->identifier())){
0153                     return;
0154                 }
0155                 if (!matchesFilter(query.getBaseFilters(), *entity)){
0156                     return;
0157                 }
0158                 updateStatus(*entity);
0159                 mResultProvider->modify(entity);
0160             });
0161             Q_ASSERT(ret);
0162         }
0163         {
0164             auto ret = QObject::connect(&configNotifier, &ConfigNotifier::removed, guard, [this](const ApplicationDomain::ApplicationDomainType::Ptr &entry) {
0165                 mResultProvider->remove(entry.staticCast<DomainType>());
0166             });
0167             Q_ASSERT(ret);
0168         }
0169     }
0170     mResultProvider->onDone([=]() { delete guard; delete this; });
0171 }
0172 
0173 template<typename DomainType>
0174 QObject *LocalStorageQueryRunner<DomainType>::guard() const
0175 {
0176     return mGuard.get();
0177 }
0178 
0179 template<typename DomainType>
0180 void LocalStorageQueryRunner<DomainType>::updateStatus(DomainType &entity)
0181 {
0182     if (mStatusUpdater) {
0183         mStatusUpdater(entity);
0184     }
0185 }
0186 
0187 template<typename DomainType>
0188 void LocalStorageQueryRunner<DomainType>::setStatusUpdater(const std::function<void(DomainType &)> &updater)
0189 {
0190     mStatusUpdater = updater;
0191 }
0192 
0193 template<typename DomainType>
0194 void LocalStorageQueryRunner<DomainType>::statusChanged(const QByteArray &identifier)
0195 {
0196     SinkTraceCtx(mLogCtx) << "Status changed " << identifier;
0197     auto entity = readFromConfig<DomainType>(mConfigStore, identifier, ApplicationDomain::getTypeName<DomainType>(), QByteArrayList{});
0198     updateStatus(*entity);
0199     mResultProvider->modify(entity);
0200 }
0201 
0202 template<typename DomainType>
0203 typename Sink::ResultEmitter<typename DomainType::Ptr>::Ptr LocalStorageQueryRunner<DomainType>::emitter()
0204 {
0205     return mResultProvider->emitter();
0206 }
0207 
0208 
0209 template <typename DomainType>
0210 LocalStorageFacade<DomainType>::LocalStorageFacade(const QByteArray &identifier, const QByteArray &typeName) : StoreFacade<DomainType>(), mIdentifier(identifier), mTypeName(typeName)
0211 {
0212 }
0213 
0214 template <typename DomainType>
0215 LocalStorageFacade<DomainType>::~LocalStorageFacade()
0216 {
0217 }
0218 
0219 template <typename DomainType>
0220 KAsync::Job<void> LocalStorageFacade<DomainType>::create(const DomainType &domainObject)
0221 {
0222     auto configStoreIdentifier = mIdentifier;
0223     auto typeName = mTypeName;
0224     return KAsync::start([domainObject, configStoreIdentifier, typeName]() {
0225         const QByteArray type = domainObject.getProperty(typeName).toByteArray();
0226         const QByteArray providedIdentifier = domainObject.identifier().isEmpty() ? domainObject.getProperty("identifier").toByteArray() : domainObject.identifier();
0227         const QByteArray identifier = providedIdentifier.isEmpty() ? ResourceConfig::newIdentifier(type) : providedIdentifier;
0228         auto configStore = ConfigStore(configStoreIdentifier, typeName);
0229         configStore.add(identifier, type);
0230         auto changedProperties = domainObject.changedProperties();
0231         changedProperties.removeOne("identifier");
0232         changedProperties.removeOne(typeName);
0233         if (!changedProperties.isEmpty()) {
0234             // We have some configuration values
0235             QMap<QByteArray, QVariant> configurationValues;
0236             for (const auto &property : changedProperties) {
0237                 configurationValues.insert(property, domainObject.getProperty(property));
0238             }
0239             configStore.modify(identifier, configurationValues);
0240         }
0241         sConfigNotifier.add(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}), type);
0242     });
0243 }
0244 
0245 template <typename DomainType>
0246 KAsync::Job<void> LocalStorageFacade<DomainType>::modify(const DomainType &domainObject)
0247 {
0248     auto configStoreIdentifier = mIdentifier;
0249     auto typeName = mTypeName;
0250     return KAsync::start([domainObject, configStoreIdentifier, typeName]() {
0251         const QByteArray identifier = domainObject.identifier();
0252         if (identifier.isEmpty()) {
0253             SinkWarning() << "We need an \"identifier\" property to identify the entity to configure.";
0254             return;
0255         }
0256         auto changedProperties = domainObject.changedProperties();
0257         changedProperties.removeOne("identifier");
0258         changedProperties.removeOne(typeName);
0259         auto configStore = ConfigStore(configStoreIdentifier, typeName);
0260         if (!changedProperties.isEmpty()) {
0261             // We have some configuration values
0262             QMap<QByteArray, QVariant> configurationValues;
0263             for (const auto &property : changedProperties) {
0264                 configurationValues.insert(property, domainObject.getProperty(property));
0265             }
0266             configStore.modify(identifier, configurationValues);
0267         }
0268 
0269         const auto type = configStore.getEntries().value(identifier);
0270         sConfigNotifier.modify(::readFromConfig<DomainType>(configStore, identifier, type, QByteArrayList{}), type);
0271     });
0272 }
0273 
0274 template <typename DomainType>
0275 KAsync::Job<void> LocalStorageFacade<DomainType>::move(const DomainType &, const QByteArray &)
0276 {
0277     return KAsync::error<void>(1, "Resources and Accounts cannot be moved.");
0278 }
0279 
0280 template <typename DomainType>
0281 KAsync::Job<void> LocalStorageFacade<DomainType>::copy(const DomainType &, const QByteArray &)
0282 {
0283     return KAsync::error<void>(1, "Resources and Accounts cannot be copied.");
0284 }
0285 
0286 template <typename DomainType>
0287 KAsync::Job<void> LocalStorageFacade<DomainType>::remove(const DomainType &domainObject)
0288 {
0289     auto configStoreIdentifier = mIdentifier;
0290     auto typeName = mTypeName;
0291     return KAsync::start([domainObject, configStoreIdentifier, typeName]() {
0292         const QByteArray identifier = domainObject.identifier();
0293         if (identifier.isEmpty()) {
0294             SinkWarning() << "We need an \"identifier\" property to identify the entity to configure";
0295             return;
0296         }
0297         SinkTrace() << "Removing: " << identifier;
0298         auto configStore = ConfigStore(configStoreIdentifier, typeName);
0299         configStore.remove(identifier);
0300         sConfigNotifier.remove(QSharedPointer<DomainType>::create(domainObject), typeName);
0301     });
0302 }
0303 
0304 template <typename DomainType>
0305 QPair<KAsync::Job<void>, typename ResultEmitter<typename DomainType::Ptr>::Ptr> LocalStorageFacade<DomainType>::load(const Query &query, const Sink::Log::Context &parentCtx)
0306 {
0307     auto ctx = parentCtx.subContext(ApplicationDomain::getTypeName<DomainType>());
0308     auto runner = new LocalStorageQueryRunner<DomainType>(query, mIdentifier, mTypeName, sConfigNotifier, ctx);
0309     return qMakePair(KAsync::null<void>(), runner->emitter());
0310 }
0311 
0312 ResourceFacade::ResourceFacade() : LocalStorageFacade<Sink::ApplicationDomain::SinkResource>("resources", Sink::ApplicationDomain::SinkResource::ResourceType::name)
0313 {
0314 }
0315 
0316 ResourceFacade::~ResourceFacade()
0317 {
0318 }
0319 
0320 KAsync::Job<void> ResourceFacade::remove(const Sink::ApplicationDomain::SinkResource &resource)
0321 {
0322     const auto identifier = resource.identifier();
0323     return Sink::Store::removeDataFromDisk(identifier).then(LocalStorageFacade<Sink::ApplicationDomain::SinkResource>::remove(resource));
0324 }
0325 
0326 QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain::SinkResource::Ptr>::Ptr> ResourceFacade::load(const Sink::Query &query, const Sink::Log::Context &parentCtx)
0327 {
0328     auto ctx = parentCtx.subContext("resource");
0329     auto runner = new LocalStorageQueryRunner<ApplicationDomain::SinkResource>(query, mIdentifier, mTypeName, sConfigNotifier, ctx);
0330     auto monitoredResources = QSharedPointer<QSet<QByteArray>>::create();
0331     runner->setStatusUpdater([runner, monitoredResources, ctx](ApplicationDomain::SinkResource &resource) {
0332         auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier()));
0333         if (!monitoredResources->contains(resource.identifier())) {
0334             auto ret = QObject::connect(resourceAccess.data(), &ResourceAccess::notification, runner->guard(), [resource, runner, resourceAccess, ctx](const Notification &notification) {
0335                 SinkTraceCtx(ctx) << "Received notification in facade: " << notification.type;
0336                 if (notification.type == Notification::Status) {
0337                     runner->statusChanged(resource.identifier());
0338                 }
0339             });
0340             Q_ASSERT(ret);
0341             monitoredResources->insert(resource.identifier());
0342         }
0343         resource.setStatusStatus(resourceAccess->getResourceStatus());
0344     });
0345     return qMakePair(KAsync::null<void>(), runner->emitter());
0346 }
0347 
0348 
0349 AccountFacade::AccountFacade() : LocalStorageFacade<Sink::ApplicationDomain::SinkAccount>("accounts", ApplicationDomain::SinkAccount::AccountType::name)
0350 {
0351 }
0352 
0353 AccountFacade::~AccountFacade()
0354 {
0355 }
0356 
0357 QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename ApplicationDomain::SinkAccount::Ptr>::Ptr> AccountFacade::load(const Sink::Query &query, const Sink::Log::Context &parentCtx)
0358 {
0359     auto ctx = parentCtx.subContext("accounts");
0360     auto runner = new LocalStorageQueryRunner<ApplicationDomain::SinkAccount>(query, mIdentifier, mTypeName, sConfigNotifier, ctx);
0361     auto monitoredResources = QSharedPointer<QSet<QByteArray>>::create();
0362     auto monitorResource = [monitoredResources, runner, ctx] (const QByteArray &accountIdentifier, const ApplicationDomain::SinkResource &resource, const ResourceAccess::Ptr &resourceAccess) {
0363         if (!monitoredResources->contains(resource.identifier())) {
0364             auto ret = QObject::connect(resourceAccess.data(), &ResourceAccess::notification, runner->guard(), [resource, runner, resourceAccess, accountIdentifier, ctx](const Notification &notification) {
0365                 SinkTraceCtx(ctx) << "Received notification in facade: " << notification.type;
0366                 if (notification.type == Notification::Status) {
0367                     runner->statusChanged(accountIdentifier);
0368                 }
0369             });
0370             Q_ASSERT(ret);
0371             monitoredResources->insert(resource.identifier());
0372         }
0373     };
0374     runner->setStatusUpdater([runner, monitoredResources, ctx, monitorResource](ApplicationDomain::SinkAccount &account) {
0375         Query query{Query::LiveQuery};
0376         query.filter<ApplicationDomain::SinkResource::Account>(account.identifier());
0377         query.request<ApplicationDomain::SinkResource::Account>()
0378              .request<ApplicationDomain::SinkResource::Capabilities>();
0379         const auto resources = Store::read<ApplicationDomain::SinkResource>(query);
0380         SinkTraceCtx(ctx) << "Found resource belonging to the account " << account.identifier() << " : " << resources;
0381         auto accountIdentifier = account.identifier();
0382 
0383         //Monitor for new resources so they can be monitored as well
0384         if (!runner->mResourceEmitter.contains(accountIdentifier)) {
0385             auto facade = Sink::FacadeFactory::instance().getFacade<ApplicationDomain::SinkResource>();
0386             Q_ASSERT(facade);
0387 
0388             auto emitter = facade->load(query, ctx).second;
0389             emitter->onAdded([=](const ApplicationDomain::SinkResource::Ptr &resource) {
0390                 auto resourceAccess = Sink::ResourceAccessFactory::instance().getAccess(resource->identifier(), ResourceConfig::getResourceType(resource->identifier()));
0391                 monitorResource(accountIdentifier, *resource, resourceAccess);
0392             });
0393             emitter->fetch();
0394             runner->mResourceEmitter[accountIdentifier] = emitter;
0395         }
0396 
0397         QList<int> states;
0398         //Gather all resources and ensure they are monitored
0399         for (const auto &resource : resources) {
0400             auto resourceAccess = ResourceAccessFactory::instance().getAccess(resource.identifier(), ResourceConfig::getResourceType(resource.identifier()));
0401             monitorResource(accountIdentifier, resource, resourceAccess);
0402             states << resourceAccess->getResourceStatus();
0403         }
0404         const auto status = [&] {
0405             if (states.contains(ApplicationDomain::ErrorStatus)) {
0406                 return ApplicationDomain::ErrorStatus;
0407             }
0408             if (states.contains(ApplicationDomain::BusyStatus)) {
0409                 return ApplicationDomain::BusyStatus;
0410             }
0411             if (states.contains(ApplicationDomain::OfflineStatus)) {
0412                 return ApplicationDomain::OfflineStatus;
0413             }
0414             if (states.contains(ApplicationDomain::ConnectedStatus)) {
0415                 return ApplicationDomain::ConnectedStatus;
0416             }
0417             return ApplicationDomain::NoStatus;
0418         }();
0419         account.setStatusStatus(status);
0420     });
0421     return qMakePair(KAsync::null<void>(), runner->emitter());
0422 }
0423 
0424 KAsync::Job<void> AccountFacade::remove(const Sink::ApplicationDomain::SinkAccount &account)
0425 {
0426     using namespace Sink::ApplicationDomain;
0427     auto job = KAsync::null();
0428 
0429     //Remove all resources
0430     job = job.then(Store::fetch<SinkResource>(Sink::Query{}.filter<SinkResource::Account>(account)))
0431         .each([] (const SinkResource::Ptr &resource) { return Store::remove(*resource); });
0432     //Remove all identities
0433     job = job.then(Store::fetch<Identity>(Sink::Query{}.filter<Identity::Account>(account)))
0434         .each([] (const Identity::Ptr &identity) { return Store::remove(*identity); });
0435 
0436     return job.then(LocalStorageFacade<Sink::ApplicationDomain::SinkAccount>::remove(account));
0437 }
0438 
0439 IdentityFacade::IdentityFacade() : LocalStorageFacade<Sink::ApplicationDomain::Identity>("identities", "type")
0440 {
0441 }
0442 
0443 IdentityFacade::~IdentityFacade()
0444 {
0445 }
0446 
0447 #pragma clang diagnostic push
0448 #pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
0449 #include "moc_resourcefacade.cpp"
0450 #pragma clang diagnostic pop