]> git.sur5r.net Git - minitube/blob - src/http/src/localcache.cpp
Update watch file to use releases instead of tags
[minitube] / src / http / src / localcache.cpp
1 #include "localcache.h"
2
3 LocalCache *LocalCache::instance(const char *name) {
4     static QMap<QByteArray, LocalCache *> instances;
5     auto i = instances.constFind(QByteArray::fromRawData(name, strlen(name)));
6     if (i != instances.constEnd()) return i.value();
7     LocalCache *instance = new LocalCache(name);
8     instances.insert(instance->getName(), instance);
9     return instance;
10 }
11
12 LocalCache::LocalCache(const QByteArray &name)
13     : name(name), maxSeconds(86400 * 30), maxSize(1024 * 1024 * 100), size(0), expiring(false),
14       insertCount(0) {
15     directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') +
16                 QLatin1String(name) + QLatin1Char('/');
17 #ifndef QT_NO_DEBUG_OUTPUT
18     hits = 0;
19     misses = 0;
20 #endif
21 }
22
23 LocalCache::~LocalCache() {
24 #ifndef QT_NO_DEBUG_OUTPUT
25     debugStats();
26 #endif
27 }
28
29 QByteArray LocalCache::hash(const QByteArray &s) {
30     QCryptographicHash hash(QCryptographicHash::Sha1);
31     hash.addData(s);
32     const QByteArray h = QByteArray::number(*(qlonglong *)hash.result().constData(), 36);
33     static const char sep('/');
34     QByteArray p;
35     p.reserve(h.length() + 2);
36     p.append(h.at(0));
37     p.append(sep);
38     p.append(h.at(1));
39     p.append(sep);
40     p.append(h.constData() + 2, strlen(h.constData()) - 2); // p.append(h.mid(2));
41     return p;
42 }
43
44 bool LocalCache::isCached(const QString &path) {
45     bool cached = (QFile::exists(path) &&
46                    (maxSeconds == 0 ||
47                     QDateTime::currentDateTime().toTime_t() - QFileInfo(path).created().toTime_t() <
48                             maxSeconds));
49 #ifndef QT_NO_DEBUG_OUTPUT
50     if (!cached) misses++;
51 #endif
52     return cached;
53 }
54
55 QByteArray LocalCache::value(const QByteArray &key) {
56     const QString path = cachePath(key);
57     if (!isCached(path)) return QByteArray();
58
59     QFile file(path);
60     if (!file.open(QIODevice::ReadOnly)) {
61         qWarning() << __PRETTY_FUNCTION__ << file.fileName() << file.errorString();
62 #ifndef QT_NO_DEBUG_OUTPUT
63         misses++;
64 #endif
65         return QByteArray();
66     }
67 #ifndef QT_NO_DEBUG_OUTPUT
68     hits++;
69 #endif
70     return file.readAll();
71 }
72
73 void LocalCache::insert(const QByteArray &key, const QByteArray &value) {
74     const QueueItem item = {key, value};
75     insertQueue.append(item);
76     QTimer::singleShot(0, [this]() {
77         if (insertQueue.isEmpty()) return;
78         for (const auto &item : insertQueue) {
79             const QString path = cachePath(item.key);
80             const QString parentDir = path.left(path.lastIndexOf('/'));
81             if (!QFile::exists(parentDir)) {
82                 QDir().mkpath(parentDir);
83             }
84             QFile file(path);
85             if (!file.open(QIODevice::WriteOnly)) {
86                 qWarning() << "Cannot create" << path;
87                 continue;
88             }
89             file.write(item.value);
90             file.close();
91             if (size > 0) size += item.value.size();
92         }
93         insertQueue.clear();
94
95         // expire cache every n inserts
96         if (maxSize > 0 && ++insertCount % 100 == 0) {
97             if (size == 0 || size > maxSize) size = expire();
98         }
99     });
100 }
101
102 bool LocalCache::clear() {
103 #ifndef QT_NO_DEBUG_OUTPUT
104     hits = 0;
105     misses = 0;
106 #endif
107     size = 0;
108     insertCount = 0;
109     return QDir(directory).removeRecursively();
110 }
111
112 QString LocalCache::cachePath(const QByteArray &key) const {
113     return directory + QLatin1String(key.constData());
114 }
115
116 qint64 LocalCache::expire() {
117     if (expiring) return size;
118     expiring = true;
119
120     QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot;
121     QDirIterator it(directory, filters, QDirIterator::Subdirectories);
122
123     QMultiMap<QDateTime, QString> cacheItems;
124     qint64 totalSize = 0;
125     while (it.hasNext()) {
126         QString path = it.next();
127         QFileInfo info = it.fileInfo();
128         cacheItems.insert(info.created(), path);
129         totalSize += info.size();
130         qApp->processEvents();
131     }
132
133     int removedFiles = 0;
134     qint64 goal = (maxSize * 9) / 10;
135     auto i = cacheItems.constBegin();
136     while (i != cacheItems.constEnd()) {
137         if (totalSize < goal) break;
138         QString name = i.value();
139         QFile file(name);
140         qint64 size = file.size();
141         file.remove();
142         totalSize -= size;
143         ++removedFiles;
144         ++i;
145         qApp->processEvents();
146     }
147 #ifndef QT_NO_DEBUG_OUTPUT
148     debugStats();
149     if (removedFiles > 0) {
150         qDebug() << "Removed:" << removedFiles << "Kept:" << cacheItems.count() - removedFiles
151                  << "New Size:" << totalSize;
152     }
153 #endif
154
155     expiring = false;
156
157     return totalSize;
158 }
159
160 #ifndef QT_NO_DEBUG_OUTPUT
161 void LocalCache::debugStats() {
162     int total = hits + misses;
163     if (total > 0) {
164         qDebug() << "Cache:" << name << '\n'
165                  << "Inserts:" << insertCount << '\n'
166                  << "Requests:" << total << '\n'
167                  << "Hits:" << hits << (hits * 100) / total << "%\n"
168                  << "Misses:" << misses << (misses * 100) / total << "%";
169     }
170 }
171 #endif