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