1 /***************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (directui@nokia.com)
9 ** If you have questions regarding the use of this file, please contact
10 ** Nokia at directui@nokia.com.
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
18 ****************************************************************************/
19 #include "mfiledatastore.h"
20 #include "mfiledatastore_p.h"
21 #include <QTemporaryFile>
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
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.
36 static QString createTempFile(const QString &originalPath)
39 QTemporaryFile tempFile(originalPath);
40 if (tempFile.open()) {
41 tempFile.setAutoRemove(false);
42 returnValue = tempFile.fileName();
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.
53 static bool copySettings(const QSettings &originalSettings,
54 QSettings &newSettings)
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) {
67 * Renames a file. Ensures that a file with the new name
69 * \param oldname Name of the file to rename.
70 * \param newName New name.
72 static void renameSettingFile(const QString &oldName,
73 const QString &newName)
75 QFile::remove(newName);
76 QFile::rename(oldName, newName);
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.
84 static void addPathsToWatcher(const QString &filePath,
85 QScopedPointer<QFileSystemWatcher>& watcher)
87 QFileInfo fileInfo(filePath);
89 bool fileExists = fileInfo.exists();
91 // If the file exists, we can take the canonical path directly
92 directory = fileInfo.canonicalPath();
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();
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);
109 // Watch the file itself if it's not being watched yet
110 if (fileExists && !watcher->files().contains(filePath)) {
111 watcher->addPath(filePath);
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.
122 static bool doSync(QSettings &originalSettings, QScopedPointer<QFileSystemWatcher>& watcher)
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());
136 addPathsToWatcher(originalSettings.fileName(), watcher);
140 MFileDataStorePrivate::MFileDataStorePrivate(const QString &filePath) :
141 settings(filePath, QSettings::IniFormat),
142 watcher(new QFileSystemWatcher())
147 MFileDataStore::MFileDataStore(const QString &filePath) :
148 d_ptr(new MFileDataStorePrivate(filePath))
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)));
159 MFileDataStore::~MFileDataStore()
164 bool MFileDataStore::createValue(const QString &key, const QVariant &value)
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
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);
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);
183 } else if (originalValueSet) {
184 // if sync fails, make sure the value in memory is the original
185 d->settings.setValue(key, originalValue);
187 d->settings.remove(key);
194 bool MFileDataStore::setValue(const QString &key, const QVariant &value)
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);
206 // Emit valueChanged signal when value is changed
207 if (originalValue != value) {
208 d->settingsSnapshot[key] = value;
209 emit valueChanged(key, value);
212 // if sync fails, make sure the value in memory is the original
213 d->settings.setValue(key, originalValue);
219 QVariant MFileDataStore::value(const QString &key) const
221 Q_D(const MFileDataStore);
222 return d->settings.value(key);
225 QStringList MFileDataStore::allKeys() const
227 Q_D(const MFileDataStore);
228 return d->settings.allKeys();
231 void MFileDataStore::remove(const QString &key)
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
237 bool originalValueSet = d->settings.contains(key);
238 if (!originalValueSet) {
241 QVariant originalValue = d->settings.value(key);
242 d->settings.remove(key);
243 bool syncOk = doSync(d->settings, d->watcher);
245 if (originalValueSet) {
246 // if sync fails, make sure the value in memory is the original
247 d->settings.setValue(key, originalValue);
250 d->settingsSnapshot.remove(key);
251 emit valueChanged(key, QVariant());
256 void MFileDataStore::clear()
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
268 bool MFileDataStore::contains(const QString &key) const
270 Q_D(const MFileDataStore);
271 return d->settings.contains(key);
274 bool MFileDataStore::isReadable() const
276 Q_D(const MFileDataStore);
277 return d->settings.status() == QSettings::NoError;
280 bool MFileDataStore::isWritable() const
282 Q_D(const MFileDataStore);
283 return d->settings.isWritable() && d->settings.status() == QSettings::NoError;
286 void MFileDataStore::takeSnapshot()
289 d->settingsSnapshot.clear();
290 foreach(const QString & key, d->settings.allKeys()) {
291 d->settingsSnapshot.insert(key, d->settings.value(key));
295 void MFileDataStore::fileChanged(const QString &fileName)
298 // sync the settings and add the path, for observing
299 // the file even if it was deleted
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));
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));
322 void MFileDataStore::directoryChanged(const QString &fileName)
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());