]> git.lyx.org Git - lyx.git/blob - src/ConverterCache.cpp
a58bfb2bce2bd2684f697232b88f5f503472dc57
[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 (!orig_from_name.exists()) {
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 (!item.cache_name.exists()) {
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, item.cache_name.lastModified())
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 /////////////////////////////////////////////////////////////////////
217 //
218 // ConverterCache
219 //
220 /////////////////////////////////////////////////////////////////////
221
222 ConverterCache::ConverterCache()
223         : pimpl_(new Impl)
224 {}
225
226
227 ConverterCache::~ConverterCache()
228 {
229         if (!lyxrc.use_converter_cache)
230                 return;
231         pimpl_->writeIndex();
232         delete pimpl_;
233 }
234
235
236 ConverterCache & ConverterCache::get()
237 {
238         // Now return the cache
239         static ConverterCache singleton;
240         return singleton;
241 }
242
243
244 void ConverterCache::init()
245 {
246         if (!lyxrc.use_converter_cache)
247                 return;
248         // We do this here and not in the constructor because package() gets
249         // initialized after all static variables.
250         cache_dir = FileName(addName(support::package().user_support().absFilename(), "cache"));
251         if (!cache_dir.exists())
252                 if (support::mkdir(cache_dir, 0700) != 0) {
253                         lyxerr << "Could not create cache directory `"
254                                << cache_dir << "'." << std::endl;
255                         exit(EXIT_FAILURE);
256                 }
257         get().pimpl_->readIndex();
258 }
259
260
261 void ConverterCache::add(FileName const & orig_from, string const & to_format,
262                 FileName const & converted_file) const
263 {
264         if (!lyxrc.use_converter_cache || orig_from.empty() ||
265             converted_file.empty())
266                 return;
267         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
268                              << ' ' << to_format << ' ' << converted_file
269                              << std::endl;
270
271         // FIXME: Should not hardcode this (see bug 3819 for details)
272         if (to_format == "pstex") {
273                 FileName const converted_eps(support::changeExtension(converted_file.absFilename(), "eps"));
274                 add(orig_from, "eps", converted_eps);
275         } else if (to_format == "pdftex") {
276                 FileName const converted_pdf(support::changeExtension(converted_file.absFilename(), "pdf"));
277                 add(orig_from, "pdf", converted_pdf);
278         }
279
280         // Is the file in the cache already?
281         CacheItem * item = pimpl_->find(orig_from, to_format);
282
283         time_t const timestamp = orig_from.lastModified();
284         Mover const & mover = getMover(to_format);
285         if (item) {
286                 LYXERR(Debug::FILES) << "ConverterCache::add(" << orig_from << "):\n"
287                                         "The file is already in the cache."
288                                      << std::endl;
289                 // First test for timestamp
290                 if (timestamp == item->timestamp) {
291                         LYXERR(Debug::FILES) << "Same timestamp."
292                                              << std::endl;
293                         return;
294                 } else {
295                         // Maybe the contents is still the same?
296                         item->timestamp = timestamp;
297                         unsigned long const checksum = support::sum(orig_from);
298                         if (checksum == item->checksum) {
299                                 LYXERR(Debug::FILES) << "Same checksum."
300                                                      << std::endl;
301                                 return;
302                         }
303                         item->checksum = checksum;
304                 }
305                 if (!mover.copy(converted_file, item->cache_name,
306                                 support::onlyFilename(item->cache_name.absFilename()), 0600)) {
307                         LYXERR(Debug::FILES) << "ConverterCache::add("
308                                              << orig_from << "):\n"
309                                                 "Could not copy file."
310                                              << std::endl;
311                 }
312         } else {
313                 CacheItem new_item(orig_from, to_format, timestamp,
314                                 support::sum(orig_from));
315                 if (mover.copy(converted_file, new_item.cache_name,
316                                support::onlyFilename(new_item.cache_name.absFilename()), 0600)) {
317                         FormatCache & format_cache = pimpl_->cache[orig_from];
318                         if (format_cache.from_format.empty())
319                                 format_cache.from_format =
320                                         formats.getFormatFromFile(orig_from);
321                         format_cache.cache[to_format] = new_item;
322                 } else
323                         LYXERR(Debug::FILES) << "ConverterCache::add("
324                                              << orig_from << "):\n"
325                                                 "Could not copy file."
326                                      << std::endl;
327         }
328 }
329
330
331 void ConverterCache::remove(FileName const & orig_from,
332                 string const & to_format) const
333 {
334         if (!lyxrc.use_converter_cache || orig_from.empty())
335                 return;
336         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
337                              << ' ' << to_format << std::endl;
338
339         CacheType::iterator const it1 = pimpl_->cache.find(orig_from);
340         if (it1 == pimpl_->cache.end())
341                 return;
342         FormatCacheType & format_cache = it1->second.cache;
343         FormatCacheType::iterator const it2 = format_cache.find(to_format);
344         if (it2 == format_cache.end())
345                 return;
346
347         format_cache.erase(it2);
348         if (format_cache.empty())
349                 pimpl_->cache.erase(it1);
350 }
351
352
353 void ConverterCache::remove_all(string const & from_format,
354                 string const & to_format) const
355 {
356         if (!lyxrc.use_converter_cache)
357                 return;
358         CacheType::iterator it1 = pimpl_->cache.begin();
359         while (it1 != pimpl_->cache.end()) {
360                 if (it1->second.from_format != from_format) {
361                         ++it1;
362                         continue;
363                 }
364                 FormatCacheType & format_cache = it1->second.cache;
365                 FormatCacheType::iterator it2 = format_cache.begin();
366                 while (it2 != format_cache.end()) {
367                         if (it2->first == to_format) {
368                                 LYXERR(Debug::FILES)
369                                         << "Removing file cache item "
370                                         << it1->first
371                                         << ' ' << to_format << std::endl;
372                                 support::unlink(it2->second.cache_name);
373                                 format_cache.erase(it2);
374                                 // Have to start over again since items in a
375                                 // map are not ordered
376                                 it2 = format_cache.begin();
377                         } else
378                                 ++it2;
379                 }
380                 if (format_cache.empty()) {
381                         pimpl_->cache.erase(it1);
382                         // Have to start over again since items in a map are
383                         // not ordered
384                         it1 = pimpl_->cache.begin();
385                 } else
386                         ++it1;
387         }
388         pimpl_->writeIndex();
389 }
390
391
392 bool ConverterCache::inCache(FileName const & orig_from,
393                 string const & to_format) const
394 {
395         if (!lyxrc.use_converter_cache || orig_from.empty())
396                 return false;
397         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
398                              << ' ' << to_format << std::endl;
399
400         CacheItem * const item = pimpl_->find(orig_from, to_format);
401         if (!item) {
402                 LYXERR(Debug::FILES) << "not in cache." << std::endl;
403                 return false;
404         }
405         time_t const timestamp = fs::last_write_time(orig_from.toFilesystemEncoding());
406         if (item->timestamp == timestamp) {
407                 LYXERR(Debug::FILES) << "identical timestamp." << std::endl;
408                 return true;
409         }
410         if (item->checksum == support::sum(orig_from)) {
411                 item->timestamp = timestamp;
412                 LYXERR(Debug::FILES) << "identical checksum." << std::endl;
413                 return true;
414         }
415         LYXERR(Debug::FILES) << "in cache, but too old." << std::endl;
416         return false;
417 }
418
419
420 FileName const & ConverterCache::cacheName(FileName const & orig_from,
421                 string const & to_format) const
422 {
423         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
424                              << ' ' << to_format << std::endl;
425
426         CacheItem * const item = pimpl_->find(orig_from, to_format);
427         BOOST_ASSERT(item);
428         return item->cache_name;
429 }
430
431
432 bool ConverterCache::copy(FileName const & orig_from, string const & to_format,
433                 FileName const & dest) const
434 {
435         if (!lyxrc.use_converter_cache || orig_from.empty() || dest.empty())
436                 return false;
437         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
438                              << ' ' << to_format << ' ' << dest << std::endl;
439
440         // FIXME: Should not hardcode this (see bug 3819 for details)
441         if (to_format == "pstex") {
442                 FileName const dest_eps(support::changeExtension(dest.absFilename(), "eps"));
443                 if (!copy(orig_from, "eps", dest_eps))
444                         return false;
445         } else if (to_format == "pdftex") {
446                 FileName const dest_pdf(support::changeExtension(dest.absFilename(), "pdf"));
447                 if (!copy(orig_from, "pdf", dest_pdf))
448                         return false;
449         }
450
451         CacheItem * const item = pimpl_->find(orig_from, to_format);
452         BOOST_ASSERT(item);
453         Mover const & mover = getMover(to_format);
454         return mover.copy(item->cache_name, dest,
455                           support::onlyFilename(dest.absFilename()));
456 }
457
458 } // namespace lyx