Commit 8a0425ad authored by David Jarvie's avatar David Jarvie
Browse files

Fix bugs in file resource migrator

parent aec1b4a5
Pipeline #25517 passed with stage
in 21 minutes and 27 seconds
KAlarm Change Log
=== Version 3.0.0 --- 16 June 2020 ===
+ Provide option to use file system resources instead of Akonadi resources.
=== Version 3.0.0 --- 30 June 2020 ===
+ Default to using file system resources instead of Akonadi resources.
+ Enable selection of multiple calendar files in Import Alarms dialogue.
+ Show alarm calendars sorted by name in calendars list.
+ Return to last used tab in Configuration dialogue when it is reopened.
......
......@@ -55,13 +55,22 @@ const QRegularExpression MatchMimeType(QStringLiteral("^application/x-vnd\\.kde\
const QString AkonadiResource::KALARM_RESOURCE(QStringLiteral("akonadi_kalarm_resource"));
const QString AkonadiResource::KALARM_DIR_RESOURCE(QStringLiteral("akonadi_kalarm_dir_resource"));
// Class to provide an object for removeDuplicateResources() signals to be received.
/*=============================================================================
* Class to provide an object for removeDuplicateResources() signals to be received.
*/
class DuplicateResourceObject : public QObject
{
Q_OBJECT
public:
typedef void (*CompletionFunction)();
DuplicateResourceObject(QObject* parent = nullptr) : QObject(parent) {}
void reset() { mAgentPaths.clear(); }
void reset(void (*completed)())
{
mAgentPaths.clear();
mCompletionFunc = completed;
agentCount = 0;
}
int agentCount {0};
public Q_SLOTS:
void collectionFetchResult(KJob*);
private:
......@@ -74,10 +83,13 @@ private:
: resourceId(r), collectionId(c) {}
};
QHash<QString, ResourceCol> mAgentPaths; // path, (resource identifier, collection ID) pairs
void (*mCompletionFunc)() {nullptr}; // function to call on completion
};
DuplicateResourceObject* AkonadiResource::mDuplicateResourceObject {nullptr};
/******************************************************************************
*/
Resource AkonadiResource::create(const Akonadi::Collection& collection)
{
if (collection.id() < 0 || collection.remoteId().isEmpty())
......@@ -561,21 +573,24 @@ KAEvent AkonadiResource::event(Resource& resource, const Akonadi::Item& item)
* Check for, and remove, any Akonadi resources which duplicate use of calendar
* files/directories.
*/
void AkonadiResource::removeDuplicateResources()
bool AkonadiResource::removeDuplicateResources(void (*completed)())
{
qCDebug(KALARM_LOG) << "AkonadiResource::removeDuplicateResources";
if (!mDuplicateResourceObject)
mDuplicateResourceObject = new DuplicateResourceObject(Resources::instance());
mDuplicateResourceObject->reset();
mDuplicateResourceObject->reset(completed);
const AgentInstance::List agents = AgentManager::self()->instances();
for (const AgentInstance& agent : agents)
{
if (agent.type().mimeTypes().indexOf(MatchMimeType) >= 0)
{
++mDuplicateResourceObject->agentCount;
CollectionFetchJob* job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive);
job->fetchScope().setResource(agent.identifier());
connect(job, &CollectionFetchJob::result, mDuplicateResourceObject, &DuplicateResourceObject::collectionFetchResult);
}
}
return mDuplicateResourceObject->agentCount > 0;
}
/******************************************************************************
......@@ -614,6 +629,12 @@ void DuplicateResourceObject::collectionFetchResult(KJob* j)
}
}
}
if (--agentCount <= 0)
{
// De-duplication is complete. Notify the caller.
if (mCompletionFunc)
(*mCompletionFunc)();
}
}
/******************************************************************************
......
......@@ -285,8 +285,13 @@ public:
/** Check for, and remove, Akonadi resources which duplicate use of
* calendar files/directories.
* @param completed Function to call on completion of de-duplication, if
* KAlarm Akonadi resources exist.
* @return true if de-duplication initiated;
* false if no KAlarm Akonadi resources found, in which case @p
* completed will not be called.
*/
static void removeDuplicateResources();
static bool removeDuplicateResources(void (*completed)() = nullptr);
/** Called to notify that a resource's Collection has been populated.
* @param events The full list of events in the Collection.
......
......@@ -120,7 +120,7 @@ FileResourceDataModel::FileResourceDataModel(QObject* parent)
{
connect(migrator, &QObject::destroyed, this, &FileResourceDataModel::slotMigrationCompleted);
setMigrationInitiated();
migrator->execute();
migrator->start();
}
MinuteTimer::connect(this, SLOT(slotUpdateTimeTo()));
......
......@@ -20,6 +20,7 @@
#include "fileresourcemigrator.h"
#include "akonadiresource.h"
#include "dirresourceimportdialog.h"
#include "fileresourcecalendarupdater.h"
#include "fileresourceconfigmanager.h"
......@@ -38,6 +39,7 @@
#include <AkonadiCore/AttributeFactory>
#include <AkonadiCore/CollectionFetchJob>
#include <AkonadiCore/CollectionFetchScope>
#include <AkonadiCore/CollectionModifyJob>
#include <KCalendarCore/MemoryCalendar>
#include <KCalendarCore/ICalFormat>
......@@ -45,6 +47,7 @@
#include <KLocalizedString>
#include <KConfig>
#include <KConfigGroup>
#include <Kdelibs4Migration>
#include <QStandardPaths>
#include <QDirIterator>
......@@ -62,6 +65,17 @@ const Akonadi::Collection::Rights WritableRights = Akonadi::Collection::CanChang
bool readDirectoryResource(const QString& dirPath, CalEvent::Types alarmTypes, QHash<CalEvent::Type, QVector<KAEvent>>& events);
}
// Private class to provide private signals.
class FileResourceMigrator::AkonadiMigration : public QObject
{
Q_OBJECT
public:
AkonadiMigration() {}
void setComplete(bool needed) { required = needed; Q_EMIT completed(required); }
bool required {false};
Q_SIGNALS:
void completed(bool needed);
};
FileResourceMigrator* FileResourceMigrator::mInstance = nullptr;
bool FileResourceMigrator::mCompleted = false;
......@@ -71,12 +85,14 @@ bool FileResourceMigrator::mCompleted = false;
*/
FileResourceMigrator::FileResourceMigrator(QObject* parent)
: QObject(parent)
, mAkonadiMigration(new AkonadiMigration)
{
}
FileResourceMigrator::~FileResourceMigrator()
{
qCDebug(KALARM_LOG) << "~FileResourceMigrator";
delete mAkonadiMigration;
mInstance = nullptr;
}
......@@ -109,7 +125,7 @@ FileResourceMigrator* FileResourceMigrator::instance()
* Migrate old Akonadi or KResource calendars, and create default file system
* resources.
*/
void FileResourceMigrator::execute()
void FileResourceMigrator::start()
{
if (mCompleted)
{
......@@ -117,7 +133,7 @@ void FileResourceMigrator::execute()
return;
}
qCDebug(KALARM_LOG) << "FileResourceMigrator::execute";
qCDebug(KALARM_LOG) << "FileResourceMigrator::start";
// First, check whether any file system resources already exist, and if so,
// find their alarm types.
......@@ -129,78 +145,34 @@ void FileResourceMigrator::execute()
{
// Some file system resources already exist, so no migration is
// required. Create any missing default file system resources.
createDefaultResources();
mMigrateKResources = false; // ignore KResources
akonadiMigrationComplete();
}
else
{
// There are no file system resources, so migrate any Akonadi resources.
mMigratingAkonadi = true;
connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, &FileResourceMigrator::migrateAkonadiResources);
migrateAkonadiResources(Akonadi::ServerManager::state());
mAkonadiMigration->required = true;
connect(mAkonadiMigration, &FileResourceMigrator::AkonadiMigration::completed, this, &FileResourceMigrator::akonadiMigrationComplete);
connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, &FileResourceMigrator::checkAkonadiResources);
checkAkonadiResources(Akonadi::ServerManager::state());
// Migration of Akonadi collections has now been initiated. On
// completion, any missing default resources will be created.
if (!mMigratingAkonadi)
{
// There are no Akonadi resources, so migrate any KResources alarm
// calendars from pre-Akonadi versions of KAlarm.
migrateKResources();
}
// completion, either KResource calendars will be migrated, or
// any missing default resources will be created.
}
// Allow any calendar updater instances to complete and auto-delete.
FileResourceCalendarUpdater::waitForCompletion();
}
/******************************************************************************
* Called when the Akonadi server manager changes state.
* Once it is running, migrate any Akonadi KAlarm resources.
*/
void FileResourceMigrator::migrateAkonadiResources(Akonadi::ServerManager::State state)
void FileResourceMigrator::checkAkonadiResources(Akonadi::ServerManager::State state)
{
switch (state)
{
case Akonadi::ServerManager::Running:
{
qCDebug(KALARM_LOG) << "FileResourceMigrator::migrateAkonadiResources: initiated";
Akonadi::AttributeFactory::registerAttribute<CollectionAttribute>();
const Akonadi::AgentInstance::List agents = Akonadi::AgentManager::self()->instances();
// First, migrate KAlarm calendar file resources.
// This will allow any KAlarm directory resources to be merged into
// single file resources, if the user prefers that.
for (const Akonadi::AgentInstance& agent : agents)
{
const QString type = agent.type().identifier();
if (type == KALARM_RESOURCE)
{
// Fetch the resource's collection to determine its alarm types
Akonadi::CollectionFetchJob* job = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::FirstLevel);
job->fetchScope().setResource(agent.identifier());
mFetchesPending << job;
connect(job, &KJob::result, this, &FileResourceMigrator::collectionFetchResult);
mMigrateKResources = false; // ignore KResources if Akonadi resources exist
}
}
// Now migrate KAlarm directory resources, which must be merged
// or converted into single file resources.
for (const Akonadi::AgentInstance& agent : agents)
{
const QString type = agent.type().identifier();
if (type == KALARM_DIR_RESOURCE)
{
// Fetch the resource's collection to determine its alarm types
Akonadi::CollectionFetchJob* job = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::FirstLevel);
job->fetchScope().setResource(agent.identifier());
mFetchesPending << job;
connect(job, &KJob::result, this, &FileResourceMigrator::collectionFetchResult);
mMigrateKResources = false; // ignore KResources if Akonadi resources exist
}
}
if (mFetchesPending.isEmpty())
mMigratingAkonadi = false; // there are no Akonadi resources to migrate
if (!AkonadiResource::removeDuplicateResources(&callMigrateAkonadiResources))
mAkonadiMigration->setComplete(false); // there are no Akonadi resources to migrate
break;
}
case Akonadi::ServerManager::Stopping:
......@@ -212,14 +184,67 @@ void FileResourceMigrator::migrateAkonadiResources(Akonadi::ServerManager::State
return; // wait for the server to change to Running state
// Can't start Akonadi, so give up trying to migrate.
qCWarning(KALARM_LOG) << "FileResourceMigrator::migrateAkonadiResources: Failed to start Akonadi server";
mMigratingAkonadi = false;
qCWarning(KALARM_LOG) << "FileResourceMigrator::checkAkonadiResources: Failed to start Akonadi server";
mAkonadiMigration->setComplete(false);
break;
}
disconnect(Akonadi::ServerManager::self(), nullptr, this, nullptr);
if (mMigrateKResources)
migrateKResources();
}
/******************************************************************************
* Called when removal of duplicate Akonadi KAlarm resources has completed.
*/
void FileResourceMigrator::callMigrateAkonadiResources()
{
if (mInstance)
mInstance->migrateAkonadiResources();
}
/******************************************************************************
* Migrate any Akonadi KAlarm resources.
*/
void FileResourceMigrator::migrateAkonadiResources()
{
qCDebug(KALARM_LOG) << "FileResourceMigrator::migrateAkonadiResources: initiated";
Akonadi::AttributeFactory::registerAttribute<CollectionAttribute>();
const Akonadi::AgentInstance::List agents = Akonadi::AgentManager::self()->instances();
// First, migrate KAlarm calendar file resources.
// This will allow any KAlarm directory resources to be merged into
// single file resources, if the user prefers that.
for (const Akonadi::AgentInstance& agent : agents)
{
const QString type = agent.type().identifier();
if (type == KALARM_RESOURCE)
{
// Fetch the resource's collection to determine its alarm types
Akonadi::CollectionFetchJob* job = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::FirstLevel);
job->fetchScope().setResource(agent.identifier());
mFetchesPending << job;
connect(job, &KJob::result, this, &FileResourceMigrator::collectionFetchResult);
mMigrateKResources = false; // ignore KResources if Akonadi resources exist
}
}
// Now migrate KAlarm directory resources, which must be merged
// or converted into single file resources.
for (const Akonadi::AgentInstance& agent : agents)
{
const QString type = agent.type().identifier();
if (type == KALARM_DIR_RESOURCE)
{
// Fetch the resource's collection to determine its alarm types
Akonadi::CollectionFetchJob* job = new Akonadi::CollectionFetchJob(Akonadi::Collection::root(), Akonadi::CollectionFetchJob::FirstLevel);
job->fetchScope().setResource(agent.identifier());
mFetchesPending << job;
connect(job, &KJob::result, this, &FileResourceMigrator::collectionFetchResult);
mMigrateKResources = false; // ignore KResources if Akonadi resources exist
}
}
if (mFetchesPending.isEmpty())
mAkonadiMigration->setComplete(false); // there are no Akonadi resources to migrate
}
/******************************************************************************
......@@ -255,6 +280,7 @@ void FileResourceMigrator::collectionFetchResult(KJob* j)
standardTypes = attr->standard();
backgroundColour = attr->backgroundColor();
}
bool converted = false;
if (resourceType == KALARM_RESOURCE)
{
qCDebug(KALARM_LOG) << "FileResourceMigrator: Creating resource" << collection.displayName() << ", alarm types:" << alarmTypes << ", standard types:" << standardTypes;
......@@ -264,8 +290,6 @@ void FileResourceMigrator::collectionFetchResult(KJob* j)
alarmTypes, collection.displayName(), backgroundColour,
enabledTypes, standardTypes, readOnly));
Resource resource = FileResourceConfigManager::addResource(settings);
// Don't delete the Akonadi resource in case it is wanted by any other application
//Akonadi::AgentManager::self()->removeInstance(agent);
// Update the calendar to the current KAlarm format if necessary,
// and if the user agrees.
......@@ -274,6 +298,7 @@ void FileResourceMigrator::collectionFetchResult(KJob* j)
updater->update(); // note that 'updater' will auto-delete when finished
mExistingAlarmTypes |= alarmTypes;
converted = true;
}
else if (resourceType == KALARM_DIR_RESOURCE)
{
......@@ -322,23 +347,50 @@ void FileResourceMigrator::collectionFetchResult(KJob* j)
resource.addEvent(event);
mExistingAlarmTypes |= alarmType;
converted = true;
}
}
}
}
if (converted)
{
// Delete the Akonadi resource, to prevent it using CPU, on the
// assumption that Akonadi access won't be needed by any other
// application. Excess CPU usage is one of the major bugs which
// prompted replacing Akonadi with file resources.
Akonadi::AgentManager::self()->removeInstance(agent);
}
}
}
mFetchesPending.removeAll(job);
if (mFetchesPending.isEmpty())
{
// The alarm types of all collections have been found, so now create
// any necessary default file system resources.
mMigratingAkonadi = false;
createDefaultResources();
// The alarm types of all collections have been found.
mAkonadiMigration->setComplete(true);
}
}
/******************************************************************************
* Called when Akonadi migration is complete or is known not to be possible.
*/
void FileResourceMigrator::akonadiMigrationComplete()
{
if (!mAkonadiMigration->required)
{
// There are no Akonadi resources, so migrate any KResources alarm
// calendars from pre-Akonadi versions of KAlarm.
migrateKResources();
}
// Create any necessary additional default file system resources.
createDefaultResources();
// Allow any calendar updater instances to complete and auto-delete.
FileResourceCalendarUpdater::waitForCompletion();
}
/******************************************************************************
* Called when a CalendarUpdater has been destroyed.
* If there are none left, and we have finished, delete this object.
......@@ -354,12 +406,24 @@ void FileResourceMigrator::checkIfComplete()
*/
void FileResourceMigrator::migrateKResources()
{
if (!mMigrateKResources)
return;
if (mExistingAlarmTypes == CalEvent::EMPTY)
{
// There are no file system resources, so migrate any KResources alarm
// calendars from pre-Akonadi versions of KAlarm.
const QString kresConfFile = QStringLiteral("kresources/alarms/stdrc");
QString configFile = QStandardPaths::locate(QStandardPaths::ConfigLocation, kresConfFile);
if (configFile.isEmpty())
{
Kdelibs4Migration kde4;
if (!kde4.kdeHomeFound())
return; // can't find $KDEHOME
configFile = kde4.locateLocal("config", kresConfFile);
if (configFile.isEmpty())
return; // can't find KResources config file
}
qCDebug(KALARM_LOG) << "FileResourceMigrator::migrateKResources";
const QString configFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/kresources/alarms/stdrc");
const KConfig config(configFile, KConfig::SimpleConfig);
// Fetch all the KResource identifiers which are actually in use
......@@ -428,9 +492,6 @@ void FileResourceMigrator::migrateKResources()
mExistingAlarmTypes |= alarmType;
}
}
// Create any necessary additional default file system resources.
createDefaultResources();
}
/******************************************************************************
......@@ -521,6 +582,6 @@ bool readDirectoryResource(const QString& dirPath, CalEvent::Types alarmTypes, Q
}
//#include "fileresourcemigrator.moc"
#include "fileresourcemigrator.moc"
// vim: et sw=4:
......@@ -52,25 +52,29 @@ public:
* Connect to the QObject::destroyed() signal to determine when
* execution has completed.
*/
void execute();
void start();
static bool completed() { return mCompleted; }
private Q_SLOTS:
void checkAkonadiResources(Akonadi::ServerManager::State);
void akonadiMigrationComplete();
void collectionFetchResult(KJob*);
void checkIfComplete();
private:
explicit FileResourceMigrator(QObject* parent = nullptr);
void migrateAkonadiResources(Akonadi::ServerManager::State);
static void callMigrateAkonadiResources();
void migrateAkonadiResources();
void migrateKResources();
void createDefaultResources();
void createCalendar(KAlarmCal::CalEvent::Type alarmType, const QString& file, const QString& name);
static FileResourceMigrator* mInstance;
class AkonadiMigration;
AkonadiMigration* mAkonadiMigration {nullptr};
QList<Akonadi::CollectionFetchJob*> mFetchesPending; // pending collection fetch jobs for existing resources
KAlarmCal::CalEvent::Types mExistingAlarmTypes {KAlarmCal::CalEvent::EMPTY}; // alarm types provided by existing non-Akonadi resources
bool mMigratingAkonadi {false}; // attempting to migrate Akonadi resources
bool mMigrateKResources {true}; // need to migrate KResource resources
static bool mCompleted; // execute() has completed
};
......
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