Commit 2794fc06 authored by David Jarvie's avatar David Jarvie
Browse files

AlarmCalendar: start refactor, remove display calendar remote access

The display calendar is always local: remote access should have been
removed in version 2.0.0.
Start refactor of AlarmCalendar class by moving alarm import/export
functions to Resources, and clarifying some display calendar specific
code.
parent 6e48bb65
This diff is collapsed.
......@@ -45,8 +45,6 @@ class AlarmCalendar : public QObject
Q_OBJECT
public:
~AlarmCalendar() override;
bool valid() const { return (mCalType == RESOURCES) || mUrl.isValid(); }
CalEvent::Type type() const { return (mCalType == RESOURCES) ? CalEvent::EMPTY : mEventType; }
bool open();
int load();
bool reload();
......@@ -58,12 +56,12 @@ class AlarmCalendar : public QObject
void setAlarmPending(KAEvent*, bool pending = true);
bool haveDisabledAlarms() const { return mHaveDisabledAlarms; }
void disabledChanged(const KAEvent*);
KCalendarCore::Event::Ptr kcalEvent(const QString& uniqueID); // if Akonadi, display calendar only
KAEvent* event(const EventId& uniqueId, bool findUniqueId = false);
KAEvent* templateEvent(const QString& templateName);
KAEvent::List events(const QString& uniqueId) const;
KAEvent::List events(CalEvent::Types s = CalEvent::EMPTY) const { return events(Resource(), s); }
KAEvent::List events(const Resource&, CalEvent::Types = CalEvent::EMPTY) const;
KCalendarCore::Event::Ptr kcalEvent(const QString& uniqueID); // display calendar only
KCalendarCore::Event::List kcalEvents(CalEvent::Type s = CalEvent::EMPTY); // display calendar only
bool eventReadOnly(const QString& eventId) const;
bool addEvent(KAEvent&, QWidget* promptparent = nullptr, bool useEventID = false, Resource* = nullptr, bool noPrompt = false, bool* cancelled = nullptr);
......@@ -74,8 +72,6 @@ class AlarmCalendar : public QObject
bool deleteDisplayEvent(const QString& eventID, bool save = false);
void purgeEvents(const KAEvent::List&);
bool isOpen();
QString path() const { return (mCalType == RESOURCES) ? QString() : mUrl.toDisplayString(); }
QString urlString() const { return (mCalType == RESOURCES) ? QString() : mUrl.toString(); }
void adjustStartOfDay();
static bool initialiseCalendars();
......@@ -84,8 +80,6 @@ class AlarmCalendar : public QObject
static AlarmCalendar* displayCalendar() { return mDisplayCalendar; }
static AlarmCalendar* displayCalendarOpen();
static KAEvent* getEvent(const EventId&);
bool importAlarms(QWidget*, Resource* = nullptr);
static bool exportAlarms(const KAEvent::List&, QWidget* parent);
Q_SIGNALS:
void earliestAlarmChanged();
......@@ -109,7 +103,7 @@ class AlarmCalendar : public QObject
AlarmCalendar();
AlarmCalendar(const QString& file, CalEvent::Type);
bool saveCal(const QString& newFile = QString());
bool isValid() const { return mCalType == RESOURCES || mCalendarStorage; }
bool isValid() const { return mCalType == RESOURCES || mDisplayCalStorage; }
void addNewEvent(const Resource&, KAEvent*, bool replace = false);
CalEvent::Type deleteEventInternal(const KAEvent&, bool deleteFromResources = true);
CalEvent::Type deleteEventInternal(const KAEvent&, Resource&, bool deleteFromResources = true);
......@@ -124,16 +118,14 @@ class AlarmCalendar : public QObject
static AlarmCalendar* mResourcesCalendar; // the calendar resources
static AlarmCalendar* mDisplayCalendar; // the display calendar
static QUrl mLastImportUrl; // last URL for Import Alarms file dialogue
KCalendarCore::FileStorage::Ptr mCalendarStorage; // for display calendar; null if resources calendar
ResourceMap mResourceMap;
KAEventMap mEventMap; // lookup of all events by UID
EarliestMap mEarliestAlarm; // alarm with earliest trigger time, by resource
QSet<QString> mPendingAlarms; // IDs of alarms which are currently being processed after triggering
QUrl mUrl; // URL of current calendar file
QUrl mICalUrl; // URL of iCalendar file
QString mLocalFile; // calendar file, or local copy if it's a remote file
KCalendarCore::FileStorage::Ptr mDisplayCalStorage; // for display calendar; null if resources calendar
QString mDisplayCalPath; // path of display calendar file
QString mDisplayICalPath; // path of display iCalendar file
CalType mCalType; // what type of calendar mCalendar is (resources/ical/vcal)
CalEvent::Type mEventType; // what type of events the calendar file is for
bool mOpen {false}; // true if the calendar file is open
......
......@@ -846,7 +846,8 @@ void MainWindow::slotWakeFromSuspend()
*/
void MainWindow::slotImportAlarms()
{
AlarmCalendar::resources()->importAlarms(this);
Resource resource;
Resources::importAlarms(resource, this);
}
/******************************************************************************
......@@ -858,8 +859,8 @@ void MainWindow::slotExportAlarms()
QVector<KAEvent> events = mListView->selectedEvents();
if (!events.isEmpty())
{
KAEvent::List evts = KAEvent::ptrList(events);
AlarmCalendar::exportAlarms(evts, this);
const KAEvent::List evts = KAEvent::ptrList(events);
Resources::exportAlarms(evts, this);
}
}
......
......@@ -24,11 +24,32 @@
#include "resourcedatamodelbase.h"
#include "resourcemodel.h"
#include "resourceselectdialog.h"
#include "mainwindow.h"
#include "preferences.h"
#include "lib/autoqpointer.h"
#include "lib/filedialog.h"
#include "lib/messagebox.h"
#include "kalarm_debug.h"
#include <KCalendarCore/MemoryCalendar>
#include <KCalendarCore/ICalFormat>
#include <KLocalizedString>
#include <KFileItem>
#include <KJobWidgets>
#include <KIO/StatJob>
#include <KIO/StoredTransferJob>
#include <kio_version.h>
#include <QTemporaryFile>
#include <QFileDialog>
using namespace KCalendarCore;
namespace
{
bool updateCalendarFormat(const FileStorage::Ptr&);
}
Resources* Resources::mInstance {nullptr};
......@@ -38,6 +59,7 @@ QHash<ResourceId, Resource> Resources::mResources;
bool Resources::mCreated {false};
bool Resources::mPopulated {false};
QUrl Resources::mLastImportUrl;
Resources* Resources::instance()
......@@ -295,6 +317,240 @@ Resource Resources::destination(CalEvent::Type type, QWidget* promptParent, bool
return res;
}
/******************************************************************************
* Import alarms from an external calendar and merge them into KAlarm's calendar.
* The alarms are given new unique event IDs.
* Parameters: parent = parent widget for error message boxes
* Reply = true if all alarms in the calendar were successfully imported
* = false if any alarms failed to be imported.
*/
bool Resources::importAlarms(Resource& resource, QWidget* parent)
{
qCDebug(KALARM_LOG) << "Resources::importAlarms";
const QUrl url = QFileDialog::getOpenFileUrl(parent, QString(), mLastImportUrl,
QStringLiteral("%1 (*.vcs *.ics)").arg(i18nc("@info", "Calendar Files")));
if (url.isEmpty())
{
qCCritical(KALARM_LOG) << "Resources::importAlarms: Empty URL";
return false;
}
if (!url.isValid())
{
qCDebug(KALARM_LOG) << "Resources::importAlarms: Invalid URL";
return false;
}
mLastImportUrl = url.adjusted(QUrl::RemoveFilename);
qCDebug(KALARM_LOG) << "Resources::importAlarms:" << url.toDisplayString();
// If the URL is remote, download it into a temporary local file.
QString filename;
bool local = url.isLocalFile();
if (local)
{
filename = url.toLocalFile();
if (!QFile::exists(filename))
{
qCDebug(KALARM_LOG) << "Resources::importAlarms: File '" << url.toDisplayString() <<"' not found";
KAMessageBox::error(parent, xi18nc("@info", "Could not load calendar <filename>%1</filename>.", url.toDisplayString()));
return false;
}
}
else
{
auto getJob = KIO::storedGet(url);
KJobWidgets::setWindow(getJob, MainWindow::mainMainWindow());
if (!getJob->exec())
{
qCCritical(KALARM_LOG) << "Resources::accessUrl: Download failure";
KAMessageBox::error(parent, xi18nc("@info", "Cannot download calendar: <filename>%1</filename>", url.toDisplayString()));
return false;
}
QTemporaryFile tmpFile;
tmpFile.setAutoRemove(false);
tmpFile.write(getJob->data());
tmpFile.seek(0);
filename = tmpFile.fileName();
qCDebug(KALARM_LOG) << "Resources::accessUrl: --- Downloaded to" << filename;
}
// Read the calendar and add its alarms to the current calendars
MemoryCalendar::Ptr cal(new MemoryCalendar(Preferences::timeSpecAsZone()));
FileStorage::Ptr calStorage(new FileStorage(cal, filename));
bool success = calStorage->load();
if (!success)
{
qCDebug(KALARM_LOG) << "Resources::importAlarms: Error loading calendar '" << filename <<"'";
KAMessageBox::error(parent, xi18nc("@info", "Could not load calendar <filename>%1</filename>.", url.toDisplayString()));
}
else
{
const bool currentFormat = updateCalendarFormat(calStorage);
const CalEvent::Types wantedTypes = resource.alarmTypes();
const Event::List events = cal->rawEvents();
for (Event::Ptr event : events)
{
if (event->alarms().isEmpty() || !KAEvent(event).isValid())
continue; // ignore events without alarms, or usable alarms
CalEvent::Type type = CalEvent::status(event);
if (type == CalEvent::TEMPLATE)
{
// If we know the event was not created by KAlarm, don't treat it as a template
if (!currentFormat)
type = CalEvent::ACTIVE;
}
Resource res;
if (resource.isValid())
{
if (!(type & wantedTypes))
continue;
res = resource;
}
else
{
switch (type)
{
case CalEvent::ACTIVE:
case CalEvent::ARCHIVED:
case CalEvent::TEMPLATE:
break;
default:
continue;
}
//TODO: does this prompt for every alarm if no default is set?
res = Resources::destination(type);
}
Event::Ptr newev(new Event(*event));
// If there is a display alarm without display text, use the event
// summary text instead.
if (type == CalEvent::ACTIVE && !newev->summary().isEmpty())
{
const Alarm::List& alarms = newev->alarms();
for (Alarm::Ptr alarm : alarms)
{
if (alarm->type() == Alarm::Display && alarm->text().isEmpty())
alarm->setText(newev->summary());
}
newev->setSummary(QString()); // KAlarm only uses summary for template names
}
// Give the event a new ID and add it to the calendars
newev->setUid(CalEvent::uid(CalFormat::createUniqueId(), type));
if (!res.addEvent(KAEvent(newev)))
success = false;
}
}
if (!local)
QFile::remove(filename);
return success;
}
/******************************************************************************
* Export all selected alarms to an external calendar.
* The alarms are given new unique event IDs.
* Parameters: parent = parent widget for error message boxes
* Reply = true if all alarms in the calendar were successfully exported
* = false if any alarms failed to be exported.
*/
bool Resources::exportAlarms(const KAEvent::List& events, QWidget* parent)
{
bool append;
//TODO: exportalarms shows up afterwards in other file dialogues
QString file = FileDialog::getSaveFileName(QUrl(QStringLiteral("kfiledialog:///exportalarms")),
QStringLiteral("*.ics|%1").arg(i18nc("@info", "Calendar Files")),
parent, i18nc("@title:window", "Choose Export Calendar"),
&append);
if (file.isEmpty())
return false;
const QUrl url = QUrl::fromLocalFile(file);
if (!url.isValid())
{
qCDebug(KALARM_LOG) << "Resources::exportAlarms: Invalid URL" << url;
return false;
}
qCDebug(KALARM_LOG) << "Resources::exportAlarms:" << url.toDisplayString();
MemoryCalendar::Ptr calendar(new MemoryCalendar(Preferences::timeSpecAsZone()));
FileStorage::Ptr calStorage(new FileStorage(calendar, file));
if (append && !calStorage->load())
{
#if KIO_VERSION < QT_VERSION_CHECK(5, 69, 0)
auto statJob = KIO::stat(url, KIO::StatJob::SourceSide, 2);
#else
auto statJob = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatDetail::StatDefaultDetails);
#endif
KJobWidgets::setWindow(statJob, parent);
statJob->exec();
KFileItem fi(statJob->statResult(), url);
if (fi.size())
{
qCCritical(KALARM_LOG) << "Resources::exportAlarms: Error loading calendar file" << file << "for append";
KAMessageBox::error(MainWindow::mainMainWindow(),
xi18nc("@info", "Error loading calendar to append to:<nl/><filename>%1</filename>", url.toDisplayString()));
return false;
}
}
KACalendar::setKAlarmVersion(calendar);
// Add the alarms to the calendar
bool success = true;
bool exported = false;
for (int i = 0, end = events.count(); i < end; ++i)
{
const KAEvent* event = events[i];
Event::Ptr kcalEvent(new Event);
const CalEvent::Type type = event->category();
const QString id = CalEvent::uid(kcalEvent->uid(), type);
kcalEvent->setUid(id);
event->updateKCalEvent(kcalEvent, KAEvent::UID_IGNORE);
if (calendar->addEvent(kcalEvent))
exported = true;
else
success = false;
}
if (exported)
{
// One or more alarms have been exported to the calendar.
// Save the calendar to file.
QTemporaryFile* tempFile = nullptr;
bool local = url.isLocalFile();
if (!local)
{
tempFile = new QTemporaryFile;
file = tempFile->fileName();
}
calStorage->setFileName(file);
calStorage->setSaveFormat(new ICalFormat);
if (!calStorage->save())
{
qCCritical(KALARM_LOG) << "Resources::exportAlarms:" << file << ": failed";
KAMessageBox::error(MainWindow::mainMainWindow(),
xi18nc("@info", "Failed to save new calendar to:<nl/><filename>%1</filename>", url.toDisplayString()));
success = false;
}
else if (!local)
{
QFile qFile(file);
qFile.open(QIODevice::ReadOnly);
auto uploadJob = KIO::storedPut(&qFile, url, -1);
KJobWidgets::setWindow(uploadJob, parent);
if (!uploadJob->exec())
{
qCCritical(KALARM_LOG) << "Resources::exportAlarms:" << file << ": upload failed";
KAMessageBox::error(MainWindow::mainMainWindow(),
xi18nc("@info", "Cannot upload new calendar to:<nl/><filename>%1</filename>", url.toDisplayString()));
success = false;
}
}
delete tempFile;
}
calendar->close();
return success;
}
/******************************************************************************
* Return whether all configured resources have been created.
*/
......@@ -626,4 +882,22 @@ bool Resources::isPopulated(ResourceId id)
}
#endif
namespace
{
/******************************************************************************
* Find the version of KAlarm which wrote the calendar file, and do any
* necessary conversions to the current format.
*/
bool updateCalendarFormat(const FileStorage::Ptr& fileStorage)
{
QString versionString;
int version = KACalendar::updateVersion(fileStorage, versionString);
if (version == KACalendar::IncompatibleFormat)
return false; // calendar was created by another program, or an unknown version of KAlarm
return true;
}
}
// vim: et sw=4:
......@@ -26,6 +26,7 @@
#include "resourcemodel.h"
#include <QObject>
#include <QUrl>
class QEventLoop;
using namespace KAlarmCal;
......@@ -124,6 +125,29 @@ public:
*/
static Resource destination(CalEvent::Type type, QWidget* promptParent = nullptr, bool noPrompt = false, bool* cancelled = nullptr);
/** Prompt the user for an external calendar file to import alarms from,
* and merge them into a resource. If the resource is invalid, the events
* will be merged into the default resource for each alarm type (obtained
* by calling destination(type)).
* The alarms are given new unique event IDs.
* @param parent Parent widget for error message boxes
* @param resource Resource to import into
* @return true if all alarms in the calendar were successfully imported;
* false if any alarms failed to be imported.
*/
static bool importAlarms(Resource& resource, QWidget* parent);
/** Prompt the user for an external calendar file, and export a list of
* alarms to it. If an existing file is chosen, the user has the choice
* whether to append or overwrite.
* The alarms are given new unique event IDs.
* @param events Events to export
* @param parent Parent widget for error message boxes
* @return true if all alarms in the calendar were successfully exported;
* false if any alarms failed to be exported.
*/
static bool exportAlarms(const KAEvent::List& events, QWidget* parent);
/** Return whether all configured resources have been created. */
static bool allCreated();
......@@ -266,6 +290,7 @@ private:
static QHash<ResourceId, Resource> mResources; // contains all ResourceType instances with an ID
static bool mCreated; // all resources have been created
static bool mPopulated; // all resources have been loaded once
static QUrl mLastImportUrl; // last URL for Import Alarms file dialogue
friend class ResourceType;
};
......
......@@ -454,7 +454,7 @@ void ResourceSelector::setStandard()
void ResourceSelector::importCalendar()
{
Resource resource = currentResource();
AlarmCalendar::resources()->importAlarms(this, &resource);
Resources::importAlarms(resource, this);
}
/******************************************************************************
......@@ -465,7 +465,7 @@ void ResourceSelector::exportCalendar()
{
const Resource resource = currentResource();
if (resource.isValid())
AlarmCalendar::exportAlarms(AlarmCalendar::resources()->events(resource), this);
Resources::exportAlarms(AlarmCalendar::resources()->events(resource), this);
}
/******************************************************************************
......
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