fix packaging
[profile/ivi/mlite.git] / mfiledatastore.cpp
1 /***************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (directui@nokia.com)
6 **
7 ** .
8 **
9 ** If you have questions regarding the use of this file, please contact
10 ** Nokia at directui@nokia.com.
11 **
12 ** This library is free software; you can redistribute it and/or
13 ** modify it under the terms of the GNU Lesser General Public
14 ** License version 2.1 as published by the Free Software Foundation
15 ** and appearing in the file LICENSE.LGPL included in the packaging
16 ** of this file.
17 **
18 ****************************************************************************/
19 #include "mfiledatastore.h"
20 #include "mfiledatastore_p.h"
21 #include <QTemporaryFile>
22 #include <QFileInfo>
23
24 /*!
25  * Creates a temporary file in the directory of an original file.
26  * \param originalPath Absolute path that is used as a base for generating
27  * the temporary file.
28  * \return Path of the created file, or an empty string if creating the
29  * file fails. The returned value is a copy, so for uses where it's assigned
30  * to a variable, it need not be const. For cases where it's passed as a
31  * parameter, it need not be const because references to non-const will
32  * not bind to function return values, because they are rvalues. When
33  * C++0x brings rvalue references, the value should not be const in order
34  * to allow rvalue references to bind to it and thus enable moving from it.
35  */
36 static QString createTempFile(const QString &originalPath)
37 {
38     QString returnValue;
39     QTemporaryFile tempFile(originalPath);
40     if (tempFile.open()) {
41         tempFile.setAutoRemove(false);
42         returnValue = tempFile.fileName();
43     }
44     return returnValue;
45 }
46
47 /*!
48  * Copies settings from a QSettings object to another QSettings object.
49  * \param originalSettings Settings to copy.
50  * \param newSettings Target of the copy.
51  * \return true if copying succeeds, false if it fails.
52  */
53 static bool copySettings(const QSettings &originalSettings,
54                          QSettings &newSettings)
55 {
56     QStringList keys = originalSettings.allKeys();
57     foreach(const QString & key, originalSettings.allKeys()) {
58         newSettings.setValue(key, originalSettings.value(key));
59         if (newSettings.status() != QSettings::NoError) {
60             return false;
61         }
62     }
63     return true;
64 }
65
66 /*!
67  * Renames a file. Ensures that a file with the new name
68  * doesn't exist.
69  * \param oldname Name of the file to rename.
70  * \param newName New name.
71  */
72 static void renameSettingFile(const QString &oldName,
73                               const QString &newName)
74 {
75     QFile::remove(newName);
76     QFile::rename(oldName, newName);
77 }
78
79 /*!
80  * Adds the necessary paths to the file system watcher
81  * \param filePath Path (including name) of the file to watch.
82  * \param watcher The file system watcher.
83  */
84 static void addPathsToWatcher(const QString &filePath,
85                               QScopedPointer<QFileSystemWatcher>& watcher)
86 {
87     QFileInfo fileInfo(filePath);
88     QString directory;
89     bool fileExists = fileInfo.exists();
90     if (fileExists) {
91         // If the file exists, we can take the canonical path directly
92         directory = fileInfo.canonicalPath();
93     } else {
94         // If the file doesn't exist, canonicalPath would return an empty string. That's why
95         // we need to get the parent directory first.
96         QFileInfo parentPath(fileInfo.absolutePath());
97         if (parentPath.exists()) {
98             directory = parentPath.canonicalFilePath();
99         }
100     }
101
102     if (!directory.isEmpty()) {
103         // Watch the directory if it's not being watched yet
104         if (!watcher->directories().contains(directory)) {
105             watcher->addPath(directory);
106         }
107     }
108
109     // Watch the file itself if it's not being watched yet
110     if (fileExists && !watcher->files().contains(filePath)) {
111         watcher->addPath(filePath);
112     }
113 }
114
115 /*!
116  * Saves the settings to a file. The settings are first
117  * saved to a temporary file, and then that file is copied
118  * over the original settings. This avoids clearing settings
119  * when there's no disk space.
120  * \param originalSettings Settings to save.
121  */
122 static bool doSync(QSettings &originalSettings, QScopedPointer<QFileSystemWatcher>& watcher)
123 {
124     bool returnValue = false;
125     QString tempFileName = createTempFile(originalSettings.fileName());
126     if (!tempFileName.isEmpty()) {
127         QSettings copiedSettings(tempFileName, QSettings::IniFormat);
128         if (copySettings(originalSettings, copiedSettings)) {
129             copiedSettings.sync();
130             if (copiedSettings.status() == QSettings::NoError) {
131                 renameSettingFile(tempFileName, originalSettings.fileName());
132                 returnValue = true;
133             }
134         }
135     }
136     addPathsToWatcher(originalSettings.fileName(), watcher);
137     return returnValue;
138 }
139
140 MFileDataStorePrivate::MFileDataStorePrivate(const QString &filePath) :
141     settings(filePath, QSettings::IniFormat),
142     watcher(new QFileSystemWatcher())
143 {
144     settings.sync();
145 }
146
147 MFileDataStore::MFileDataStore(const QString &filePath) :
148     d_ptr(new MFileDataStorePrivate(filePath))
149 {
150     Q_D(MFileDataStore);
151     takeSnapshot();
152     addPathsToWatcher(filePath, d->watcher);
153     connect(d->watcher.data(), SIGNAL(fileChanged(QString)),
154             this, SLOT(fileChanged(QString)));
155     connect(d->watcher.data(), SIGNAL(directoryChanged(QString)),
156             this, SLOT(directoryChanged(QString)));
157 }
158
159 MFileDataStore::~MFileDataStore()
160 {
161     delete d_ptr;
162 }
163
164 bool MFileDataStore::createValue(const QString &key, const QVariant &value)
165 {
166     Q_D(MFileDataStore);
167     bool returnValue = false;
168     // QSettings has some kind of a cache so we'll prevent any temporary writes
169     // by checking if the data can be actually stored before doing anything
170     if (isWritable()) {
171         bool originalValueSet = d->settings.contains(key);
172         QVariant originalValue = d->settings.value(key);
173         d->settings.setValue(key, value);
174         bool syncOk = doSync(d->settings, d->watcher);
175         if (syncOk) {
176             returnValue = true;
177             // Emit valueChanged signal when value is changed or a new key is added
178             if ((originalValueSet && originalValue != value)
179                     || !originalValueSet) {
180                 d->settingsSnapshot[key] = value;
181                 emit valueChanged(key, value);
182             }
183         } else if (originalValueSet) {
184             // if sync fails, make sure the value in memory is the original
185             d->settings.setValue(key, originalValue);
186         } else {
187             d->settings.remove(key);
188         }
189
190     }
191     return returnValue;
192 }
193
194 bool MFileDataStore::setValue(const QString &key, const QVariant &value)
195 {
196     Q_D(MFileDataStore);
197     bool returnValue = false;
198     // QSettings has some kind of a cache so we'll prevent any temporary writes
199     // by checking if the data can be actually stored before doing anything
200     if (isWritable() && d->settings.contains(key)) {
201         QVariant originalValue = d->settings.value(key);
202         d->settings.setValue(key, value);
203         bool syncOk = doSync(d->settings, d->watcher);
204         if (syncOk) {
205             returnValue = true;
206             // Emit valueChanged signal when value is changed
207             if (originalValue != value) {
208                 d->settingsSnapshot[key] = value;
209                 emit valueChanged(key, value);
210             }
211         } else {
212             // if sync fails, make sure the value in memory is the original
213             d->settings.setValue(key, originalValue);
214         }
215     }
216     return returnValue;
217 }
218
219 QVariant MFileDataStore::value(const QString &key) const
220 {
221     Q_D(const MFileDataStore);
222     return d->settings.value(key);
223 }
224
225 QStringList MFileDataStore::allKeys() const
226 {
227     Q_D(const MFileDataStore);
228     return d->settings.allKeys();
229 }
230
231 void MFileDataStore::remove(const QString &key)
232 {
233     Q_D(MFileDataStore);
234     // QSettings has some kind of a cache so we'll prevent any temporary writes
235     // by checking if the data can be actually stored before doing anything
236     if (isWritable()) {
237         bool originalValueSet = d->settings.contains(key);
238         if (!originalValueSet) {
239             return;
240         }
241         QVariant originalValue = d->settings.value(key);
242         d->settings.remove(key);
243         bool syncOk = doSync(d->settings, d->watcher);
244         if (!syncOk) {
245             if (originalValueSet) {
246                 // if sync fails, make sure the value in memory is the original
247                 d->settings.setValue(key, originalValue);
248             }
249         } else {
250             d->settingsSnapshot.remove(key);
251             emit valueChanged(key, QVariant());
252         }
253     }
254 }
255
256 void MFileDataStore::clear()
257 {
258     Q_D(MFileDataStore);
259     // QSettings has some kind of a cache so we'll prevent any temporary writes
260     // by checking if the data can be actually stored before doing anything
261     if (isWritable()) {
262         d->settings.clear();
263         d->settings.sync();
264         takeSnapshot();
265     }
266 }
267
268 bool MFileDataStore::contains(const QString &key) const
269 {
270     Q_D(const MFileDataStore);
271     return d->settings.contains(key);
272 }
273
274 bool MFileDataStore::isReadable() const
275 {
276     Q_D(const MFileDataStore);
277     return d->settings.status() == QSettings::NoError;
278 }
279
280 bool MFileDataStore::isWritable() const
281 {
282     Q_D(const MFileDataStore);
283     return d->settings.isWritable() && d->settings.status() == QSettings::NoError;
284 }
285
286 void MFileDataStore::takeSnapshot()
287 {
288     Q_D(MFileDataStore);
289     d->settingsSnapshot.clear();
290     foreach(const QString & key, d->settings.allKeys()) {
291         d->settingsSnapshot.insert(key, d->settings.value(key));
292     }
293 }
294
295 void MFileDataStore::fileChanged(const QString &fileName)
296 {
297     Q_D(MFileDataStore);
298     // sync the settings and add the path, for observing
299     // the file even if it was deleted
300     d->settings.sync();
301     addPathsToWatcher(d->settings.fileName(), d->watcher);
302     if (d->settings.fileName() == fileName && isWritable()) {
303         // Check whether the values for existing keys have changed or
304         // if keys have been deleted
305         foreach(const QString & key, d->settingsSnapshot.keys()) {
306             if ((d->settings.contains(key)
307                     && d->settings.value(key) != d->settingsSnapshot.value(key))
308                     || (!d->settings.contains(key))) {
309                 emit valueChanged(key, d->settings.value(key));
310             }
311         }
312         // Check whether new keys have been added
313         foreach(const QString & key, d->settings.allKeys()) {
314             if (!d->settingsSnapshot.contains(key)) {
315                 emit valueChanged(key, d->settings.value(key));
316             }
317         }
318         takeSnapshot();
319     }
320 }
321
322 void MFileDataStore::directoryChanged(const QString &fileName)
323 {
324     Q_D(MFileDataStore);
325     if (fileName == QFileInfo(d->settings.fileName()).canonicalPath()) {
326         // we can't know which file changed, so we'll sync at this
327         // point. This is not very optimal, but it at least works.
328         fileChanged(d->settings.fileName());
329     }
330 }
331