1 /*
2  *  singlefileresource.h  -  calendar resource held in a single file
3  *  Program:  kalarm
4  *  SPDX-FileCopyrightText: 2020-2021 David Jarvie <djarvie@kde.org>
5  *
6  *  SPDX-License-Identifier: LGPL-2.0-or-later
7  */
8 
9 #pragma once
10 
11 #include "fileresource.h"
12 #include "fileresourceconfigmanager.h"
13 
14 #include <KCalendarCore/MemoryCalendar>
15 #include <KCalendarCore/FileStorage>
16 
17 #include <QUrl>
18 
19 namespace KIO {
20 class FileCopyJob;
21 }
22 class KJob;
23 class QTimer;
24 
25 using namespace KAlarmCal;
26 
27 /**
28  * Calendar resource stored in a single file, either local or remote.
29  */
30 class SingleFileResource : public FileResource
31 {
32     Q_OBJECT
33 public:
34     /** Construct a new SingleFileResource.
35      *  Initialises the resource and initiates loading its events.
36      */
37     static Resource create(FileResourceSettings* settings);
38 
39 protected:
40     /** Constructor.
41      *  Initialises the resource and initiates loading its events.
42      */
43     explicit SingleFileResource(FileResourceSettings* settings);
44 
45 public:
46     ~SingleFileResource() override;
47 
48     /** Return the type of storage used by the resource. */
storageType()49     StorageType storageType() const override   { return File; }
50 
51     /** Return whether the resource is configured as read-only or is
52      *  read-only on disc.
53      */
54     bool readOnly() const override;
55 
56     /** Return whether the resource is both enabled and fully writable for a
57      *  given alarm type, i.e. not read-only, and compatible with the current
58      *  KAlarm calendar format.
59      *
60      *  @param type  alarm type to check for, or EMPTY to check for any type.
61      *  @return 1 = fully enabled and writable,
62      *          0 = enabled and writable except that backend calendar is in an
63      *              old KAlarm format,
64      *         -1 = read-only, disabled or incompatible format.
65      */
66     int writableStatus(CalEvent::Type type = CalEvent::EMPTY) const override;
67 
68     /** Reload the resource. Any cached data is first discarded.
69      *  @param discardMods  Discard any modifications since the last save.
70      *  @return true if loading succeeded or has been initiated.
71      *          false if it failed.
72      */
73     bool reload(bool discardMods = false) override;
74 
75     /** Return whether the resource is waiting for a save() to complete. */
76     bool isSaving() const override;
77 
78     /** Close the resource. This saves any unsaved data.
79      *  Saving is not performed if the resource is disabled.
80      */
81     void close() override;
82 
83     /** Called when the resource's FileResourceSettings object is about to be destroyed. */
84     void removeSettings() override;
85 
86 protected:
87     /** Update the resource to the current KAlarm storage format. */
88     bool updateStorageFmt() override;
89 
90     /** This method is called by load() to allow derived classes to implement
91      *  loading the resource from its backend, and fetch all events into
92      *  @p newEvents.
93      *  If the resource is cached, it should be loaded from the cache file (which
94      *  if @p readThroughCache is true, should first be downloaded from the
95      *  resource file).
96      *  If the resource initiates but does not complete loading, loaded() must be
97      *  called when loading completes or fails.
98      *  @see loaded()
99      *
100      *  @param newEvents         To be updated to contain the events fetched.
101      *  @param readThroughCache  If the resource is cached, refresh the cache first.
102      *  @return 1 = loading succeeded
103      *          0 = loading has been initiated, but has not yet completed
104      *         -1 = loading failed.
105      */
106     int doLoad(QHash<QString, KAEvent>& newEvents, bool readThroughCache, QString& errorMessage) override;
107 
108     /** This method is called by save() to allow derived classes to implement
109      *  saving the resource to its backend.
110      *  If the resource is cached, it should be saved to the cache file (which
111      *  if @p writeThroughCache is true, should then be uploaded from the
112      *  resource file).
113      *  If the resource initiates but does not complete saving, saved() must be
114      *  called when saving completes or fails.
115      *  @see saved()
116      *
117      *  @param writeThroughCache  If the resource is cached, update the file
118      *                            after writing to the cache.
119      *  @param force              Save even if no changes have been made since last
120      *                            loaded or saved.
121      *  @return 1 = saving succeeded
122      *          0 = saving has been initiated, but has not yet completed
123      *         -1 = saving failed.
124      */
125     int doSave(bool writeThroughCache, bool force, QString& errorMessage) override;
126 
127     /** Schedule the resource for saving.
128      *  This delays calling save(), so as to enable multiple event changes to
129      *  be saved together.
130      *
131      *  @return true if saving succeeded or has been initiated/scheduled.
132      *          false if it failed.
133      */
134     bool scheduleSave(bool writeThroughCache = true) override;
135 
136     /**
137      * Handles everything needed when the hash of a file has changed between the
138      * last write and the first read. This stores the new hash in a config file
139      * and notifies implementing resources to handle a hash change if the
140      * previous known hash was not empty.
141      * Returns true on success, false otherwise.
142      */
143     bool readLocalFile(const QString& fileName, QString& errorMessage);
144 
145     /** Read the data from the given local file. */
146     bool readFromFile(const QString& fileName, QString& errorMessage);
147 
148     /**
149      * Reimplement to write your data to the given file.
150      * The file is always local, storing back to the network url is done
151      * automatically when needed.
152      */
153     bool writeToFile(const QString& fileName, QString& errorMessage);
154 
155     /** This method is called by addEvent() to allow derived classes to add
156      *  an event to the resource.
157      */
158     bool doAddEvent(const KAEvent&) override;
159 
160     /** This method is called by updateEvent() to allow derived classes to update
161      *  an event in the resource. The event's UID must be unchanged.
162      */
163     bool doUpdateEvent(const KAEvent&) override;
164 
165     /** This method is called by deleteEvent() to allow derived classes to delete
166      *  an event from the resource.
167      */
168     bool doDeleteEvent(const KAEvent&) override;
169 
170     /** Called when settings have changed, to allow derived classes to process
171      *  the changes.
172      *  @note  Resources::notifySettingsChanged() is called after this, to
173      *         notify clients.
174      */
175     void handleSettingsChange(Changes&) override;
176 
177     /**
178      * Generates the full path for the cache file in the case that a remote file
179      * is used.
180      */
181     QString cacheFilePath() const;
182 
183     /**
184      * Calculates an MD5 hash for given file. If the file does not exists
185      * or the path is empty, this will return an empty QByteArray.
186      */
187     QByteArray calculateHash(const QString& fileName) const;
188 
189     /**
190      * Stores the given hash into the config file.
191      */
192     void saveHash(const QByteArray& hash) const;
193 
194 private Q_SLOTS:
slotSave()195     void slotSave()   { save(nullptr, mSavePendingCache); }
196 //    void handleProgress(KJob*, unsigned long);
197     void localFileChanged(const QString& fileName);
198     void slotDownloadJobResult(KJob*);
199     void slotUploadJobResult(KJob*);
updateFormat()200     void updateFormat()    { updateStorageFmt(); }
201     bool addLoadedEvent(const KCalendarCore::Event::Ptr&);
202 
203 private:
204     void setLoadFailure(bool exists, Status);
205 
206     QUrl               mSaveUrl;   // current local file for save() to use (may be temporary)
207     KIO::FileCopyJob*  mDownloadJob {nullptr};
208     KIO::FileCopyJob*  mUploadJob {nullptr};
209     QByteArray         mCurrentHash;
210     KCalendarCore::MemoryCalendar::Ptr mCalendar;
211     KCalendarCore::FileStorage::Ptr    mFileStorage;
212     QHash<QString, KAEvent> mLoadedEvents;    // events loaded from calendar last time file was read
213     QTimer*            mSaveTimer {nullptr};  // timer to enable multiple saves to be grouped
214     bool               mSavePendingCache;     // writeThroughCache parameter for delayed save()
215     bool               mFileReadOnly {false}; // the calendar file is a read-only local file
216 };
217 
218 
219 // vim: et sw=4:
220