1 /*
2 * This file is part of RawTherapee.
3 *
4 * Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5 *
6 * RawTherapee is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * RawTherapee is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "cachemanager.h"
20
21 #include <memory>
22 #include <iostream>
23
24 #include <glib/gstdio.h>
25 #include <giomm.h>
26
27 #ifdef WIN32
28 #include <windows.h>
29 #endif
30
31 #include "guiutils.h"
32 #include "options.h"
33 #include "procparamchangers.h"
34 #include "thumbnail.h"
35
36 namespace
37 {
38
39 constexpr int cacheDirMode = 0777;
40 constexpr const char* cacheDirs[] = { "profiles", "images", "aehistograms", "embprofiles", "data" };
41
42 }
43
CacheManager()44 CacheManager::CacheManager():
45 pl_(nullptr)
46 {
47 }
48
49
getInstance()50 CacheManager* CacheManager::getInstance ()
51 {
52 static CacheManager instance;
53 return &instance;
54 }
55
init()56 void CacheManager::init ()
57 {
58 MyMutex::MyLock lock (mutex);
59
60 openEntries.clear ();
61 baseDir = options.cacheBaseDir;
62
63 auto error = g_mkdir_with_parents (baseDir.c_str(), cacheDirMode);
64
65 for (const auto& cacheDir : cacheDirs) {
66 if (strncmp(cacheDir, "aehistograms", 12)) { // don't create aehistograms folder.
67 error |= g_mkdir_with_parents (Glib::build_filename (baseDir, cacheDir).c_str(), cacheDirMode);
68 }
69 }
70
71 if (error != 0 && options.rtSettings.verbose) {
72 std::cerr << "Failed to create all cache directories: " << g_strerror(errno) << std::endl;
73 }
74 }
75
76
getEntry(const Glib::ustring & fname)77 Thumbnail* CacheManager::getEntry (const Glib::ustring& fname)
78 {
79 std::unique_ptr<Thumbnail> thumbnail;
80
81 // take manager lock and search for entry,
82 // if found return it,
83 // else release lock and create it
84 {
85 MyMutex::MyLock lock (mutex);
86
87 // if it is open, return it
88 const auto iterator = openEntries.find (fname);
89 if (iterator != openEntries.end ()) {
90
91 auto cachedThumbnail = iterator->second;
92
93 cachedThumbnail->increaseRef ();
94 return cachedThumbnail;
95 }
96 }
97
98 // build path name
99 const auto md5 = getMD5 (fname);
100
101 if (md5.empty ()) {
102 return nullptr;
103 }
104
105 const auto cacheName = getCacheFileName ("data", fname, ".txt", md5);
106
107 // let's see if we have it in the cache
108 {
109 CacheImageData imageData;
110
111 const auto error = imageData.load (cacheName);
112 if (error == 0 && imageData.supported) {
113
114 thumbnail.reset (new Thumbnail(this, fname, &imageData));
115 if (!thumbnail->isSupported ()) {
116 thumbnail.reset ();
117 }
118 }
119 }
120
121 // if not, create a new one
122 if (!thumbnail) {
123
124 thumbnail.reset (new Thumbnail (this, fname, md5));
125 if (!thumbnail->isSupported ()) {
126 thumbnail.reset ();
127 }
128 }
129
130 // retake the lock and see if it was added while we we're unlocked, if it
131 // was use it over our version. if not added we create the cache entry
132 if (thumbnail) {
133 MyMutex::MyLock lock (mutex);
134
135 const auto iterator = openEntries.find (fname);
136 if (iterator != openEntries.end ()) {
137
138 auto cachedThumbnail = iterator->second;
139
140 cachedThumbnail->increaseRef ();
141 return cachedThumbnail;
142 }
143
144 // it wasn't, create a new entry
145 openEntries.emplace (fname, thumbnail.get ());
146 }
147
148 return thumbnail.release ();
149 }
150
151
deleteEntry(const Glib::ustring & fname)152 void CacheManager::deleteEntry (const Glib::ustring& fname)
153 {
154 MyMutex::MyLock lock (mutex);
155
156 // check if it is opened
157 auto iterator = openEntries.find (fname);
158 if (iterator == openEntries.end ()) {
159 deleteFiles (fname, getMD5 (fname), true, true);
160 return;
161 }
162
163 auto thumbnail = iterator->second;
164
165 // decrease reference count;
166 // this will call back into CacheManager,
167 // so we release the lock for it
168 {
169 lock.release ();
170 thumbnail->decreaseRef ();
171 lock.acquire ();
172 }
173
174 // check again if in the editor,
175 // the thumbnail still exists,
176 // if not, delete it
177 if (openEntries.count (fname) == 0) {
178 deleteFiles (fname, thumbnail->getMD5 (), true, true);
179 }
180 }
181
clearFromCache(const Glib::ustring & fname,bool purge) const182 void CacheManager::clearFromCache (const Glib::ustring& fname, bool purge) const
183 {
184 deleteFiles (fname, getMD5 (fname), true, purge);
185 }
186
renameEntry(const std::string & oldfilename,const std::string & oldmd5,const std::string & newfilename)187 void CacheManager::renameEntry (const std::string& oldfilename, const std::string& oldmd5, const std::string& newfilename)
188 {
189 MyMutex::MyLock lock (mutex);
190
191 const auto newmd5 = getMD5 (newfilename);
192
193 auto error = g_rename (getCacheFileName ("profiles", oldfilename, paramFileExtension, oldmd5).c_str (), getCacheFileName ("profiles", newfilename, paramFileExtension, newmd5).c_str ());
194 error |= g_rename (getCacheFileName ("images", oldfilename, ".rtti", oldmd5).c_str (), getCacheFileName ("images", newfilename, ".rtti", newmd5).c_str ());
195 error |= g_rename (getCacheFileName ("aehistograms", oldfilename, "", oldmd5).c_str (), getCacheFileName ("aehistograms", newfilename, "", newmd5).c_str ());
196 error |= g_rename (getCacheFileName ("embprofiles", oldfilename, ".icc", oldmd5).c_str (), getCacheFileName ("embprofiles", newfilename, ".icc", newmd5).c_str ());
197 error |= g_rename (getCacheFileName ("data", oldfilename, ".txt", oldmd5).c_str (), getCacheFileName ("data", newfilename, ".txt", newmd5).c_str ());
198
199 if (error != 0 && options.rtSettings.verbose) {
200 std::cerr << "Failed to rename all files for cache entry '" << oldfilename << "': " << g_strerror(errno) << std::endl;
201 }
202
203 // check if it is opened
204 // if it is open, update md5
205 const auto iterator = openEntries.find (oldfilename);
206 if (iterator == openEntries.end ()) {
207 return;
208 }
209
210 auto thumbnail = iterator->second;
211 openEntries.erase (iterator);
212 openEntries.emplace (newfilename, thumbnail);
213
214 thumbnail->setFileName (newfilename);
215 thumbnail->updateCache ();
216 thumbnail->saveThumbnail ();
217 }
218
closeThumbnail(Thumbnail * thumbnail)219 void CacheManager::closeThumbnail (Thumbnail* thumbnail)
220 {
221 MyMutex::MyLock lock (mutex);
222
223 openEntries.erase (thumbnail->getFileName ());
224 delete thumbnail;
225 }
226
closeCache() const227 void CacheManager::closeCache () const
228 {
229 MyMutex::MyLock lock (mutex);
230
231 applyCacheSizeLimitation ();
232 }
233
clearAll() const234 void CacheManager::clearAll () const
235 {
236 MyMutex::MyLock lock (mutex);
237
238 for (const auto& cacheDir : cacheDirs) {
239 deleteDir (cacheDir);
240 }
241 }
242
clearImages() const243 void CacheManager::clearImages () const
244 {
245 MyMutex::MyLock lock (mutex);
246
247 deleteDir ("data");
248 deleteDir ("images");
249 deleteDir ("aehistograms");
250 deleteDir ("embprofiles");
251 }
252
clearProfiles() const253 void CacheManager::clearProfiles () const
254 {
255 MyMutex::MyLock lock (mutex);
256
257 deleteDir ("profiles");
258 }
259
deleteDir(const Glib::ustring & dirName) const260 void CacheManager::deleteDir (const Glib::ustring& dirName) const
261 {
262 try {
263
264 Glib::Dir dir (Glib::build_filename (baseDir, dirName));
265
266 auto error = 0;
267 for (auto entry = dir.begin (); entry != dir.end (); ++entry) {
268 error |= g_remove (Glib::build_filename (baseDir, dirName, *entry).c_str ());
269 }
270
271 if (error != 0 && options.rtSettings.verbose) {
272 std::cerr << "Failed to delete all entries in cache directory '" << dirName << "': " << g_strerror(errno) << std::endl;
273 }
274
275 } catch (Glib::Error&) {}
276 }
277
deleteFiles(const Glib::ustring & fname,const std::string & md5,bool purgeData,bool purgeProfile) const278 void CacheManager::deleteFiles (const Glib::ustring& fname, const std::string& md5, bool purgeData, bool purgeProfile) const
279 {
280 if (md5.empty ()) {
281 return;
282 }
283
284 auto error = g_remove (getCacheFileName ("images", fname, ".rtti", md5).c_str ());
285 error |= g_remove (getCacheFileName ("aehistograms", fname, "", md5).c_str ());
286 error |= g_remove (getCacheFileName ("embprofiles", fname, ".icc", md5).c_str ());
287
288 if (purgeData) {
289 error |= g_remove (getCacheFileName ("data", fname, ".txt", md5).c_str ());
290 }
291
292 if (purgeProfile) {
293 error |= g_remove (getCacheFileName ("profiles", fname, paramFileExtension, md5).c_str ());
294 }
295
296 if (error != 0 && options.rtSettings.verbose) {
297 std::cerr << "Failed to delete all files for cache entry '" << fname << "': " << g_strerror(errno) << std::endl;
298 }
299 }
300
getMD5(const Glib::ustring & fname)301 std::string CacheManager::getMD5 (const Glib::ustring& fname)
302 {
303
304 #ifdef WIN32
305
306 std::unique_ptr<wchar_t, GFreeFunc> wfname(reinterpret_cast<wchar_t*>(g_utf8_to_utf16 (fname.c_str (), -1, NULL, NULL, NULL)), g_free);
307
308 WIN32_FILE_ATTRIBUTE_DATA fileAttr;
309 if (GetFileAttributesExW(wfname.get(), GetFileExInfoStandard, &fileAttr)) {
310 // We use name, size and creation time to identify a file.
311 const auto identifier = Glib::ustring::compose("%1-%2-%3-%4", fileAttr.nFileSizeLow, fileAttr.ftCreationTime.dwHighDateTime, fileAttr.ftCreationTime.dwLowDateTime, fname);
312 return Glib::Checksum::compute_checksum(Glib::Checksum::CHECKSUM_MD5, identifier);
313 }
314
315 #else
316
317 const auto file = Gio::File::create_for_path(fname);
318 if (file) {
319
320 try
321 {
322 const auto info = file->query_info("standard::*");
323 if (info) {
324 // We only use name and size to identify a file.
325 const auto identifier = Glib::ustring::compose("%1%2", fname, info->get_size());
326 return Glib::Checksum::compute_checksum(Glib::Checksum::CHECKSUM_MD5, identifier);
327 }
328
329 } catch(Gio::Error&) {}
330 }
331
332 #endif
333
334 return {};
335 }
336
getCacheFileName(const Glib::ustring & subDir,const Glib::ustring & fname,const Glib::ustring & fext,const Glib::ustring & md5) const337 Glib::ustring CacheManager::getCacheFileName (const Glib::ustring& subDir,
338 const Glib::ustring& fname,
339 const Glib::ustring& fext,
340 const Glib::ustring& md5) const
341 {
342 const auto dirName = Glib::build_filename (baseDir, subDir);
343 const auto baseName = Glib::path_get_basename (fname) + "." + md5;
344 return Glib::build_filename (dirName, baseName + fext);
345 }
346
applyCacheSizeLimitation() const347 void CacheManager::applyCacheSizeLimitation () const
348 {
349 // first count files without fetching file name and timestamp.
350 std::size_t numFiles = 0;
351 try {
352
353 const auto dirName = Glib::build_filename (baseDir, "data");
354 const auto dir = Gio::File::create_for_path (dirName);
355
356 auto enumerator = dir->enumerate_children ("");
357
358 while (numFiles <= options.maxCacheEntries && enumerator->next_file ()) {
359 ++numFiles;
360 }
361
362 } catch (Glib::Exception&) {}
363
364 if (numFiles <= options.maxCacheEntries) {
365 return;
366 }
367
368 using FNameMTime = std::pair<Glib::ustring, Glib::TimeVal>;
369 std::vector<FNameMTime> files;
370
371 try {
372
373 const auto dirName = Glib::build_filename (baseDir, "data");
374 const auto dir = Gio::File::create_for_path (dirName);
375
376 auto enumerator = dir->enumerate_children ("standard::name,time::modified");
377
378 while (auto file = enumerator->next_file ()) {
379 files.emplace_back (file->get_name (), file->modification_time ());
380 }
381
382 } catch (Glib::Exception&) {}
383
384 if (files.size () <= options.maxCacheEntries) {
385 return;
386 }
387
388 std::sort (files.begin (), files.end (), [] (const FNameMTime& lhs, const FNameMTime& rhs)
389 {
390 return lhs.second < rhs.second;
391 });
392
393 auto cacheEntries = files.size ();
394
395 for (auto entry = files.begin (); cacheEntries-- > options.maxCacheEntries; ++entry) {
396
397 const auto& name = entry->first;
398
399 constexpr auto md5_size = 32;
400 const auto name_size = name.size();
401
402 if (name_size < md5_size + 5) {
403 continue;
404 }
405
406 const auto fname = name.substr (0, name_size - md5_size - 5);
407 const auto md5 = name.substr (name_size - md5_size - 4, md5_size);
408
409 deleteFiles (fname, md5, true, false);
410 }
411 }
412
413
getImageData(const Glib::ustring & fname,CacheImageData & out)414 bool CacheManager::getImageData(const Glib::ustring &fname, CacheImageData &out)
415 {
416 const auto md5 = getMD5(fname);
417
418 if (!md5.empty()) {
419 const auto cacheName = getCacheFileName("data", fname, ".txt", md5);
420
421 const auto error = out.load(cacheName);
422 if (error != 0) {
423 return false;
424 }
425 } else {
426 return false;
427 }
428
429 return true;
430 }
431