Commit f8d6bb55 authored by Shashwat Jolly's avatar Shashwat Jolly

Switch to file based caching instead of Akonadi attribute

Required because Akonadi deletes the item before itemRemoved() is called.
The etebase cache is needed for the call to the server.
parent 68a55c5b
......@@ -25,7 +25,6 @@ set(etesyncresource_SRCS
calendartaskbasehandler.cpp
calendarhandler.cpp
taskhandler.cpp
etebasecacheattribute.cpp
setupwizard.cpp
${etesyncconfig_SRCS}
......
......@@ -5,7 +5,6 @@
*/
#include "entriesfetchjob.h"
#include "etebasecacheattribute.h"
#include <kcontacts/addressee.h>
#include <KCalendarCore/Event>
......@@ -22,10 +21,12 @@
using namespace Akonadi;
using namespace EteSyncAPI;
EntriesFetchJob::EntriesFetchJob(const EtebaseAccount *account, const Akonadi::Collection &collection, QObject *parent)
EntriesFetchJob::EntriesFetchJob(const EtebaseAccount *account, const Akonadi::Collection &collection, EtebaseCollectionPtr etesyncCollection, const QString &cacheDir, QObject *parent)
: KJob(parent)
, mAccount(account)
, mCollection(collection)
, mEtesyncCollection(std::move(etesyncCollection))
, mCacheDir(cacheDir)
{
}
......@@ -42,27 +43,26 @@ void EntriesFetchJob::start()
void EntriesFetchJob::fetchEntries()
{
if (!mCollection.hasAttribute<EtebaseCacheAttribute>()) {
if (!mAccount) {
setError(UserDefinedError);
setErrorText(QStringLiteral("No cache for collection ") + mCollection.remoteId());
setErrorText(QStringLiteral("EntriesFetchJob: Etebase account is null"));
return;
}
if (!mAccount) {
if (!mEtesyncCollection) {
setError(UserDefinedError);
setErrorText(QStringLiteral("EntriesFetchJob: Etebase account is empty"));
setErrorText(QStringLiteral("EntriesFetchJob: Etebase collection is null"));
return;
}
EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mAccount));
const QByteArray collectionCache = mCollection.attribute<EtebaseCacheAttribute>()->etebaseCache();
EtebaseCollectionPtr etesyncCollection(etebase_collection_manager_cache_load(collectionManager.get(), collectionCache.constData(), collectionCache.size()));
EtebaseCollectionMetadataPtr metaData(etebase_collection_get_meta(etesyncCollection.get()));
EtebaseCollectionMetadataPtr metaData(etebase_collection_get_meta(mEtesyncCollection.get()));
const QString type = QString::fromUtf8(etebase_collection_metadata_get_collection_type(metaData.get()));
qCDebug(ETESYNC_LOG) << "Type:" << type;
QString sToken = mCollection.remoteRevision();
bool done = 0;
EtebaseItemManagerPtr itemManager(etebase_collection_manager_get_item_manager(collectionManager.get(), etesyncCollection.get()));
EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mAccount));
EtebaseItemManagerPtr itemManager(etebase_collection_manager_get_item_manager(collectionManager.get(), mEtesyncCollection.get()));
while (!done) {
EtebaseFetchOptionsPtr fetchOptions(etebase_fetch_options_new());
......@@ -152,10 +152,18 @@ void EntriesFetchJob::saveItemCache(const EtebaseItemManager *itemManager, const
return;
}
qCDebug(ETESYNC_LOG) << "Saving cache for item" << etebase_item_get_uid(etesyncItem);
QString etesyncItemUid = QString::fromUtf8(etebase_item_get_uid(etesyncItem));
qCDebug(ETESYNC_LOG) << "Saving cache for item" << etesyncItemUid;
uintptr_t ret_size;
EtebaseCachePtr cache(etebase_item_manager_cache_save(itemManager, etesyncItem, &ret_size));
QByteArray cacheData((char *)cache.get(), ret_size);
EtebaseCacheAttribute *etebaseCacheAttribute = item.attribute<EtebaseCacheAttribute>(Item::AddIfMissing);
etebaseCacheAttribute->setEtebaseCache(cacheData);
const QString path = mCacheDir + QLatin1Char('/') + etesyncItemUid;
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
qCDebug(ETESYNC_LOG) << "Unable to open " << path << file.errorString();
return;
}
file.write(cacheData);
}
......@@ -20,7 +20,7 @@ class EntriesFetchJob : public KJob
Q_OBJECT
public:
explicit EntriesFetchJob(const EtebaseAccount *account, const Akonadi::Collection &collection, QObject *parent = nullptr);
explicit EntriesFetchJob(const EtebaseAccount *account, const Akonadi::Collection &collection, EtebaseCollectionPtr etesyncCollection, const QString &cacheDir, QObject *parent = nullptr);
void start() override;
......@@ -47,8 +47,10 @@ protected:
private:
const EtebaseAccount *mAccount = nullptr;
Akonadi::Collection mCollection;
const EtebaseCollectionPtr mEtesyncCollection;
Akonadi::Item::List mItems;
Akonadi::Item::List mRemovedItems;
QString mCacheDir;
};
} // namespace EteSyncAPI
......
......@@ -68,6 +68,11 @@ struct EtebaseDeleter
etebase_item_metadata_destroy(ptr);
}
void operator()(EtebaseItem *ptr)
{
etebase_item_destroy(ptr);
}
void operator()(char *ptr)
{
std::free(ptr);
......@@ -89,6 +94,7 @@ using EtebaseCollectionMetadataPtr = std::unique_ptr<EtebaseCollectionMetadata,
using EtebaseItemManagerPtr = std::unique_ptr<EtebaseItemManager, EtebaseDeleter>;
using EtebaseItemListResponsePtr = std::unique_ptr<EtebaseItemListResponse, EtebaseDeleter>;
using EtebaseItemMetadataPtr = std::unique_ptr<EtebaseItemMetadata, EtebaseDeleter>;
using EtebaseItemPtr = std::unique_ptr<EtebaseItem, EtebaseDeleter>;
using EtebaseCachePtr = std::unique_ptr<void, EtebaseDeleter>;
using CharPtr = std::unique_ptr<char, EtebaseDeleter>;
......
/*
* SPDX-FileCopyrightText: 2020 Shashwat Jolly <shashwat.jolly@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "etebaseadapter.h"
#include "etebasecacheattribute.h"
#include <AkonadiCore/Attribute>
EtebaseCacheAttribute::EtebaseCacheAttribute(QByteArray etebaseCache)
: mEtebaseCache(etebaseCache)
{
}
void EtebaseCacheAttribute::setEtebaseCache(const QByteArray etebaseCache)
{
mEtebaseCache = etebaseCache;
}
QByteArray EtebaseCacheAttribute::etebaseCache() const
{
return mEtebaseCache;
}
QByteArray EtebaseCacheAttribute::type() const
{
static const QByteArray sType("etebasecache");
return sType;
}
Akonadi::Attribute *EtebaseCacheAttribute::clone() const
{
return new EtebaseCacheAttribute(mEtebaseCache);
}
QByteArray EtebaseCacheAttribute::serialized() const
{
return mEtebaseCache;
}
void EtebaseCacheAttribute::deserialize(const QByteArray &data)
{
mEtebaseCache = data;
}
/*
* SPDX-FileCopyrightText: 2020 Shashwat Jolly <shashwat.jolly@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef ETEBASECACHEATTRIBUTE_H
#define ETEBASECACHEATTRIBUTE_H
#include "etebaseadapter.h"
#include <AkonadiCore/Attribute>
class EtebaseCacheAttribute : public Akonadi::Attribute
{
public:
explicit EtebaseCacheAttribute(QByteArray etebaseCache = QByteArray());
void setEtebaseCache(const QByteArray etebaseCache);
QByteArray etebaseCache() const;
QByteArray type() const override;
Attribute *clone() const override;
QByteArray serialized() const override;
void deserialize(const QByteArray &data) override;
private:
QByteArray mEtebaseCache;
};
#endif
......@@ -23,7 +23,6 @@
#include <KMessageBox>
#include <QDBusConnection>
#include "etebasecacheattribute.h"
#include "entriesfetchjob.h"
#include "etesync_debug.h"
#include "etesyncadapter.h"
......@@ -56,8 +55,10 @@ EteSyncResource::EteSyncResource(const QString &id)
changeRecorder()->fetchCollection(true);
changeRecorder()->collectionFetchScope().setAncestorRetrieval(CollectionFetchScope::All);
// Make local resource directory
// Make resource directories
initialiseDirectory(baseDirectoryPath());
initialiseDirectory(collectionsCacheDirectoryPath());
initialiseDirectory(itemsCacheDirectoryPath());
mClientState = EteSyncClientState::Ptr(new EteSyncClientState());
connect(mClientState.get(), &EteSyncClientState::clientInitialised, this, &EteSyncResource::initialiseDone);
......@@ -72,8 +73,6 @@ EteSyncResource::EteSyncResource(const QString &id)
mCalendarHandler = CalendarHandler::Ptr(new CalendarHandler(this));
mTaskHandler = TaskHandler::Ptr(new TaskHandler(this));
AttributeFactory::registerAttribute<EtebaseCacheAttribute>();
connect(this, &Akonadi::AgentBase::reloadConfiguration, this, &EteSyncResource::onReloadConfiguration);
qCDebug(ETESYNC_LOG) << "Resource started";
......@@ -123,7 +122,7 @@ void EteSyncResource::retrieveCollections()
mJournalsCache.clear();
auto job = new JournalsFetchJob(mClientState->account(), mRootCollection, this);
auto job = new JournalsFetchJob(mClientState->account(), mRootCollection, collectionsCacheDirectoryPath(), this);
connect(job, &JournalsFetchJob::finished, this, &EteSyncResource::slotCollectionsRetrieved);
job->start();
}
......@@ -325,17 +324,16 @@ BaseHandler *EteSyncResource::fetchHandlerForCollection(const Akonadi::Collectio
void EteSyncResource::retrieveItems(const Akonadi::Collection &collection)
{
qCDebug(ETESYNC_LOG) << "Retrieving items for collection" << collection.remoteId();
EtebaseCollectionPtr etesyncCollection = getEtesyncCollectionFromCache(collection.remoteId());
if (!etesyncCollection) {
cancelTask(i18n("No cache for collection '%1'", collection.remoteId()));
return;
}
const int timeSinceLastCacheUpdate = mJournalsCacheUpdateTime.secsTo(QDateTime::currentDateTime());
if (timeSinceLastCacheUpdate <= 30) {
qCDebug(ETESYNC_LOG) << "Retrieve items called immediately after collection fetch";
if (!collection.hasAttribute<EtebaseCacheAttribute>()) {
qCDebug(ETESYNC_LOG) << "No cache for collection" << collection.remoteId();
cancelTask(i18n("No cache for collection '%1'", collection.remoteId()));
return;
}
EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
const QByteArray collectionCache = collection.attribute<EtebaseCacheAttribute>()->etebaseCache();
EtebaseCollectionPtr etesyncCollection(etebase_collection_manager_cache_load(collectionManager.get(), collectionCache.constData(), collectionCache.size()));
const QString sToken = QString::fromUtf8(etebase_collection_get_stoken(etesyncCollection.get()));
qCDebug(ETESYNC_LOG) << "Comparing" << sToken << "and" << collection.remoteRevision();
......@@ -351,7 +349,7 @@ void EteSyncResource::retrieveItems(const Akonadi::Collection &collection)
return;
}
auto job = new EntriesFetchJob(mClientState->account(), collection, this);
auto job = new EntriesFetchJob(mClientState->account(), collection, std::move(etesyncCollection), itemsCacheDirectoryPath(), this);
connect(job, &EntriesFetchJob::finished, this, &EteSyncResource::slotItemsRetrieved);
......@@ -379,6 +377,42 @@ void EteSyncResource::slotItemsRetrieved(KJob *job)
qCDebug(ETESYNC_LOG) << "Items retrieval done";
}
EtebaseCollectionPtr EteSyncResource::getEtesyncCollectionFromCache(const QString &collectionUid)
{
if (collectionUid.isEmpty()) {
qCDebug(ETESYNC_LOG) << "Unable to get collection cache - uid is empty";
return nullptr;
}
qCDebug(ETESYNC_LOG) << "Getting cache for collection" << collectionUid;
QString collectionCachePath = collectionsCacheDirectoryPath() + QLatin1Char('/') + collectionUid;
QFile collectionCacheFile(collectionCachePath);
if (!collectionCacheFile.exists()) {
qCDebug(ETESYNC_LOG) << "No cache file for collection" << collectionUid;
return nullptr;
}
if (!collectionCacheFile.open(QIODevice::ReadOnly)) {
qCDebug(ETESYNC_LOG) << "Unable to open " << collectionCachePath << collectionCacheFile.errorString();
return nullptr;
}
QByteArray collectionCache = collectionCacheFile.readAll();
if (collectionCache.isEmpty()) {
qCDebug(ETESYNC_LOG) << "Empty cache file for collection" << collectionUid;
return nullptr;
}
EtebaseCollectionManagerPtr collectionManager(etebase_account_get_collection_manager(mClientState->account()));
EtebaseCollectionPtr etesyncCollection(etebase_collection_manager_cache_load(collectionManager.get(), collectionCache.constData(), collectionCache.size()));
return etesyncCollection;
}
void EteSyncResource::aboutToQuit()
{
}
......@@ -402,6 +436,16 @@ QString EteSyncResource::baseDirectoryPath() const
return Settings::self()->basePath();
}
QString EteSyncResource::collectionsCacheDirectoryPath() const
{
return baseDirectoryPath() + QStringLiteral("/CollectionCache");
}
QString EteSyncResource::itemsCacheDirectoryPath() const
{
return baseDirectoryPath() + QStringLiteral("/ItemCache");
}
void EteSyncResource::initialiseDirectory(const QString &path) const
{
QDir dir(path);
......
......@@ -54,6 +54,10 @@ protected:
void initialiseDirectory(const QString &path) const;
QString baseDirectoryPath() const;
QString collectionsCacheDirectoryPath() const;
QString itemsCacheDirectoryPath() const;
EtebaseCollectionPtr getEtesyncCollectionFromCache(const QString &collectionUid);
EtebaseItemPtr getEtesyncItemFromCache(const QString &itemUid, const EtebaseCollection *parentCollection);
bool handleError();
bool handleError(int errorCode);
......
......@@ -14,7 +14,6 @@
#include <KCalendarCore/Event>
#include <KCalendarCore/Todo>
#include "etebasecacheattribute.h"
#include "etesync_debug.h"
#define COLLECTIONS_FETCH_BATCH_SIZE 50
......@@ -22,10 +21,11 @@
using namespace EteSyncAPI;
using namespace Akonadi;
JournalsFetchJob::JournalsFetchJob(const EtebaseAccount *account, const Akonadi::Collection &resourceCollection, QObject *parent)
JournalsFetchJob::JournalsFetchJob(const EtebaseAccount *account, const Akonadi::Collection &resourceCollection, const QString &cacheDir, QObject *parent)
: KJob(parent)
, mAccount(account)
, mResourceCollection(resourceCollection)
, mCacheDir(cacheDir)
{
}
......@@ -177,10 +177,18 @@ void JournalsFetchJob::saveCollectionCache(const EtebaseCollectionManager *colle
return;
}
qCDebug(ETESYNC_LOG) << "Saving cache for collection" << etebase_collection_get_uid(etesyncCollection);
QString etesyncCollectionUid = QString::fromUtf8(etebase_collection_get_uid(etesyncCollection));
qCDebug(ETESYNC_LOG) << "Saving cache for collection" << etesyncCollectionUid;
uintptr_t ret_size;
EtebaseCachePtr cache(etebase_collection_manager_cache_save(collectionManager, etesyncCollection, &ret_size));
QByteArray cacheData((char *)cache.get(), ret_size);
EtebaseCacheAttribute *etebaseCacheAttribute = collection.attribute<EtebaseCacheAttribute>(Collection::AddIfMissing);
etebaseCacheAttribute->setEtebaseCache(cacheData);
const QString path = mCacheDir + QLatin1Char('/') + etesyncCollectionUid;
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
qCDebug(ETESYNC_LOG) << "Unable to open " << path << file.errorString();
return;
}
file.write(cacheData);
}
......@@ -20,7 +20,7 @@ class JournalsFetchJob : public KJob
Q_OBJECT
public:
explicit JournalsFetchJob(const EtebaseAccount *account, const Akonadi::Collection &resourceCollection, QObject *parent = nullptr);
explicit JournalsFetchJob(const EtebaseAccount *account, const Akonadi::Collection &resourceCollection, const QString &cacheDir = QString(), QObject *parent = nullptr);
void start() override;
......@@ -49,6 +49,7 @@ private:
Akonadi::Collection::List mRemovedCollections;
const Akonadi::Collection mResourceCollection;
QString mSyncToken;
QString mCacheDir;
};
} // namespace EteSyncAPI
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment