~ubuntu-branches/ubuntu/wily/psi/wily-proposed

« back to all changes in this revision

Viewing changes to src/filecache.cpp

  • Committer: Package Import Robot
  • Author(s): Jan Niehusmann
  • Date: 2014-07-01 21:49:34 UTC
  • mfrom: (6.1.7 sid)
  • Revision ID: package-import@ubuntu.com-20140701214934-gt4dkgm94byi4vnn
Tags: 0.15-1
* New upstream version
* set debhelper compat level to 9
* set Standards-Version to 3.9.5 (no further changes)
* add lintian override regarding license-problem-non-free-RFC
* use qconf to regenerate configure script
* implement hardening using buildflags instead of hardening-wrapper

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * filecache.cpp - File storage with age and size control
 
3
 * Copyright (C) 2010 Rion
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU General Public
 
7
 * License as published by the Free Software Foundation; either
 
8
 * version 2 of the License, or (at your option) any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
 * Lesser General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public
 
16
 * License along with this library; if not, write to the Free Software
 
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
18
 *
 
19
 */
 
20
 
 
21
#include <QTimer>
 
22
#include <QCryptographicHash>
 
23
#include <QDir>
 
24
#include <QDebug>
 
25
#include "filecache.h"
 
26
#include "optionstree.h"
 
27
#include "fileutil.h"
 
28
#include "applicationinfo.h"
 
29
 
 
30
FileCacheItem::FileCacheItem(FileCache *parent, const QString &itemId,
 
31
                                                         const QString &type, const QDateTime &dt,
 
32
                                                         unsigned int maxAge, unsigned int size,
 
33
                                                         const QByteArray &data)
 
34
        : QObject(parent)
 
35
        , _id(itemId)
 
36
        , _type(type)
 
37
        , _ctime(dt)
 
38
        , _maxAge(maxAge)
 
39
        , _size(size)
 
40
        , _data(data)
 
41
        , _synced(false)
 
42
{
 
43
        _hash = QCryptographicHash::hash(_id.toUtf8(),
 
44
                                                                         QCryptographicHash::Sha1).toHex();
 
45
        QString ext = FileUtil::mimeToFileExt(_type);
 
46
        _fileName = _hash + (ext.isEmpty()? "":"." + ext);
 
47
}
 
48
 
 
49
bool FileCacheItem::inMemory() const
 
50
{
 
51
        return !_data.isNull();
 
52
}
 
53
 
 
54
void FileCacheItem::sync()
 
55
{
 
56
        if (!_synced && !_data.isNull()) {
 
57
                if (_data.size()) {
 
58
                        QFile f(parentCache()->cacheDir() + "/" + _fileName);
 
59
                        if (f.open(QIODevice::WriteOnly)) {
 
60
                                f.write(_data);
 
61
                                f.close();
 
62
                                _synced = true;
 
63
                        } else {
 
64
                                qWarning("Can't open file %s for writing",
 
65
                                                 qPrintable(_fileName));
 
66
                        }
 
67
                } else {
 
68
                        _synced = true;
 
69
                }
 
70
        }
 
71
}
 
72
 
 
73
bool FileCacheItem::remove() const
 
74
{
 
75
        if (_synced && _size) {
 
76
                QFile f(parentCache()->cacheDir() + "/" + _fileName);
 
77
                if (!f.remove()) {
 
78
                        return !f.exists();
 
79
                }
 
80
        }
 
81
        return true;
 
82
}
 
83
 
 
84
void FileCacheItem::unload()
 
85
{
 
86
        if (!_synced) {
 
87
                sync();
 
88
        }
 
89
        _data = QByteArray();
 
90
}
 
91
 
 
92
bool FileCacheItem::isExpired(bool finishSession) const
 
93
{
 
94
        return _maxAge == FileCache::Session ? finishSession :
 
95
                        _maxAge == FileCache::Forever ? false :
 
96
                        _ctime.addSecs(_maxAge) < QDateTime::currentDateTime();
 
97
}
 
98
 
 
99
QByteArray FileCacheItem::data()
 
100
{
 
101
        if (_data.isNull()) {
 
102
                if (_size) {
 
103
                        QFile f(parentCache()->cacheDir() + "/" + _fileName);
 
104
                        if (f.open(QIODevice::ReadOnly)) {
 
105
                                _data = f.readAll();
 
106
                                // TODO check if filesize differs
 
107
                                f.close();
 
108
                        } else {
 
109
                                qWarning("Can't open file %s for reading",
 
110
                                                 qPrintable(_fileName));
 
111
                        }
 
112
                } else {
 
113
                        _data = QByteArray(""); // empty but not null
 
114
                }
 
115
        }
 
116
        return _data;
 
117
}
 
118
 
 
119
 
 
120
 
 
121
 
 
122
//------------------------------------------------------------------------------
 
123
// FileCache
 
124
//------------------------------------------------------------------------------
 
125
FileCache::FileCache(const QString &cacheDir, QObject *parent)
 
126
        : QObject(parent)
 
127
        , _cacheDir(cacheDir)
 
128
        , _memoryCacheSize(FileCache::DefaultMemoryCacheSize)
 
129
        , _fileCacheSize(FileCache::DefaultFileCacheSize)
 
130
        , _defaultMaxAge(Forever)
 
131
        , _syncPolicy(InstantFLush)
 
132
        , _registryChanged(false)
 
133
{
 
134
        _registry = new OptionsTree(this);
 
135
        _syncTimer = new QTimer(this);
 
136
        _syncTimer->setSingleShot(true);
 
137
        _syncTimer->setInterval(1000);
 
138
        connect(_syncTimer, SIGNAL(timeout()), SLOT(sync()));
 
139
 
 
140
        _registry->loadOptions(_cacheDir + "/cache.xml", "items",
 
141
                                                   ApplicationInfo::fileCacheNS());
 
142
 
 
143
        foreach(const QString &prefix, _registry->getChildOptionNames("", true, true)) {
 
144
                QString id = _registry->getOption(prefix + ".id").toString();
 
145
                FileCacheItem *item = new FileCacheItem(this, id,
 
146
                        _registry->getOption(prefix + ".type").toString(),
 
147
                        QDateTime::fromString(_registry->getOption(prefix + ".ctime").toString(), Qt::ISODate),
 
148
                        _registry->getOption(prefix + ".max-age").toInt(),
 
149
                        _registry->getOption(prefix + ".size").toInt()
 
150
                );
 
151
                item->setSynced(true);
 
152
                _items[id] = item;
 
153
                if (item->isExpired()) {
 
154
                        remove(item->id());
 
155
                }
 
156
        }
 
157
}
 
158
 
 
159
FileCache::~FileCache()
 
160
{
 
161
        gc();
 
162
        sync(true);
 
163
}
 
164
 
 
165
void FileCache::gc()
 
166
{
 
167
        QDir dir(_cacheDir);
 
168
        foreach(const QString &id, _items.keys()) {
 
169
                FileCacheItem *item = _items.value(id);
 
170
                // remove broken cache items
 
171
                if (item->isSynced() && item->size() && !dir.exists(item->fileName())) {
 
172
                        remove(id, false);
 
173
                        continue;
 
174
                }
 
175
                // remove expired items
 
176
                if (item->isExpired()) {
 
177
                        remove(id, false);
 
178
                }
 
179
        }
 
180
}
 
181
 
 
182
FileCacheItem *FileCache::append(const QString &id, const QString &type,
 
183
                                           const QByteArray &data, unsigned int maxAge)
 
184
{
 
185
        FileCacheItem *item = new FileCacheItem(this, id, type,
 
186
                                                                                        QDateTime::currentDateTime(),
 
187
                                                                                        maxAge, data.size(), data);
 
188
        _items[id] = item;
 
189
        _pendingSyncItems[id] = item;
 
190
        _syncTimer->start();
 
191
 
 
192
        return item;
 
193
}
 
194
 
 
195
void FileCache::remove(const QString &id, bool needSync)
 
196
{
 
197
        FileCacheItem *item = _items.value(id);
 
198
        if (item) {
 
199
                if (item->isSynced()) {
 
200
                        _registry->removeOption("h" + item->hash(), true);
 
201
                        _registryChanged = true;
 
202
                }
 
203
                item->remove();
 
204
                _items.remove(id);
 
205
                _pendingSyncItems.remove(id);
 
206
                delete item;
 
207
                if (needSync) {
 
208
                        _syncTimer->start();
 
209
                }
 
210
        }
 
211
}
 
212
 
 
213
FileCacheItem *FileCache::get(const QString &id)
 
214
{
 
215
        FileCacheItem *item = _items.value(id);
 
216
        if (item && !item->isExpired()) {
 
217
                if (!item->isExpired()) {
 
218
                        return item;
 
219
                }
 
220
                remove(id);
 
221
        }
 
222
        return 0;
 
223
}
 
224
 
 
225
QByteArray FileCache::getData(const QString &id)
 
226
{
 
227
        FileCacheItem *item = get(id);
 
228
        return item ? item->data() : QByteArray();
 
229
}
 
230
 
 
231
bool ctimeLessThan(FileCacheItem *a, FileCacheItem *b)
 
232
{
 
233
        return a->created() < b->created();
 
234
};
 
235
 
 
236
void FileCache::sync()
 
237
{
 
238
        sync(false);
 
239
}
 
240
 
 
241
void FileCache::sync(bool finishSession)
 
242
{
 
243
        QList<FileCacheItem *> loadedItems;
 
244
        QList<FileCacheItem *> syncedItems;
 
245
        unsigned int sumMemorySize = 0;
 
246
        unsigned int sumFileSize = 0;
 
247
        FileCacheItem *item;
 
248
 
 
249
        foreach(const QString &id, _items.keys()) {
 
250
                item = _items.value(id);
 
251
                if (item->isExpired(finishSession)) {
 
252
                        remove(id, false);
 
253
                        continue;
 
254
                }
 
255
                if (item->size()) {
 
256
                        if (item->inMemory()) {
 
257
                                loadedItems.append(item);
 
258
                                sumMemorySize += item->size();
 
259
                        }
 
260
                        if (item->isSynced()) {
 
261
                                sumFileSize += item->size();
 
262
                                syncedItems.append(item);
 
263
                        }
 
264
                }
 
265
        }
 
266
 
 
267
        // flush overflowed in-memory data to disk
 
268
        if (sumMemorySize > _memoryCacheSize) {
 
269
                qSort(loadedItems.begin(), loadedItems.end(), ctimeLessThan);
 
270
                while (sumMemorySize > _memoryCacheSize && loadedItems.size()) {
 
271
                        item = loadedItems.takeFirst();
 
272
                        if (!item->isSynced()) {
 
273
                                if (_pendingSyncItems.contains(item->id())) {
 
274
                                        toRegistry(item); // save item to registry if not yet
 
275
                                        _pendingSyncItems.remove(item->id());
 
276
                                }
 
277
                                syncedItems.append(item); // unload below will sync item
 
278
                                sumFileSize += item->size();
 
279
                        }
 
280
                        item->unload(); // will flush data to disk if necesary
 
281
                        sumMemorySize -= item->size();
 
282
                }
 
283
        }
 
284
 
 
285
        // register pending items and flush them if necessary
 
286
        foreach (FileCacheItem *item, _pendingSyncItems.values()) {
 
287
                toRegistry(item);
 
288
                if (_syncPolicy == InstantFLush) {
 
289
                        item->sync();
 
290
                }
 
291
                _pendingSyncItems.remove(item->id());
 
292
        }
 
293
 
 
294
        // remove overflowed disk data
 
295
        if (sumFileSize > _fileCacheSize) {
 
296
                qSort(syncedItems.begin(), syncedItems.end(), ctimeLessThan);
 
297
                while (sumFileSize > _fileCacheSize && syncedItems.size()) {
 
298
                        item = syncedItems.takeFirst();
 
299
                        remove(item->id(), false);
 
300
                }
 
301
        }
 
302
 
 
303
        if (_registryChanged) {
 
304
                _registry->saveOptions(_cacheDir + "/cache.xml", "items",
 
305
                                                           ApplicationInfo::fileCacheNS(),
 
306
                                                           ApplicationInfo::version());
 
307
                _registryChanged = false;
 
308
        }
 
309
}
 
310
 
 
311
void FileCache::toRegistry(FileCacheItem *item)
 
312
{
 
313
        QString prefix = QString("h") + item->hash();
 
314
        _registry->setOption(prefix + ".id", item->id());
 
315
        _registry->setOption(prefix + ".type", item->type());
 
316
        _registry->setOption(prefix + ".ctime", item->created().toString(Qt::ISODate));
 
317
        _registry->setOption(prefix + ".max-age", (int)item->maxAge());
 
318
        _registry->setOption(prefix + ".size", (int)item->size());
 
319
 
 
320
        _registryChanged = true;
 
321
}