]> git.lyx.org Git - lyx.git/blob - src/ConverterCache.cpp
Update of documentation in the source related to bug 4135 and the function callback...
[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         // FIXME: Should not hardcode this (see bug 3819 for details)
265         if (to_format == "pstex") {
266                 FileName const converted_eps(support::changeExtension(converted_file.absFilename(), "eps"));
267                 add(orig_from, "eps", converted_eps);
268         } else if (to_format == "pdftex") {
269                 FileName const converted_pdf(support::changeExtension(converted_file.absFilename(), "pdf"));
270                 add(orig_from, "pdf", converted_pdf);
271         }
272
273         // Is the file in the cache already?
274         CacheItem * item = pimpl_->find(orig_from, to_format);
275
276         time_t const timestamp = fs::last_write_time(orig_from.toFilesystemEncoding());
277         Mover const & mover = getMover(to_format);
278         if (item) {
279                 LYXERR(Debug::FILES) << "ConverterCache::add(" << orig_from << "):\n"
280                                         "The file is already in the cache."
281                                      << std::endl;
282                 // First test for timestamp
283                 if (timestamp == item->timestamp) {
284                         LYXERR(Debug::FILES) << "Same timestamp."
285                                              << std::endl;
286                         return;
287                 } else {
288                         // Maybe the contents is still the same?
289                         item->timestamp = timestamp;
290                         unsigned long const checksum = support::sum(orig_from);
291                         if (checksum == item->checksum) {
292                                 LYXERR(Debug::FILES) << "Same checksum."
293                                                      << std::endl;
294                                 return;
295                         }
296                         item->checksum = checksum;
297                 }
298                 if (!mover.copy(converted_file, item->cache_name,
299                                 support::onlyFilename(item->cache_name.absFilename()), 0600))
300                         LYXERR(Debug::FILES) << "ConverterCache::add("
301                                              << orig_from << "):\n"
302                                                 "Could not copy file."
303                                              << std::endl;
304         } else {
305                 CacheItem new_item(orig_from, to_format, timestamp,
306                                 support::sum(orig_from));
307                 if (mover.copy(converted_file, new_item.cache_name,
308                                support::onlyFilename(new_item.cache_name.absFilename()), 0600)) {
309                         FormatCache & format_cache = pimpl_->cache[orig_from];
310                         if (format_cache.from_format.empty())
311                                 format_cache.from_format =
312                                         formats.getFormatFromFile(orig_from);
313                         format_cache.cache[to_format] = new_item;
314                 } else
315                         LYXERR(Debug::FILES) << "ConverterCache::add("
316                                              << orig_from << "):\n"
317                                                 "Could not copy file."
318                                      << std::endl;
319         }
320 }
321
322
323 void ConverterCache::remove(FileName const & orig_from,
324                 string const & to_format) const
325 {
326         if (!lyxrc.use_converter_cache || orig_from.empty())
327                 return;
328         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
329                              << ' ' << to_format << std::endl;
330
331         CacheType::iterator const it1 = pimpl_->cache.find(orig_from);
332         if (it1 == pimpl_->cache.end())
333                 return;
334         FormatCacheType & format_cache = it1->second.cache;
335         FormatCacheType::iterator const it2 = format_cache.find(to_format);
336         if (it2 == format_cache.end())
337                 return;
338
339         format_cache.erase(it2);
340         if (format_cache.empty())
341                 pimpl_->cache.erase(it1);
342 }
343
344
345 void ConverterCache::remove_all(string const & from_format,
346                 string const & to_format) const
347 {
348         if (!lyxrc.use_converter_cache)
349                 return;
350         CacheType::iterator it1 = pimpl_->cache.begin();
351         while (it1 != pimpl_->cache.end()) {
352                 if (it1->second.from_format != from_format) {
353                         ++it1;
354                         continue;
355                 }
356                 FormatCacheType & format_cache = it1->second.cache;
357                 FormatCacheType::iterator it2 = format_cache.begin();
358                 while (it2 != format_cache.end()) {
359                         if (it2->first == to_format) {
360                                 LYXERR(Debug::FILES)
361                                         << "Removing file cache item "
362                                         << it1->first
363                                         << ' ' << to_format << std::endl;
364                                 support::unlink(it2->second.cache_name);
365                                 format_cache.erase(it2);
366                                 // Have to start over again since items in a
367                                 // map are not ordered
368                                 it2 = format_cache.begin();
369                         } else
370                                 ++it2;
371                 }
372                 if (format_cache.empty()) {
373                         pimpl_->cache.erase(it1);
374                         // Have to start over again since items in a map are
375                         // not ordered
376                         it1 = pimpl_->cache.begin();
377                 } else
378                         ++it1;
379         }
380         pimpl_->writeIndex();
381 }
382
383
384 bool ConverterCache::inCache(FileName const & orig_from,
385                 string const & to_format) const
386 {
387         if (!lyxrc.use_converter_cache || orig_from.empty())
388                 return false;
389         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
390                              << ' ' << to_format << std::endl;
391
392         CacheItem * const item = pimpl_->find(orig_from, to_format);
393         if (!item) {
394                 LYXERR(Debug::FILES) << "not in cache." << std::endl;
395                 return false;
396         }
397         time_t const timestamp = fs::last_write_time(orig_from.toFilesystemEncoding());
398         if (item->timestamp == timestamp) {
399                 LYXERR(Debug::FILES) << "identical timestamp." << std::endl;
400                 return true;
401         }
402         if (item->checksum == support::sum(orig_from)) {
403                 item->timestamp = timestamp;
404                 LYXERR(Debug::FILES) << "identical checksum." << std::endl;
405                 return true;
406         }
407         LYXERR(Debug::FILES) << "in cache, but too old." << std::endl;
408         return false;
409 }
410
411
412 FileName const & ConverterCache::cacheName(FileName const & orig_from,
413                 string const & to_format) const
414 {
415         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
416                              << ' ' << to_format << std::endl;
417
418         CacheItem * const item = pimpl_->find(orig_from, to_format);
419         BOOST_ASSERT(item);
420         return item->cache_name;
421 }
422
423
424 bool ConverterCache::copy(FileName const & orig_from, string const & to_format,
425                 FileName const & dest) const
426 {
427         if (!lyxrc.use_converter_cache || orig_from.empty() || dest.empty())
428                 return false;
429         LYXERR(Debug::FILES) << BOOST_CURRENT_FUNCTION << ' ' << orig_from
430                              << ' ' << to_format << ' ' << dest << std::endl;
431
432         // FIXME: Should not hardcode this (see bug 3819 for details)
433         if (to_format == "pstex") {
434                 FileName const dest_eps(support::changeExtension(dest.absFilename(), "eps"));
435                 if (!copy(orig_from, "eps", dest_eps))
436                         return false;
437         } else if (to_format == "pdftex") {
438                 FileName const dest_pdf(support::changeExtension(dest.absFilename(), "pdf"));
439                 if (!copy(orig_from, "pdf", dest_pdf))
440                         return false;
441         }
442
443         CacheItem * const item = pimpl_->find(orig_from, to_format);
444         BOOST_ASSERT(item);
445         Mover const & mover = getMover(to_format);
446         return mover.copy(item->cache_name, dest,
447                           support::onlyFilename(dest.absFilename()));
448 }
449
450 } // namespace lyx