2
* filecache.cpp - File storage with age and size control
3
* Copyright (C) 2010 Rion
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.
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.
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
22
#include <QCryptographicHash>
25
#include "filecache.h"
26
#include "optionstree.h"
28
#include "applicationinfo.h"
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)
43
_hash = QCryptographicHash::hash(_id.toUtf8(),
44
QCryptographicHash::Sha1).toHex();
45
QString ext = FileUtil::mimeToFileExt(_type);
46
_fileName = _hash + (ext.isEmpty()? "":"." + ext);
49
bool FileCacheItem::inMemory() const
51
return !_data.isNull();
54
void FileCacheItem::sync()
56
if (!_synced && !_data.isNull()) {
58
QFile f(parentCache()->cacheDir() + "/" + _fileName);
59
if (f.open(QIODevice::WriteOnly)) {
64
qWarning("Can't open file %s for writing",
65
qPrintable(_fileName));
73
bool FileCacheItem::remove() const
75
if (_synced && _size) {
76
QFile f(parentCache()->cacheDir() + "/" + _fileName);
84
void FileCacheItem::unload()
92
bool FileCacheItem::isExpired(bool finishSession) const
94
return _maxAge == FileCache::Session ? finishSession :
95
_maxAge == FileCache::Forever ? false :
96
_ctime.addSecs(_maxAge) < QDateTime::currentDateTime();
99
QByteArray FileCacheItem::data()
101
if (_data.isNull()) {
103
QFile f(parentCache()->cacheDir() + "/" + _fileName);
104
if (f.open(QIODevice::ReadOnly)) {
106
// TODO check if filesize differs
109
qWarning("Can't open file %s for reading",
110
qPrintable(_fileName));
113
_data = QByteArray(""); // empty but not null
122
//------------------------------------------------------------------------------
124
//------------------------------------------------------------------------------
125
FileCache::FileCache(const QString &cacheDir, QObject *parent)
127
, _cacheDir(cacheDir)
128
, _memoryCacheSize(FileCache::DefaultMemoryCacheSize)
129
, _fileCacheSize(FileCache::DefaultFileCacheSize)
130
, _defaultMaxAge(Forever)
131
, _syncPolicy(InstantFLush)
132
, _registryChanged(false)
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()));
140
_registry->loadOptions(_cacheDir + "/cache.xml", "items",
141
ApplicationInfo::fileCacheNS());
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()
151
item->setSynced(true);
153
if (item->isExpired()) {
159
FileCache::~FileCache()
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())) {
175
// remove expired items
176
if (item->isExpired()) {
182
FileCacheItem *FileCache::append(const QString &id, const QString &type,
183
const QByteArray &data, unsigned int maxAge)
185
FileCacheItem *item = new FileCacheItem(this, id, type,
186
QDateTime::currentDateTime(),
187
maxAge, data.size(), data);
189
_pendingSyncItems[id] = item;
195
void FileCache::remove(const QString &id, bool needSync)
197
FileCacheItem *item = _items.value(id);
199
if (item->isSynced()) {
200
_registry->removeOption("h" + item->hash(), true);
201
_registryChanged = true;
205
_pendingSyncItems.remove(id);
213
FileCacheItem *FileCache::get(const QString &id)
215
FileCacheItem *item = _items.value(id);
216
if (item && !item->isExpired()) {
217
if (!item->isExpired()) {
225
QByteArray FileCache::getData(const QString &id)
227
FileCacheItem *item = get(id);
228
return item ? item->data() : QByteArray();
231
bool ctimeLessThan(FileCacheItem *a, FileCacheItem *b)
233
return a->created() < b->created();
236
void FileCache::sync()
241
void FileCache::sync(bool finishSession)
243
QList<FileCacheItem *> loadedItems;
244
QList<FileCacheItem *> syncedItems;
245
unsigned int sumMemorySize = 0;
246
unsigned int sumFileSize = 0;
249
foreach(const QString &id, _items.keys()) {
250
item = _items.value(id);
251
if (item->isExpired(finishSession)) {
256
if (item->inMemory()) {
257
loadedItems.append(item);
258
sumMemorySize += item->size();
260
if (item->isSynced()) {
261
sumFileSize += item->size();
262
syncedItems.append(item);
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());
277
syncedItems.append(item); // unload below will sync item
278
sumFileSize += item->size();
280
item->unload(); // will flush data to disk if necesary
281
sumMemorySize -= item->size();
285
// register pending items and flush them if necessary
286
foreach (FileCacheItem *item, _pendingSyncItems.values()) {
288
if (_syncPolicy == InstantFLush) {
291
_pendingSyncItems.remove(item->id());
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);
303
if (_registryChanged) {
304
_registry->saveOptions(_cacheDir + "/cache.xml", "items",
305
ApplicationInfo::fileCacheNS(),
306
ApplicationInfo::version());
307
_registryChanged = false;
311
void FileCache::toRegistry(FileCacheItem *item)
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());
320
_registryChanged = true;