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 ¬ification) { 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 ¬ification) { 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