2 * \file ConverterCache.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Angus Leeming
10 * Full author contact details are available in file CREDITS.
15 #include "ConverterCache.h"
22 #include "support/convert.h"
23 #include "support/debug.h"
24 #include "support/filetools.h"
25 #include "support/lyxtime.h"
26 #include "support/Package.h"
28 #include "support/lassert.h"
29 #include <boost/crc.hpp>
38 using namespace lyx::support
;
44 unsigned long do_crc(string
const & s
)
46 boost::crc_32_type crc
;
47 crc
= for_each(s
.begin(), s
.end(), crc
);
48 return crc
.checksum();
52 static FileName cache_dir
;
58 CacheItem(FileName
const & orig_from
, string
const & to_format
,
59 time_t t
, unsigned long c
)
60 : timestamp(t
), checksum(c
)
63 os
<< setw(10) << setfill('0') << do_crc(orig_from
.absFilename())
65 cache_name
= FileName(addName(cache_dir
.absFilename(), os
.str()));
66 LYXERR(Debug::FILES
, "Add file cache item " << orig_from
67 << ' ' << to_format
<< ' ' << cache_name
68 << ' ' << long(timestamp
) << ' ' << checksum
<< '.');
73 unsigned long checksum
;
79 /** The cache contains one item per orig file and target format, so use a
80 * nested map to find the cache item quickly by filename and format.
82 typedef map
<string
, CacheItem
> FormatCacheType
;
85 /// Format of the source file
87 /// Cache target format -> item to quickly find the item by format
88 FormatCacheType cache
;
90 typedef map
<FileName
, FormatCache
> CacheType
;
93 class ConverterCache::Impl
{
100 CacheItem
* find(FileName
const & from
, string
const & format
);
105 void ConverterCache::Impl::readIndex()
107 time_t const now
= current_time();
108 FileName
const index(addName(cache_dir
.absFilename(), "index"));
109 ifstream
is(index
.toFilesystemEncoding().c_str());
115 string
const orig_from
= lex
.getString();
118 string
const to_format
= lex
.getString();
121 time_t const timestamp
=
122 convert
<unsigned long>(lex
.getString());
125 unsigned long const checksum
=
126 convert
<unsigned long>(lex
.getString());
127 FileName
const orig_from_name(orig_from
);
128 CacheItem
item(orig_from_name
, to_format
, timestamp
, checksum
);
130 // Don't cache files that do not exist anymore
131 if (!orig_from_name
.exists()) {
132 LYXERR(Debug::FILES
, "Not caching file `"
133 << orig_from
<< "' (does not exist anymore).");
134 item
.cache_name
.removeFile();
138 // Don't add items that are not in the cache anymore
139 // This can happen if two instances of LyX are running
140 // at the same time and update the index file independantly.
141 if (!item
.cache_name
.exists()) {
142 LYXERR(Debug::FILES
, "Not caching file `" << orig_from
143 << "' (cached copy does not exist anymore).");
147 // Delete the cached file if it is too old
148 if (difftime(now
, item
.cache_name
.lastModified())
149 > lyxrc
.converter_cache_maxage
) {
150 LYXERR(Debug::FILES
, "Not caching file `"
151 << orig_from
<< "' (too old).");
152 item
.cache_name
.removeFile();
156 FormatCache
& format_cache
= cache
[orig_from_name
];
157 if (format_cache
.from_format
.empty())
158 format_cache
.from_format
=
159 formats
.getFormatFromFile(orig_from_name
);
160 format_cache
.cache
[to_format
] = item
;
166 void ConverterCache::Impl::writeIndex()
168 FileName
const index(addName(cache_dir
.absFilename(), "index"));
169 ofstream
os(index
.toFilesystemEncoding().c_str());
171 if (!index
.changePermission(0600))
173 os
.open(index
.toFilesystemEncoding().c_str());
174 CacheType::iterator it1
= cache
.begin();
175 CacheType::iterator
const end1
= cache
.end();
176 for (; it1
!= end1
; ++it1
) {
177 FormatCacheType
const & format_cache
= it1
->second
.cache
;
178 FormatCacheType::const_iterator it2
= format_cache
.begin();
179 FormatCacheType::const_iterator
const end2
= format_cache
.end();
180 for (; it2
!= end2
; ++it2
)
181 os
<< Lexer::quoteString(it1
->first
.absFilename())
182 << ' ' << it2
->first
<< ' '
183 << it2
->second
.timestamp
<< ' '
184 << it2
->second
.checksum
<< '\n';
190 CacheItem
* ConverterCache::Impl::find(FileName
const & from
,
191 string
const & format
)
193 if (!lyxrc
.use_converter_cache
)
195 CacheType::iterator
const it1
= cache
.find(from
);
196 if (it1
== cache
.end())
198 FormatCacheType
& format_cache
= it1
->second
.cache
;
199 FormatCacheType::iterator
const it2
= format_cache
.find(format
);
200 if (it2
== format_cache
.end())
202 return &(it2
->second
);
206 /////////////////////////////////////////////////////////////////////
210 /////////////////////////////////////////////////////////////////////
212 ConverterCache::ConverterCache()
217 ConverterCache::~ConverterCache()
223 ConverterCache
& ConverterCache::get()
225 // Now return the cache
226 static ConverterCache singleton
;
231 void ConverterCache::init()
233 if (!lyxrc
.use_converter_cache
)
235 // We do this here and not in the constructor because package() gets
236 // initialized after all static variables.
237 cache_dir
= FileName(addName(package().user_support().absFilename(), "cache"));
238 if (!cache_dir
.exists())
239 if (!cache_dir
.createDirectory(0700)) {
240 lyxerr
<< "Could not create cache directory `"
241 << cache_dir
<< "'." << endl
;
244 get().pimpl_
->readIndex();
248 void ConverterCache::writeIndex() const
250 pimpl_
->writeIndex();
254 void ConverterCache::add(FileName
const & orig_from
, string
const & to_format
,
255 FileName
const & converted_file
) const
257 if (!lyxrc
.use_converter_cache
|| orig_from
.empty() ||
258 converted_file
.empty())
260 LYXERR(Debug::FILES
, ' ' << orig_from
261 << ' ' << to_format
<< ' ' << converted_file
);
263 // FIXME: Should not hardcode this (see bug 3819 for details)
264 if (to_format
== "pstex") {
265 FileName
const converted_eps(changeExtension(converted_file
.absFilename(), "eps"));
266 add(orig_from
, "eps", converted_eps
);
267 } else if (to_format
== "pdftex") {
268 FileName
const converted_pdf(changeExtension(converted_file
.absFilename(), "pdf"));
269 add(orig_from
, "pdf", converted_pdf
);
272 // Is the file in the cache already?
273 CacheItem
* item
= pimpl_
->find(orig_from
, to_format
);
275 time_t const timestamp
= orig_from
.lastModified();
276 Mover
const & mover
= getMover(to_format
);
278 LYXERR(Debug::FILES
, "ConverterCache::add(" << orig_from
<< "):\n"
279 "The file is already in the cache.");
280 // First test for timestamp
281 if (timestamp
== item
->timestamp
) {
282 LYXERR(Debug::FILES
, "Same timestamp.");
285 // Maybe the contents is still the same?
286 item
->timestamp
= timestamp
;
287 unsigned long const checksum
= orig_from
.checksum();
288 if (checksum
== item
->checksum
) {
289 LYXERR(Debug::FILES
, "Same checksum.");
292 item
->checksum
= checksum
;
293 if (!mover
.copy(converted_file
, item
->cache_name
,
294 onlyFilename(item
->cache_name
.absFilename()))) {
295 LYXERR(Debug::FILES
, "Could not copy file " << orig_from
<< " to "
296 << item
->cache_name
);
297 } else if (!item
->cache_name
.changePermission(0600)) {
298 LYXERR(Debug::FILES
, "Could not change file mode"
299 << item
->cache_name
);
302 CacheItem
new_item(orig_from
, to_format
, timestamp
,
303 orig_from
.checksum());
304 if (mover
.copy(converted_file
, new_item
.cache_name
,
305 onlyFilename(new_item
.cache_name
.absFilename()))) {
306 if (!new_item
.cache_name
.changePermission(0600)) {
307 LYXERR(Debug::FILES
, "Could not change file mode"
308 << new_item
.cache_name
);
310 FormatCache
& format_cache
= pimpl_
->cache
[orig_from
];
311 if (format_cache
.from_format
.empty())
312 format_cache
.from_format
=
313 formats
.getFormatFromFile(orig_from
);
314 format_cache
.cache
[to_format
] = new_item
;
316 LYXERR(Debug::FILES
, "ConverterCache::add(" << orig_from
<< "):\n"
317 "Could not copy file.");
322 void ConverterCache::remove(FileName
const & orig_from
,
323 string
const & to_format
) const
325 if (!lyxrc
.use_converter_cache
|| orig_from
.empty())
327 LYXERR(Debug::FILES
, orig_from
<< ' ' << to_format
);
329 CacheType::iterator
const it1
= pimpl_
->cache
.find(orig_from
);
330 if (it1
== pimpl_
->cache
.end())
332 FormatCacheType
& format_cache
= it1
->second
.cache
;
333 FormatCacheType::iterator
const it2
= format_cache
.find(to_format
);
334 if (it2
== format_cache
.end())
337 format_cache
.erase(it2
);
338 if (format_cache
.empty())
339 pimpl_
->cache
.erase(it1
);
343 void ConverterCache::remove_all(string
const & from_format
,
344 string
const & to_format
) const
346 if (!lyxrc
.use_converter_cache
)
348 CacheType::iterator it1
= pimpl_
->cache
.begin();
349 while (it1
!= pimpl_
->cache
.end()) {
350 if (it1
->second
.from_format
!= from_format
) {
354 FormatCacheType
& format_cache
= it1
->second
.cache
;
355 FormatCacheType::iterator it2
= format_cache
.begin();
356 while (it2
!= format_cache
.end()) {
357 if (it2
->first
== to_format
) {
358 LYXERR(Debug::FILES
, "Removing file cache item "
359 << it1
->first
<< ' ' << to_format
);
360 it2
->second
.cache_name
.removeFile();
361 format_cache
.erase(it2
);
362 // Have to start over again since items in a
363 // map are not ordered
364 it2
= format_cache
.begin();
369 if (format_cache
.empty()) {
370 pimpl_
->cache
.erase(it1
);
371 // Have to start over again since items in a map are
373 it1
= pimpl_
->cache
.begin();
378 pimpl_
->writeIndex();
382 bool ConverterCache::inCache(FileName
const & orig_from
,
383 string
const & to_format
) const
385 if (!lyxrc
.use_converter_cache
|| orig_from
.empty())
387 LYXERR(Debug::FILES
, orig_from
<< ' ' << to_format
);
389 CacheItem
* const item
= pimpl_
->find(orig_from
, to_format
);
391 LYXERR(Debug::FILES
, "not in cache.");
394 time_t const timestamp
= orig_from
.lastModified();
395 if (item
->timestamp
== timestamp
) {
396 LYXERR(Debug::FILES
, "identical timestamp.");
399 if (item
->checksum
== orig_from
.checksum()) {
400 item
->timestamp
= timestamp
;
401 LYXERR(Debug::FILES
, "identical checksum.");
404 LYXERR(Debug::FILES
, "in cache, but too old.");
409 FileName
const & ConverterCache::cacheName(FileName
const & orig_from
,
410 string
const & to_format
) const
412 LYXERR(Debug::FILES
, orig_from
<< ' ' << to_format
);
414 CacheItem
* const item
= pimpl_
->find(orig_from
, to_format
);
416 return item
->cache_name
;
420 bool ConverterCache::copy(FileName
const & orig_from
, string
const & to_format
,
421 FileName
const & dest
) const
423 if (!lyxrc
.use_converter_cache
|| orig_from
.empty() || dest
.empty())
425 LYXERR(Debug::FILES
, orig_from
<< ' ' << to_format
<< ' ' << dest
);
427 // FIXME: Should not hardcode this (see bug 3819 for details)
428 if (to_format
== "pstex") {
429 FileName
const dest_eps(changeExtension(dest
.absFilename(), "eps"));
430 if (!copy(orig_from
, "eps", dest_eps
))
432 } else if (to_format
== "pdftex") {
433 FileName
const dest_pdf(changeExtension(dest
.absFilename(), "pdf"));
434 if (!copy(orig_from
, "pdf", dest_pdf
))
438 CacheItem
* const item
= pimpl_
->find(orig_from
, to_format
);
440 Mover
const & mover
= getMover(to_format
);
441 return mover
.copy(item
->cache_name
, dest
,
442 onlyFilename(dest
.absFilename()));