]> git.lyx.org Git - lyx.git/blob - src/ConverterCache.cpp
16e0c7f747273eea1ddf89a673d42a037fd195a2
[lyx.git] / src / ConverterCache.cpp
1 /**
2  * \file ConverterCache.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Baruch Even
7  * \author Angus Leeming
8  * \author Georg Baum
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "ConverterCache.h"
16
17 #include "Format.h"
18 #include "Lexer.h"
19 #include "LyXRC.h"
20 #include "Mover.h"
21 #include "debug.h"
22
23 #include "support/convert.h"
24 #include "support/filetools.h"
25 #include "support/lyxlib.h"
26 #include "support/lyxtime.h"
27 #include "support/Package.h"
28
29 #include <boost/crc.hpp>
30 #include <boost/filesystem/operations.hpp>
31 #include <boost/current_function.hpp>
32
33 #include <fstream>
34 #include <iomanip>
35 #include <map>
36 #include <sstream>
37
38 using lyx::support::addName;
39
40 using std::string;
41
42 namespace fs = boost::filesystem;
43
44 namespace lyx {
45
46 using support::FileName;
47
48 namespace {
49
50 unsigned long do_crc(string const & s)
51 {
52         boost::crc_32_type crc;
53         crc = std::for_each(s.begin(), s.end(), crc);
54         return crc.checksum();
55 }
56
57
58 static FileName cache_dir;
59
60
61 class CacheItem {
62 public:
63         CacheItem() {}
64         CacheItem(FileName const & orig_from, string const & to_format,
65                   time_t t, unsigned long c)
66                 : timestamp(t), checksum(c)
67         {
68                 std::ostringstream os;
69                 os << std::setw(10) << std::setfill('0') << do_crc(orig_from.absFilename())
70                    << '-' << to_format;
71                 cache_name = FileName(addName(cache_dir.absFilename(), os.str()));
72                 LYXERR(Debug::FILES) << "Add file cache item " << orig_from
73                                      << ' ' << to_format << ' ' << cache_name
74                                      << ' ' << timestamp << ' ' << checksum
75                                      << '.' << std::endl;
76         }
77         ~CacheItem() {}
78         FileName cache_name;
79         time_t timestamp;
80         unsigned long checksum;
81 };
82
83 }
84
85
86 /** The cache contains one item per orig file and target format, so use a
87  *  nested map to find the cache item quickly by filename and format.
88  */
89 typedef std::map<string, CacheItem> FormatCacheType;
90 class FormatCache {
91 public:
92         /// Format of the source file
93         string from_format;
94         /// Cache target format -> item to quickly find the item by format
95         FormatCacheType cache;
96 };
97 typedef std::map<FileName, FormatCache> CacheType;
98
99
100 class ConverterCache::Impl {
101 public:
102         ///
103         void readIndex();
104         ///
105         void writeIndex();
106         ///
107         CacheItem * find(FileName const & from, string const & format);
108         CacheType cache;
109 };
110
111
112 void ConverterCache::Impl::readIndex()
113 {
114         time_t const now = current_time();
115         FileName const index(addName(cache_dir.absFilename(), "index"));
116         std::ifstream is(index.toFilesystemEncoding().c_str());
117         Lexer lex(0, 0);
118         lex.setStream(is);
119         while (lex.isOK()) {
120                 if (!lex.next(true))
121                         break;
122                 string const orig_from = lex.getString();
123                 if (!lex.next())
124                         break;
125                 string const to_format = lex.getString();
126                 if (!lex.next())
127                         break;
128                 time_t const timestamp =
129                         convert<unsigned long>(lex.getString());
130                 if (!lex.next())
131                         break;
132                 unsigned long const checksum =
133                         convert<unsigned long>(lex.getString());
134                 FileName const orig_from_name(orig_from);
135                 CacheItem item(orig_from_name, to_format, timestamp, checksum);
136
137                 // Don't cache files that do not exist anymore
138                 if (!fs::exists(orig_from_name.toFilesystemEncoding())) {
139                         LYXERR(Debug::FILES) << "Not caching file `"
140                                 << orig_from << "' (does not exist anymore)."
141                                 << std::endl;
142                         support::unlink(item.cache_name);
143                         continue;
144                 }
145
146                 // Don't add items that are not in the cache anymore
147                 // This can happen if two instances of LyX are running
148                 // at the same time and update the index file independantly.
149                 if (!fs::exists(item.cache_name.toFilesystemEncoding())) {
150                         LYXERR(Debug::FILES) << "Not caching file `"
151                                 << orig_from
152                                 << "' (cached copy does not exist anymore)."
153                                 << std::endl;
154                         continue;
155                 }
156
157                 // Delete the cached file if it is too old
158                 if (difftime(now, fs::last_write_time(item.cache_name.toFilesystemEncoding())) >
159                     lyxrc.converter_cache_maxage) {
160                         LYXERR(Debug::FILES) << "Not caching file `"
161                                 << orig_from << "' (too old)." << std::endl;
162                         support::unlink(item.cache_name);
163                         continue;
164                 }
165
166                 FormatCache & format_cache = cache[orig_from_name];
167                 if (format_cache.from_format.empty())
168                         format_cache.from_format =
169                                 formats.getFormatFromFile(orig_from_name);
170                 format_cache.cache[to_format] = item;
171         }
172         is.close();
173 }
174
175
176 void ConverterCache::Impl::writeIndex()
177 {
178         FileName const index(addName(cache_dir.absFilename(), "index"));
179         std::ofstream os(index.toFilesystemEncoding().c_str());
180         os.close();
181         if (!lyx::support::chmod(index, 0600))
182                 return;
183         os.open(index.toFilesystemEncoding().c_str());
184         CacheType::iterator it1 = cache.begin();
185         CacheType::iterator const end1 = cache.end();
186         for (; it1 != end1; ++it1) {
187                 FormatCacheType const & format_cache = it1->second.cache;
188                 FormatCacheType::const_iterator it2 = format_cache.begin();
189                 FormatCacheType::const_iterator const end2 = format_cache.end();
190                 for (; it2 != end2; ++it2)
191                         os << Lexer::quoteString(it1->first.absFilename())
192                            << ' ' << it2->first << ' '
193                            << it2->second.timestamp << ' '
194                            << it2->second.checksum << '\n';
195         }
196         os.close();
197 }
198
199
200 CacheItem * ConverterCache::Impl::find(FileName const & from,
201                 string const & format)
202 {
203         if (!lyxrc.use_converter_cache)
204                 return 0;
205         CacheType::iterator const it1 = cache.find(from);
206         if (it1 == cache.end())
207                 return 0;
208         FormatCacheType & format_cache = it1->second.cache;
209         FormatCacheType::iterator const it2 = format_cache.find(format);
210         if (it2 == format_cache.end())
211                 return 0;
212         return &(it2->second);
213 }
214
215
216 ConverterCache & ConverterCache::get()
217 {
218         // Now return the cache
219         static ConverterCache singleton;
220         return singleton;
221 }
222
223
224 void ConverterCache::init()
225 {
226         if (!lyxrc.use_converter_cache)
227                 return;
228         // We do this here and not in the constructor because package() gets
229         // initialized after all static variables.
230         cache_dir = FileName(addName(support::package().user_support().absFilename(), "cache"));
231         if (!fs::exists(cache_dir.toFilesystemEncoding()))
232                 if (support::mkdir(cache_dir, 0700) != 0) {
233                         lyxerr << "Could not create cache directory `"
234                                << cache_dir << "'." << std::endl;
235                         exit(EXIT_FAILURE);
236                 }
237         get().pimpl_->readIndex();
238 }
239
240
241 ConverterCache::ConverterCache()
242         : pimpl_(new Impl)
243 {}
244
245
246 ConverterCache::~ConverterCache()
247 {
248         if (!lyxrc.use_converter_cache)
249                 return;
250         pimpl_->writeIndex();
251 }
252
253
254 void ConverterCache::add(FileName const & orig_from, string const & to_format,
255                 FileName const & converted_file) const
256 {
257         if (!lyxrc.use_converter_cache || orig_from.empty() ||
258             converted_file.empty())
259                 return;
260         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
261                              << ' ' << to_format << ' ' << converted_file
262                              << std::endl;
263
264         // Is the file in the cache already?
265         CacheItem * item = pimpl_->find(orig_from, to_format);
266
267         time_t const timestamp = fs::last_write_time(orig_from.toFilesystemEncoding());
268         Mover const & mover = getMover(to_format);
269         if (item) {
270                 LYXERR(Debug::FILES) << "ConverterCache::add(" << orig_from << "):\n"
271                                         "The file is already in the cache."
272                                      << std::endl;
273                 // First test for timestamp
274                 if (timestamp == item->timestamp) {
275                         LYXERR(Debug::FILES) << "Same timestamp."
276                                              << std::endl;
277                         return;
278                 } else {
279                         // Maybe the contents is still the same?
280                         item->timestamp = timestamp;
281                         unsigned long const checksum = support::sum(orig_from);
282                         if (checksum == item->checksum) {
283                                 LYXERR(Debug::FILES) << "Same checksum."
284                                                      << std::endl;
285                                 return;
286                         }
287                         item->checksum = checksum;
288                 }
289                 if (!mover.copy(converted_file, item->cache_name, 0600))
290                         LYXERR(Debug::FILES) << "ConverterCache::add("
291                                              << orig_from << "):\n"
292                                                 "Could not copy file."
293                                              << std::endl;
294         } else {
295                 CacheItem new_item(orig_from, to_format, timestamp,
296                                 support::sum(orig_from));
297                 if (mover.copy(converted_file, new_item.cache_name, 0600)) {
298                         FormatCache & format_cache = pimpl_->cache[orig_from];
299                         if (format_cache.from_format.empty())
300                                 format_cache.from_format =
301                                         formats.getFormatFromFile(orig_from);
302                         format_cache.cache[to_format] = new_item;
303                 } else
304                         LYXERR(Debug::FILES) << "ConverterCache::add("
305                                              << orig_from << "):\n"
306                                                 "Could not copy file."
307                                      << std::endl;
308         }
309 }
310
311
312 void ConverterCache::remove(FileName const & orig_from,
313                 string const & to_format) const
314 {
315         if (!lyxrc.use_converter_cache || orig_from.empty())
316                 return;
317         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
318                              << ' ' << to_format << std::endl;
319
320         CacheType::iterator const it1 = pimpl_->cache.find(orig_from);
321         if (it1 == pimpl_->cache.end())
322                 return;
323         FormatCacheType & format_cache = it1->second.cache;
324         FormatCacheType::iterator const it2 = format_cache.find(to_format);
325         if (it2 == format_cache.end())
326                 return;
327
328         format_cache.erase(it2);
329         if (format_cache.empty())
330                 pimpl_->cache.erase(it1);
331 }
332
333
334 void ConverterCache::remove_all(string const & from_format,
335                 string const & to_format) const
336 {
337         if (!lyxrc.use_converter_cache)
338                 return;
339         CacheType::iterator it1 = pimpl_->cache.begin();
340         while (it1 != pimpl_->cache.end()) {
341                 if (it1->second.from_format != from_format) {
342                         ++it1;
343                         continue;
344                 }
345                 FormatCacheType & format_cache = it1->second.cache;
346                 FormatCacheType::iterator it2 = format_cache.begin();
347                 while (it2 != format_cache.end()) {
348                         if (it2->first == to_format) {
349                                 LYXERR(Debug::FILES)
350                                         << "Removing file cache item "
351                                         << it1->first
352                                         << ' ' << to_format << std::endl;
353                                 support::unlink(it2->second.cache_name);
354                                 format_cache.erase(it2);
355                                 // Have to start over again since items in a
356                                 // map are not ordered
357                                 it2 = format_cache.begin();
358                         } else
359                                 ++it2;
360                 }
361                 if (format_cache.empty()) {
362                         pimpl_->cache.erase(it1);
363                         // Have to start over again since items in a map are
364                         // not ordered
365                         it1 = pimpl_->cache.begin();
366                 } else
367                         ++it1;
368         }
369         pimpl_->writeIndex();
370 }
371
372
373 bool ConverterCache::inCache(FileName const & orig_from,
374                 string const & to_format) const
375 {
376         if (!lyxrc.use_converter_cache || orig_from.empty())
377                 return false;
378         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
379                              << ' ' << to_format << std::endl;
380
381         CacheItem * const item = pimpl_->find(orig_from, to_format);
382         if (!item) {
383                 LYXERR(Debug::FILES) << "not in cache." << std::endl;
384                 return false;
385         }
386         time_t const timestamp = fs::last_write_time(orig_from.toFilesystemEncoding());
387         if (item->timestamp == timestamp) {
388                 LYXERR(Debug::FILES) << "identical timestamp." << std::endl;
389                 return true;
390         }
391         if (item->checksum == support::sum(orig_from)) {
392                 item->timestamp = timestamp;
393                 LYXERR(Debug::FILES) << "identical checksum." << std::endl;
394                 return true;
395         }
396         LYXERR(Debug::FILES) << "in cache, but too old." << std::endl;
397         return false;
398 }
399
400
401 FileName const & ConverterCache::cacheName(FileName const & orig_from,
402                 string const & to_format) const
403 {
404         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
405                              << ' ' << to_format << std::endl;
406
407         CacheItem * const item = pimpl_->find(orig_from, to_format);
408         BOOST_ASSERT(item);
409         return item->cache_name;
410 }
411
412
413 bool ConverterCache::copy(FileName const & orig_from, string const & to_format,
414                 FileName const & dest) const
415 {
416         if (!lyxrc.use_converter_cache || orig_from.empty() || dest.empty())
417                 return false;
418         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
419                              << ' ' << to_format << ' ' << dest << std::endl;
420
421         CacheItem * const item = pimpl_->find(orig_from, to_format);
422         BOOST_ASSERT(item);
423         Mover const & mover = getMover(to_format);
424         return mover.copy(item->cache_name, dest);
425 }
426
427 } // namespace lyx