alarmcalendar.cpp 44 KB
Newer Older
1
2
3
/*
 *  alarmcalendar.cpp  -  KAlarm calendar file access
 *  Program:  kalarm
4
 *  Copyright © 2001-2020 David Jarvie <djarvie@kde.org>
5
6
7
8
9
10
11
12
13
14
15
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
David Jarvie's avatar
David Jarvie committed
16
17
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
David Jarvie's avatar
David Jarvie committed
18
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20
 */

Laurent Montel's avatar
Laurent Montel committed
21
#include "alarmcalendar.h"
22

23
#include "kalarm.h"
24
25
26
27
#include "functions.h"
#include "kalarmapp.h"
#include "mainwindow.h"
#include "preferences.h"
28
#include "resources/datamodel.h"
29
#include "resources/resources.h"
David Jarvie's avatar
David Jarvie committed
30
#include "lib/messagebox.h"
31
#include "kalarm_debug.h"
32

33
34
#include <KCalendarCore/MemoryCalendar>
#include <KCalendarCore/ICalFormat>
35

36
#include <KLocalizedString>
37
#include <KIO/StatJob>
38
39
#include <KIO/StoredTransferJob>
#include <KJobWidgets>
40
#include <KFileItem>
41
#include <KSharedConfig>
42
#include <kio_version.h>
43

David Jarvie's avatar
Tidy    
David Jarvie committed
44
#include <QTemporaryFile>
Laurent Montel's avatar
Laurent Montel committed
45
#include <QStandardPaths>
46
#include <QDir>
47

Laurent Montel's avatar
Laurent Montel committed
48
using namespace KCalendarCore;
49
using namespace KAlarmCal;
David Jarvie's avatar
David Jarvie committed
50

David Jarvie's avatar
David Jarvie committed
51

Laurent Montel's avatar
Laurent Montel committed
52
static const QString displayCalendarName = QStringLiteral("displaying.ics");
53
static const ResourceId DISPLAY_RES_ID = -1;   // resource ID used for displaying calendar
David Jarvie's avatar
David Jarvie committed
54

55
56
ResourcesCalendar* ResourcesCalendar::mInstance = nullptr;
DisplayCalendar*  DisplayCalendar::mInstance = nullptr;
David Jarvie's avatar
David Jarvie committed
57
58
59


/******************************************************************************
60
* Initialise backend calendar access.
David Jarvie's avatar
David Jarvie committed
61
*/
62
void AlarmCalendar::initialise()
David Jarvie's avatar
David Jarvie committed
63
{
64
    KACalendar::setProductId(KALARM_NAME, KALARM_VERSION);
Laurent Montel's avatar
Laurent Montel committed
65
    CalFormat::setApplication(QStringLiteral(KALARM_NAME), QString::fromLatin1(KACalendar::icalProductId()));
David Jarvie's avatar
David Jarvie committed
66
67
68
}

/******************************************************************************
69
70
71
72
73
74
75
76
77
78
79
80
81
82
* Initialise the resource alarm calendars, and ensure that their file names are
* different. The resources calendar contains the active alarms, archived alarms
* and alarm templates;
*/
void ResourcesCalendar::initialise()
{
    AlarmCalendar::initialise();
    mInstance = new ResourcesCalendar();
}

/******************************************************************************
* Initialise the display alarm calendar.
* It is user-specific, and contains details of alarms which are currently being
* displayed to that user and which have not yet been acknowledged;
David Jarvie's avatar
David Jarvie committed
83
*/
84
void DisplayCalendar::initialise()
David Jarvie's avatar
David Jarvie committed
85
{
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
    QDir dir;
    dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
    const QString displayCal = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + displayCalendarName;
    mInstance = new DisplayCalendar(displayCal);
}

/******************************************************************************
* Terminate access to the resource calendars.
*/
void ResourcesCalendar::terminate()
{
    delete mInstance;
    mInstance = nullptr;
}

/******************************************************************************
* Terminate access to the display calendar.
*/
void DisplayCalendar::terminate()
{
    delete mInstance;
    mInstance = nullptr;
David Jarvie's avatar
David Jarvie committed
108
109
110
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
111
* Return the display calendar, opening it first if necessary.
David Jarvie's avatar
David Jarvie committed
112
*/
113
DisplayCalendar* DisplayCalendar::instanceOpen()
David Jarvie's avatar
David Jarvie committed
114
{
115
116
117
    if (mInstance->open())
        return mInstance;
    qCCritical(KALARM_LOG) << "DisplayCalendar::instanceOpen: Open error";
Laurent Montel's avatar
Laurent Montel committed
118
    return nullptr;
David Jarvie's avatar
David Jarvie committed
119
120
}

David Jarvie's avatar
David Jarvie committed
121
122
123
124
/******************************************************************************
* Find and return the event with the specified ID.
* The calendar searched is determined by the calendar identifier in the ID.
*/
125
KAEvent* ResourcesCalendar::getEvent(const EventId& eventId)
David Jarvie's avatar
David Jarvie committed
126
{
127
    if (eventId.eventId().isEmpty())
Laurent Montel's avatar
Laurent Montel committed
128
        return nullptr;
129
    return mInstance->event(eventId);
130
}
David Jarvie's avatar
David Jarvie committed
131
132

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
133
134
135
* Constructor for the resources calendar.
*/
AlarmCalendar::AlarmCalendar()
136
137
138
139
140
141
142
143
{
}

/******************************************************************************
* Constructor for the resources calendar.
*/
ResourcesCalendar::ResourcesCalendar()
    : AlarmCalendar()
David Jarvie's avatar
David Jarvie committed
144
{
145
    Resources* resources = Resources::instance();
146
147
148
149
150
151
    connect(resources, &Resources::resourceAdded, this, &ResourcesCalendar::slotResourceAdded);
    connect(resources, &Resources::eventsAdded, this, &ResourcesCalendar::slotEventsAdded);
    connect(resources, &Resources::eventsToBeRemoved, this, &ResourcesCalendar::slotEventsToBeRemoved);
    connect(resources, &Resources::eventUpdated, this, &ResourcesCalendar::slotEventUpdated);
    connect(resources, &Resources::resourcesPopulated, this, &ResourcesCalendar::slotResourcesPopulated);
    connect(resources, &Resources::settingsChanged, this, &ResourcesCalendar::slotResourceSettingsChanged);
152
153
154
155
156

    // Fetch events from all resources which already exist.
    QVector<Resource> allResources = Resources::enabledResources();
    for (Resource& resource : allResources)
        slotResourceAdded(resource);
David Jarvie's avatar
David Jarvie committed
157
158
159
}

/******************************************************************************
160
* Constructor for the display calendar file.
David Jarvie's avatar
David Jarvie committed
161
*/
162
163
164
DisplayCalendar::DisplayCalendar(const QString& path)
    : AlarmCalendar()
    , mDisplayCalPath(path)
165
    , mDisplayICalPath(path)
166
{
167
168
    mDisplayICalPath.replace(QStringLiteral("\\.vcs$"), QStringLiteral(".ics"));
    mCalType = (mDisplayCalPath == mDisplayICalPath) ? LOCAL_ICAL : LOCAL_VCAL;    // is the calendar in ICal or VCal format?
169
170
}

171
ResourcesCalendar::~ResourcesCalendar()
David Jarvie's avatar
David Jarvie committed
172
{
173
    close();
David Jarvie's avatar
David Jarvie committed
174
175
}

176
DisplayCalendar::~DisplayCalendar()
177
{
178
    close();
179
180
}

181
/******************************************************************************
182
* Open the calendar if not already open, and load it into memory.
183
*/
184
bool DisplayCalendar::open()
185
{
186
    if (isOpen())
187
        return true;
188
189
190
191

    // Open the display calendar.
    qCDebug(KALARM_LOG) << "DisplayCalendar::open:" << mDisplayCalPath;
    if (!mCalendarStorage)
192
    {
193
194
        MemoryCalendar::Ptr calendar(new MemoryCalendar(Preferences::timeSpecAsZone()));
        mCalendarStorage = FileStorage::Ptr(new FileStorage(calendar, mDisplayCalPath));
195
196
    }

197
198
199
200
201
202
203
204
    // Check for file's existence, assuming that it does exist when uncertain,
    // to avoid overwriting it.
    QFileInfo fi(mDisplayCalPath);
    if (!fi.exists()  ||  !fi.isFile()  ||  load() == 0)
    {
        // The calendar file doesn't yet exist, or it's zero length, so create a new one
        if (saveCal(mDisplayICalPath))
            load();
205
    }
206

207
208
    if (!mOpen)
    {
209
210
        mCalendarStorage->calendar().clear();
        mCalendarStorage.clear();
211
    }
212
    return isOpen();
213
214
215
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
216
* Load the calendar into memory.
David Jarvie's avatar
David Jarvie committed
217
218
219
220
* Reply = 1 if success
*       = 0 if zero-length file exists.
*       = -1 if failure to load calendar file
*       = -2 if instance uninitialised.
221
*/
222
int DisplayCalendar::load()
223
{
224
225
226
227
228
229
    // Load the display calendar.
    if (!mCalendarStorage)
        return -2;

    qCDebug(KALARM_LOG) << "DisplayCalendar::load:" << mDisplayCalPath;
    if (!mCalendarStorage->load())
230
    {
231
232
233
234
        // Load error. Check if the file is zero length
        QFileInfo fi(mDisplayCalPath);
        if (fi.exists()  &&  !fi.size())
            return 0;     // file is zero length
235

236
237
238
239
240
241
242
243
244
        qCCritical(KALARM_LOG) << "DisplayCalendar::load: Error loading calendar file '" << mDisplayCalPath <<"'";
        KAMessageBox::error(MainWindow::mainMainWindow(),
                            xi18nc("@info", "<para>Error loading calendar:</para><para><filename>%1</filename></para><para>Please fix or delete the file.</para>", mDisplayCalPath));
        // load() could have partially populated the calendar, so clear it out
        mCalendarStorage->calendar()->close();
        mCalendarStorage->calendar().clear();
        mCalendarStorage.clear();
        mOpen = false;
        return -1;
245
    }
246
247
248
249
    QString versionString;
    KACalendar::updateVersion(mCalendarStorage, versionString);   // convert events to current KAlarm format for when calendar is saved
    updateKAEvents();

250
251
    mOpen = true;
    return 1;
252
253
}

254
255
256
257
258
259
260
261
262
263
264
265
266
/******************************************************************************
* Reload the calendar resources into memory.
*/
bool ResourcesCalendar::reload()
{
    qCDebug(KALARM_LOG) << "ResourcesCalendar::reload";
    bool ok = true;
    QVector<Resource> resources = Resources::enabledResources();
    for (Resource& resource : resources)
        ok = ok && resource.reload();
    return ok;
}

David Jarvie's avatar
David Jarvie committed
267
268
269
/******************************************************************************
* Reload the calendar file into memory.
*/
270
bool DisplayCalendar::reload()
David Jarvie's avatar
David Jarvie committed
271
{
272
    if (!mCalendarStorage)
273
        return false;
274

275
    qCDebug(KALARM_LOG) << "DisplayCalendar::reload:" << mDisplayCalPath;
276
277
    close();
    return open();
David Jarvie's avatar
David Jarvie committed
278
279
}

280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/******************************************************************************
* Save the calendar.
*/
bool ResourcesCalendar::save()
{
    bool ok = true;
    // Get all enabled, writable resources.
    QVector<Resource> resources = Resources::enabledResources(CalEvent::EMPTY, true);
    for (Resource& resource : resources)
        ok = ok && resource.save();
    return ok;
}

/******************************************************************************
* Save the calendar.
*/
bool DisplayCalendar::save()
{
    return saveCal();
}

301
302
/******************************************************************************
* Save the calendar from memory to file.
David Jarvie's avatar
David Jarvie committed
303
* If a filename is specified, create a new calendar file.
304
*/
305
bool DisplayCalendar::saveCal(const QString& newFile)
306
{
307
    if (!mCalendarStorage)
308
309
        return false;
    if (!mOpen  &&  newFile.isEmpty())
310
311
        return false;

312
    qCDebug(KALARM_LOG) << "DisplayCalendar::saveCal:" << "\"" << newFile;
313
314
315
    QString saveFilename = newFile.isEmpty() ? mDisplayCalPath : newFile;
    if (mCalType == LOCAL_VCAL  &&  newFile.isNull())
        saveFilename = mDisplayICalPath;
316
317
318
    mCalendarStorage->setFileName(saveFilename);
    mCalendarStorage->setSaveFormat(new ICalFormat);
    if (!mCalendarStorage->save())
319
    {
320
        qCCritical(KALARM_LOG) << "DisplayCalendar::saveCal: Saving" << saveFilename << "failed.";
321
322
323
324
        KAMessageBox::error(MainWindow::mainMainWindow(),
                            xi18nc("@info", "Failed to save calendar to <filename>%1</filename>", mDisplayICalPath));
        return false;
    }
325

326
327
328
329
330
    if (mCalType == LOCAL_VCAL)
    {
        // The file was in vCalendar format, but has now been saved in iCalendar format.
        mDisplayCalPath = mDisplayICalPath;
        mCalType = LOCAL_ICAL;
331
332
    }
    return true;
333
334
335
}

/******************************************************************************
336
* Close display calendar file at program exit.
337
*/
338
void ResourcesCalendar::close()
339
{
340
341
342
343
344
345
346
347
348
349
350
    // Resource map should be empty, but just in case...
    while (!mResourceMap.isEmpty())
        removeKAEvents(mResourceMap.begin().key(), true, CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE | CalEvent::DISPLAYING);
}

/******************************************************************************
* Close display calendar file at program exit.
*/
void DisplayCalendar::close()
{
    if (mCalendarStorage)
351
    {
352
353
354
        mCalendarStorage->calendar()->close();
        mCalendarStorage->calendar().clear();
        mCalendarStorage.clear();
355
    }
356

357
358
359
    // Flag as closed now to prevent removeKAEvents() doing silly things
    // when it's called again
    mOpen = false;
360

361
362
    // Resource map should be empty, but just in case...
    while (!mResourceMap.isEmpty())
363
        removeKAEvents(mResourceMap.begin().key(), CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE | CalEvent::DISPLAYING);
David Jarvie's avatar
David Jarvie committed
364
365
}

David Jarvie's avatar
David Jarvie committed
366
367
368
369
370
/******************************************************************************
* Create a KAEvent instance corresponding to each KCalendarCore::Event in the
* display calendar, and store them in the event map in place of the old set.
* Called after the display calendar has completed loading.
*/
371
void DisplayCalendar::updateKAEvents()
372
{
373
374
    qCDebug(KALARM_LOG) << "DisplayCalendar::updateKAEvents";
    const ResourceId key = DISPLAY_RES_ID;
375
    KAEvent::List& events = mResourceMap[key];
David Jarvie's avatar
David Jarvie committed
376
    for (KAEvent* event : events)
377
    {
378
        mEventMap.remove(EventId(key, event->id()));
379
380
381
        delete event;
    }
    events.clear();
382
    Calendar::Ptr cal = mCalendarStorage->calendar();
383
384
    if (!cal)
        return;
385

David Jarvie's avatar
David Jarvie committed
386
387
    const Event::List kcalevents = cal->rawEvents();
    for (Event::Ptr kcalevent : kcalevents)
388
389
390
    {
        if (kcalevent->alarms().isEmpty())
            continue;    // ignore events without alarms
391

392
393
394
        KAEvent* event = new KAEvent(kcalevent);
        if (!event->isValid())
        {
395
            qCWarning(KALARM_LOG) << "DisplayCalendar::updateKAEvents: Ignoring unusable event" << kcalevent->uid();
396
397
398
            delete event;
            continue;    // ignore events without usable alarms
        }
399
        event->setResourceId(key);
400
401
        events += event;
        mEventMap[EventId(key, kcalevent->uid())] = event;
402
    }
403

404
405
}

406
407
408
409
410
411
/******************************************************************************
* Delete a calendar and all its KAEvent instances of specified alarm types from
* the lists.
* Called after the calendar is deleted or alarm types have been disabled, or
* the AlarmCalendar is closed.
*/
412
bool AlarmCalendar::removeKAEvents(ResourceId key, CalEvent::Types types)
413
414
415
416
417
{
    bool removed = false;
    ResourceMap::Iterator rit = mResourceMap.find(key);
    if (rit != mResourceMap.end())
    {
418
        KAEvent::List retained;
419
420
421
422
        KAEvent::List& events = rit.value();
        for (int i = 0, end = events.count();  i < end;  ++i)
        {
            KAEvent* event = events[i];
423
            bool remove = (event->resourceId() != key);
424
            if (remove)
425
            {
426
                if (key != DISPLAY_RES_ID)
427
                    qCCritical(KALARM_LOG) << "AlarmCalendar::removeKAEvents: Event" << event->id() << ", resource" << event->resourceId() << "Indexed under resource" << key;
428
            }
429
430
431
            else
                remove = event->category() & types;
            if (remove)
432
            {
433
                mEventMap.remove(EventId(key, event->id()));
434
435
436
437
                delete event;
                removed = true;
            }
            else
438
                retained.push_back(event);
439
        }
440
        if (retained.empty())
441
            mResourceMap.erase(rit);
442
443
        else
            events.swap(retained);
444
    }
445
446
447
448
449
450
451
452
453
454
455
    return removed;
}

bool ResourcesCalendar::removeKAEvents(ResourceId key, bool closing, CalEvent::Types types)
{
    if (!AlarmCalendar::removeKAEvents(key, types))
        return false;

    mEarliestAlarm.remove(key);
    // Emit signal only if we're not in the process of closing the calendar
    if (!closing)
456
    {
457
458
459
        Q_EMIT earliestAlarmChanged();
        if (mHaveDisabledAlarms)
            checkForDisabledAlarms();
460
    }
461
    return true;
462
}
463

464
/******************************************************************************
465
466
* Called when the enabled or read-only status of a resource has changed.
* If the resource is now disabled, remove its events from the calendar.
467
*/
468
void ResourcesCalendar::slotResourceSettingsChanged(Resource& resource, ResourceType::Changes change)
469
{
470
    if (change & ResourceType::Enabled)
471
    {
472
        if (resource.isValid())
473
        {
474
            // For each alarm type which has been disabled, remove the
475
            // resource's events from the map, but not from the resource.
476
            const CalEvent::Types enabled = resource.enabledTypes();
477
            const CalEvent::Types disabled = ~enabled & (CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE);
478
            removeKAEvents(resource.id(), false, disabled);
479

480
            // For each alarm type which has been enabled, add the resource's
481
482
483
            // events to the map.
            if (enabled != CalEvent::EMPTY)
                slotEventsAdded(resource, resource.events());
484
        }
485
    }
486
487
}

488
/******************************************************************************
489
* Called when all resources have been populated for the first time.
490
*/
491
void ResourcesCalendar::slotResourcesPopulated()
492
493
494
495
496
497
498
{
    // Now that all calendars have been processed, all repeat-at-login alarms
    // will have been triggered. Prevent any new or updated repeat-at-login
    // alarms (e.g. when they are edited by the user) triggering from now on.
    mIgnoreAtLogin = true;
}

499
500
501
502
503
/******************************************************************************
* Called when a resource has been added.
* Add its KAEvent instances to those held by AlarmCalendar.
* All events must have their resource ID set.
*/
504
void ResourcesCalendar::slotResourceAdded(Resource& resource)
505
506
507
508
{
    slotEventsAdded(resource, resource.events());
}

509
/******************************************************************************
510
* Called when events have been added to a resource.
511
* Add corresponding KAEvent instances to those held by AlarmCalendar.
512
* All events must have their resource ID set.
513
*/
514
void ResourcesCalendar::slotEventsAdded(Resource& resource, const QList<KAEvent>& events)
515
{
516
517
    for (const KAEvent& event : events)
        slotEventUpdated(resource, event);
518
519
520
}

/******************************************************************************
521
* Called when an event has been changed in a resource.
522
* Change the corresponding KAEvent instance held by AlarmCalendar.
523
* The event must have its resource ID set.
524
*/
525
void ResourcesCalendar::slotEventUpdated(Resource& resource, const KAEvent& event)
526
{
527
528
    bool added = true;
    bool updated = false;
529
    KAEventMap::Iterator it = mEventMap.find(EventId(event));
530
531
532
533
    if (it != mEventMap.end())
    {
        // The event ID already exists - remove the existing event first
        KAEvent* storedEvent = it.value();
534
        if (event.category() == storedEvent->category())
535
536
        {
            // The existing event is the same type - update it in place
537
            *storedEvent = event;
538
            addNewEvent(resource, storedEvent, true);
539
540
541
542
543
544
545
            updated = true;
        }
        else
            delete storedEvent;
        added = false;
    }
    if (!updated)
546
        addNewEvent(resource, new KAEvent(event));
547

548
    if (event.category() == CalEvent::ACTIVE)
549
    {
550
        bool enabled = event.enabled();
551
        checkForDisabledAlarms(!enabled, enabled);
552
553
        if (!mIgnoreAtLogin  &&  added  &&  enabled  &&  event.repeatAtLogin())
            Q_EMIT atLoginEventAdded(event);
554
    }
555
556
557
}

/******************************************************************************
558
* Called when events are about to be removed from a resource.
559
560
* Remove the corresponding KAEvent instances held by AlarmCalendar.
*/
561
void ResourcesCalendar::slotEventsToBeRemoved(Resource& resource, const QList<KAEvent>& events)
562
{
563
    for (const KAEvent& event : events)
564
    {
565
566
        if (mEventMap.contains(EventId(event)))
            deleteEventInternal(event, resource, false);
567
    }
568
}
David Jarvie's avatar
David Jarvie committed
569

David Jarvie's avatar
David Jarvie committed
570
571
572
573
/******************************************************************************
* This method must only be called from the main KAlarm queue processing loop,
* to prevent asynchronous calendar operations interfering with one another.
*
574
* Purge a list of archived events from the calendar.
David Jarvie's avatar
David Jarvie committed
575
*/
576
void ResourcesCalendar::purgeEvents(const KAEvent::List& events)
David Jarvie's avatar
David Jarvie committed
577
{
David Jarvie's avatar
David Jarvie committed
578
    for (const KAEvent* event : events)
579
    {
David Jarvie's avatar
David Jarvie committed
580
        deleteEventInternal(*event);
581
    }
582
583
    if (mHaveDisabledAlarms)
        checkForDisabledAlarms();
David Jarvie's avatar
David Jarvie committed
584
585
}

586
587
/******************************************************************************
* Add the specified event to the calendar.
David Jarvie's avatar
David Jarvie committed
588
* If it is an active event and 'useEventID' is false, a new event ID is
589
590
* created. In all other cases, the event ID is taken from 'evnt' (if non-null).
* 'evnt' is updated with the actual event ID.
591
592
593
594
* The event is added to 'resource' if specified; otherwise the default resource
* is used or the user is prompted, depending on policy. If 'noPrompt' is true,
* the user will not be prompted so that if no default resource is defined, the
* function will fail.
595
596
* Reply = true if 'evnt' was written to the calendar. 'evnt' is updated.
*       = false if an error occurred, in which case 'evnt' is unchanged.
597
*/
598
bool ResourcesCalendar::addEvent(KAEvent& evnt, QWidget* promptParent, bool useEventID, Resource* resourceptr, bool noPrompt, bool* cancelled)
599
{
600
601
    if (cancelled)
        *cancelled = false;
602
603
    Resource nullresource;
    Resource& resource(resourceptr ? *resourceptr : nullresource);
604
605
606
    qCDebug(KALARM_LOG) << "ResourcesCalendar::addEvent:" << evnt.id() << ", resource" << resource.displayId();

    // Check that the event type is valid for the calendar
David Jarvie's avatar
David Jarvie committed
607
    const CalEvent::Type type = evnt.category();
608
    switch (type)
609
    {
610
611
612
613
614
615
        case CalEvent::ACTIVE:
        case CalEvent::ARCHIVED:
        case CalEvent::TEMPLATE:
            break;
        default:
            return false;
616
617
    }

618
    const ResourceId key = resource.id();
619
620
    KAEvent* event = new KAEvent(evnt);
    QString id = event->id();
621
    if (type == CalEvent::ACTIVE)
622
623
624
625
626
627
628
629
630
    {
        if (id.isEmpty())
            useEventID = false;
        else if (!useEventID)
            id.clear();
    }
    else
        useEventID = true;
    if (id.isEmpty())
631
        id = CalFormat::createUniqueId();
632
    if (useEventID)
633
        id = CalEvent::uid(id, type);   // include the alarm type tag in the ID
634
    event->setEventId(id);
635

636
637
    bool ok = false;
    bool remove = false;
638
639
640
641
    Resource res;
    if (resource.isEnabled(type))
        res = resource;
    else
642
    {
643
644
        res = Resources::destination(type, promptParent, noPrompt, cancelled);
        if (!res.isValid())
645
        {
646
647
            const char* typeStr = (type == CalEvent::ACTIVE) ? "Active alarm" : (type == CalEvent::ARCHIVED) ? "Archived alarm" : "alarm Template";
            qCWarning(KALARM_LOG) << "ResourcesCalendar::addEvent: Error! Cannot create" << typeStr << "(No default calendar is defined)";
648
649
        }
    }
650
    if (res.isValid())
651
    {
652
653
654
        // Don't add event to mEventMap yet - its ID is not yet known.
        // It will be added after it is inserted into the data model, when
        // the resource signals eventsAdded().
655
656
657
658
        ok = res.addEvent(*event);
        remove = ok;   // if success, delete the local event instance on exit
        if (ok  &&  type == CalEvent::ACTIVE  &&  !event->enabled())
            checkForDisabledAlarms(true, false);
659
660
661
662
663
664
    }
    if (!ok)
    {
        if (remove)
        {
            // Adding to mCalendar failed, so undo AlarmCalendar::addEvent()
665
            mEventMap.remove(EventId(key, event->id()));
666
667
668
669
            KAEvent::List& events = mResourceMap[key];
            int i = events.indexOf(event);
            if (i >= 0)
                events.remove(i);
670
671
672
673
674
675
676
677
678
679
            if (mEarliestAlarm[key] == event)
                findEarliestAlarm(key);
        }
        delete event;
        return false;
    }
    evnt = *event;
    if (remove)
        delete event;
    return true;
680
681
}

682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
/******************************************************************************
* Add the specified event to the calendar.
* Reply = true if 'evnt' was written to the calendar. 'evnt' is updated.
*       = false if an error occurred, in which case 'evnt' is unchanged.
*/
bool DisplayCalendar::addEvent(KAEvent& evnt)
{
    if (!mOpen)
        return false;
    qCDebug(KALARM_LOG) << "DisplayCalendar::addEvent:" << evnt.id();
    // Check that the event type is valid for the calendar
    const CalEvent::Type type = evnt.category();
    if (type != CalEvent::DISPLAYING)
        return false;

    Event::Ptr kcalEvent(new Event);
    KAEvent* event = new KAEvent(evnt);
    QString id = event->id();
    if (id.isEmpty())
        id = kcalEvent->uid();
    id = CalEvent::uid(id, type);   // include the alarm type tag in the ID
    kcalEvent->setUid(id);
    event->setEventId(id);
    event->updateKCalEvent(kcalEvent, KAEvent::UID_IGNORE);

    bool ok = false;
    bool remove = false;
    const ResourceId key = DISPLAY_RES_ID;
    if (!mEventMap.contains(EventId(key, event->id())))
    {
        addNewEvent(Resource(), event);
        ok = mCalendarStorage->calendar()->addEvent(kcalEvent);
        remove = !ok;
    }
    if (!ok)
    {
        if (remove)
        {
            // Adding to mCalendar failed, so undo AlarmCalendar::addEvent()
            mEventMap.remove(EventId(key, event->id()));
            KAEvent::List& events = mResourceMap[key];
            int i = events.indexOf(event);
            if (i >= 0)
                events.remove(i);
        }
        delete event;
        return false;
    }
    evnt = *event;
    if (remove)
        delete event;
    return true;
}
735
736
737

/******************************************************************************
* Internal method to add an already checked event to the calendar.
738
739
740
* mEventMap takes ownership of the KAEvent.
* If 'replace' is true, an existing event is being updated (NOTE: its category()
* must remain the same).
741
*/
742
void AlarmCalendar::addNewEvent(const Resource& resource, KAEvent* event, bool replace)
743
{
David Jarvie's avatar
David Jarvie committed
744
    const ResourceId key = resource.id();
745
    event->setResourceId(key);
746
747
748
    if (!replace)
    {
        mResourceMap[key] += event;
749
        mEventMap[EventId(key, event->id())] = event;
750
    }
751
752
753
754
755
756
757
758
759
760
761
762
}

/******************************************************************************
* Internal method to add an already checked event to the calendar.
* mEventMap takes ownership of the KAEvent.
* If 'replace' is true, an existing event is being updated (NOTE: its category()
* must remain the same).
*/
void ResourcesCalendar::addNewEvent(const Resource& resource, KAEvent* event, bool replace)
{
    AlarmCalendar::addNewEvent(resource, event, replace);

763
    if ((resource.alarmTypes() & CalEvent::ACTIVE)
764
    &&  event->category() == CalEvent::ACTIVE)
765
766
    {
        // Update the earliest alarm to trigger
767
        const ResourceId key = resource.id();
David Jarvie's avatar
David Jarvie committed
768
        const KAEvent* earliest = mEarliestAlarm.value(key, (KAEvent*)nullptr);
769
770
771
772
        if (replace  &&  earliest == event)
            findEarliestAlarm(key);
        else
        {
773
            const KADateTime dt = event->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
774
775
776
777
            if (dt.isValid()
            &&  (!earliest  ||  dt < earliest->nextTrigger(KAEvent::ALL_TRIGGER)))
            {
                mEarliestAlarm[key] = event;
Laurent Montel's avatar
Laurent Montel committed
778
                Q_EMIT earliestAlarmChanged();
779
780
781
            }
        }
    }
782
783
}

784
785
786
/******************************************************************************
* Modify the specified event in the calendar with its new contents.
* The new event must have a different event ID from the old one.
787
* It is assumed to be of the same event type as the old one (active, etc.)
788
* Reply = true if 'newEvent' was written to the calendar. 'newEvent' is updated.
789
*       = false if an error occurred, in which case 'newEvent' is unchanged.
790
*/
791
bool ResourcesCalendar::modifyEvent(const EventId& oldEventId, KAEvent& newEvent)
792
{
793
    const EventId newId(oldEventId.resourceId(), newEvent.id());
794
    qCDebug(KALARM_LOG) << "ResourcesCalendar::modifyEvent:" << oldEventId << "->" << newId;
795
796
797
    bool noNewId = newId.isEmpty();
    if (!noNewId  &&  oldEventId == newId)
    {
798
        qCCritical(KALARM_LOG) << "ResourcesCalendar::modifyEvent: Same IDs";
799
800
        return false;
    }
801
802
803
804

    // Set the event's ID, and update the old event in the resources calendar.
    const KAEvent* storedEvent = event(oldEventId);
    if (!storedEvent)
805
    {
806
        qCCritical(KALARM_LOG) << "ResourcesCalendar::modifyEvent: Old event not found";
807
        return false;
808
    }
809
810
811
812
813
    if (noNewId)
        newEvent.setEventId(CalFormat::createUniqueId());
    Resource resource = Resources::resource(oldEventId.resourceId());
    if (!resource.isValid())
        return false;
814
815
    // Don't add new event to mEventMap yet - it will be added when the resource
    // signals eventsAdded().
816
817
818
819
820
821
822
    if (!resource.addEvent(newEvent))
        return false;
    // Note: deleteEventInternal() will delete storedEvent before using the
    // event parameter, so need to pass a copy as the parameter.
    deleteEventInternal(KAEvent(*storedEvent), resource);
    if (mHaveDisabledAlarms)
        checkForDisabledAlarms();
823
    return true;
824
825
}

826
827
/******************************************************************************
* Update the specified event in the calendar with its new contents.
828
* The event retains the same ID. The event must be in the resource calendar.
829
830
* Reply = event which has been updated
*       = 0 if error.
831
*/
832
KAEvent* ResourcesCalendar::updateEvent(const KAEvent& evnt)
833
{
834
    KAEvent* kaevnt = event(EventId(evnt));
835
836
    if (kaevnt)
    {
837
838
        Resource resource = Resources::resourceForEvent(evnt.id());
        if (resource.updateEvent(evnt))
839
        {
840
            *kaevnt = evnt;
841
842
843
            return kaevnt;
        }
    }
844
    qCDebug(KALARM_LOG) << "ResourcesCalendar::updateEvent: error";
Laurent Montel's avatar
Laurent Montel committed
845
    return nullptr;
846
847
}

848

849
850
851
852
/******************************************************************************
* Delete the specified event from the resource calendar, if it exists.
* The calendar is then optionally saved.
*/
853
bool ResourcesCalendar::deleteEvent(const KAEvent& event, bool saveit)
854
{
855
856
857
858
859
860
    Q_UNUSED(saveit);

    const CalEvent::Type status = deleteEventInternal(event);
    if (mHaveDisabledAlarms)
        checkForDisabledAlarms();
    return status != CalEvent::EMPTY;
861
862
}

863
864
865
866
/******************************************************************************
* Delete the specified event from the calendar, if it exists.
* The calendar is then optionally saved.
*/
867
bool DisplayCalendar::deleteEvent(const QString& eventID, bool saveit)
868
{
869
    if (mOpen)
870
    {
871
872
873
874
        Event::Ptr kcalEvent;
        if (mCalendarStorage)
            kcalEvent = mCalendarStorage->calendar()->event(eventID);   // display calendar

875
        Resource resource;
876
877
878
879
880
881
882
883
884
        deleteEventBase(eventID, resource);

        CalEvent::Type status = CalEvent::EMPTY;
        if (kcalEvent)
        {
            status = CalEvent::status(kcalEvent);
            mCalendarStorage->calendar()->deleteEvent(kcalEvent);
        }

885
        if (status != CalEvent::EMPTY)
886
887
888
889
890
891
892
        {
            if (saveit)
                return save();
            return true;
        }
    }
    return false;
893
894
}

895
896
/******************************************************************************
* Internal method to delete the specified event from the calendar and lists.
897
898
* Reply = event status, if it was found in the resource calendar/calendar
*         resource or local calendar
899
*       = CalEvent::EMPTY otherwise.
900
*/
901
CalEvent::Type ResourcesCalendar::deleteEventInternal(const KAEvent& event, bool deleteFromResources)
902
{
903
    Resource resource = Resources::resource(event.resourceId());
904
    if (!resource.isValid())
905
        return CalEvent::EMPTY;
906
    return deleteEventInternal(event.id(), event, resource, deleteFromResources);
907
908
}

909
CalEvent::Type ResourcesCalendar::deleteEventInternal(const KAEvent& event, Resource& resource, bool deleteFromResources)
910
{
911
    if (!resource.isValid())
912
        return CalEvent::EMPTY;
913
    if (event.resourceId() != resource.id())
914
    {
915
        qCCritical(KALARM_LOG) << "ResourcesCalendar::deleteEventInternal: Event" << event.id() << ": resource" << event.resourceId() << "differs from 'resource'" << resource.id();
916
917
        return CalEvent::EMPTY;
    }
918
    return deleteEventInternal(event.id(), event, resource, deleteFromResources);
919
920
}

921
CalEvent::Type ResourcesCalendar::deleteEventInternal(const QString& eventID, const KAEvent& event, Resource& resource, bool deleteFromResources)
922
{
923
924
    // Make a copy of the KAEvent and the ID QString, since the supplied
    // references might be destructed when the event is deleted below.
925
    const QString id = eventID;
David Jarvie's avatar
David Jarvie committed
926
927
    const KAEvent paramEvent = event;

928

David Jarvie's avatar
David Jarvie committed
929
    const ResourceId key = resource.id();
930
931
    KAEvent* ev = deleteEventBase(eventID, resource);
    if (ev)
932
933
    {
        if (mEarliestAlarm[key] == ev)
934
            findEarliestAlarm(resource);
935
936
937
938
939
    }
    else
    {
        for (EarliestMap::Iterator eit = mEarliestAlarm.begin();  eit != mEarliestAlarm.end();  ++eit)
        {
940
            ev = eit.value();
941
942
943
944
945
946
947
            if (ev  &&  ev->id() == id)
            {
                findEarliestAlarm(eit.key());
                break;
            }
        }
    }
948
949


950
    CalEvent::Type status = CalEvent::EMPTY;
951
    if (deleteFromResources)
952
    {
953
        // Delete from the resources calendar
954
        CalEvent::Type s = paramEvent.category();
955
        if (resource.deleteEvent(paramEvent))
956
957
958
            status = s;
    }
    return status;
959
960
}

961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
/******************************************************************************
* Internal method to delete the specified event from the calendar and lists.
* Reply = event which was deleted, or null if not found.
*/
KAEvent* AlarmCalendar::deleteEventBase(const QString& eventID, Resource& resource)
{
    // Make a copy of the ID QString, since the supplied reference might be
    // destructed when the event is deleted below.
    const QString id = eventID;

    const ResourceId key = resource.id();
    KAEventMap::Iterator it = mEventMap.find(EventId(key, id));
    if (it == mEventMap.end())
        return nullptr;

    KAEvent* ev = it.value();
    mEventMap.erase(it);
    KAEvent::List& events = mResourceMap[key];
    int i = events.indexOf(ev);
    if (i >= 0)
        events.remove(i);
    delete ev;
    return ev;
}

/******************************************************************************
* Return the event with the specified ID.
* If 'findUniqueId' is true, and the resource ID is invalid, if there is a
* unique event with the given ID, it will be returned.
*/
KAEvent* AlarmCalendar::event(const EventId& uniqueID)
{
    if (!isValid())