]> git.lyx.org Git - lyx.git/blob - src/ConverterCache.C
fee76c0afbeb3ebd11a4675fb906476ae0a057ce
[lyx.git] / src / ConverterCache.C
1 /**
2  * \file ConverterCache.C
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 "debug.h"
18 #include "lyxrc.h"
19 #include "mover.h"
20
21 #include "support/filetools.h"
22 #include "support/lyxlib.h"
23 #include "support/lyxtime.h"
24 #include "support/package.h"
25
26 #include <boost/crc.hpp>
27 #include <boost/filesystem/operations.hpp>
28
29 #include <fstream>
30 #include <iomanip>
31 #include <map>
32 #include <sstream>
33
34 using lyx::support::absolutePath;
35 using lyx::support::addName;
36
37 using std::string;
38
39 namespace fs = boost::filesystem;
40
41 namespace lyx {
42
43 namespace {
44
45 unsigned long do_crc(string const & s)
46 {
47         boost::crc_32_type crc;
48         crc = std::for_each(s.begin(), s.end(), crc);
49         return crc.checksum();
50 }
51
52
53 static string cache_dir;
54
55
56 class CacheItem {
57 public:
58         CacheItem() {}
59         CacheItem(string const & orig_from, string const & to_format,
60                   time_t t, unsigned long c)
61                 : timestamp(t), checksum(c)
62         {
63                 BOOST_ASSERT(absolutePath(orig_from));
64                 std::ostringstream os;
65                 os << std::setw(10) << std::setfill('0') << do_crc(orig_from)
66                    << '-' << to_format;
67                 cache_name = addName(cache_dir, os.str());
68                 lyxerr[Debug::FILES] << "Add file cache item " << orig_from
69                                      << ' ' << to_format << ' ' << cache_name
70                                      << ' ' << timestamp << ' ' << checksum
71                                      << '.' << std::endl;
72         }
73         ~CacheItem() {}
74         string cache_name;
75         time_t timestamp;
76         unsigned long checksum;
77 };
78
79 }
80
81
82 /** The cache contains one item per orig file and target format, so use a
83  *  nested map to find the cache item quickly by filename and format.
84  */
85 typedef std::map<string, CacheItem> FormatCacheType;
86 typedef std::map<string, FormatCacheType> CacheType;
87
88
89 class ConverterCache::Impl {
90 public:
91         ///
92         void readIndex();
93         ///
94         void writeIndex();
95         ///
96         CacheItem * find(string const & from, string const & format);
97         CacheType cache;
98 };
99
100
101 void ConverterCache::Impl::readIndex()
102 {
103         time_t const now = current_time();
104         string const index = addName(cache_dir, "index");
105         std::ifstream is(index.c_str());
106         while (is.good()) {
107                 string orig_from;
108                 string to_format;
109                 time_t timestamp;
110                 unsigned long checksum;
111                 if (!(is >> orig_from >> to_format >> timestamp >> checksum))
112                         return;
113                 CacheItem item(orig_from, to_format, timestamp, checksum);
114
115                 // Don't cache files that do not exist anymore
116                 if (!fs::exists(orig_from)) {
117                         lyxerr[Debug::FILES] << "Not caching file `"
118                                 << orig_from << "' (does not exist anymore)."
119                                 << std::endl;
120                         support::unlink(item.cache_name);
121                         continue;
122                 }
123
124                 // Delete the cached file if it is too old
125                 if (difftime(now, fs::last_write_time(item.cache_name)) >
126                     lyxrc.converter_cache_maxage) {
127                         lyxerr[Debug::FILES] << "Not caching file `"
128                                 << orig_from << "' (too old)." << std::endl;
129                         support::unlink(item.cache_name);
130                         continue;
131                 }
132
133                 cache[orig_from][to_format] = item;
134         }
135         is.close();
136 }
137
138
139 void ConverterCache::Impl::writeIndex()
140 {
141         string const index = addName(cache_dir, "index");
142         std::ofstream os(index.c_str());
143         os.close();
144         if (!lyx::support::chmod(index.c_str(), 0600))
145                 return;
146         os.open(index.c_str());
147         CacheType::iterator it1 = cache.begin();
148         CacheType::iterator const end1 = cache.end();
149         for (; it1 != end1; ++it1) {
150                 FormatCacheType::iterator it2 = it1->second.begin();
151                 FormatCacheType::iterator const end2 = it1->second.end();
152                 for (; it2 != end2; ++it2)
153                         os << it1->first << ' ' << it2->first << ' '
154                            << it2->second.timestamp << ' '
155                            << it2->second.checksum << '\n';
156         }
157         os.close();
158 }
159
160
161 CacheItem * ConverterCache::Impl::find(string const & from,
162                 string const & format)
163 {
164         if (!lyxrc.use_converter_cache)
165                 return 0;
166         CacheType::iterator const it1 = cache.find(from);
167         if (it1 == cache.end())
168                 return 0;
169         FormatCacheType::iterator const it2 = it1->second.find(format);
170         if (it2 == it1->second.end())
171                 return 0;
172         return &(it2->second);
173 }
174
175
176 ConverterCache & ConverterCache::get()
177 {
178         // Now return the cache
179         static ConverterCache singleton;
180         return singleton;
181 }
182
183
184 void ConverterCache::init()
185 {
186         if (!lyxrc.use_converter_cache)
187                 return;
188         // We do this here and not in the constructor because package() gets
189         // initialized after all static variables.
190         cache_dir = addName(support::package().user_support(), "cache");
191         if (!fs::exists(cache_dir))
192                 if (support::mkdir(cache_dir, 0700) != 0) {
193                         lyxerr << "Could not create cache directory `"
194                                << cache_dir << "'." << std::endl;
195                         exit(EXIT_FAILURE);
196                 }
197         get().pimpl_->readIndex();
198 }
199
200
201 ConverterCache::ConverterCache()
202         : pimpl_(new Impl)
203 {}
204
205
206 ConverterCache::~ConverterCache()
207 {
208         if (!lyxrc.use_converter_cache)
209                 return;
210         pimpl_->writeIndex();
211 }
212
213
214 void ConverterCache::add(string const & orig_from, string const & to_format,
215                 string const & converted_file) const
216 {
217         if (!lyxrc.use_converter_cache)
218                 return;
219         lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
220                              << ' ' << to_format << ' ' << converted_file
221                              << std::endl;
222         BOOST_ASSERT(absolutePath(orig_from));
223         BOOST_ASSERT(absolutePath(converted_file));
224
225         // Is the file in the cache already?
226         CacheItem * item = pimpl_->find(orig_from, to_format);
227
228         time_t const timestamp = fs::last_write_time(orig_from);
229         Mover const & mover = movers(to_format);
230         if (item) {
231                 lyxerr[Debug::FILES] << "ConverterCache::add(" << orig_from << "):\n"
232                                         "The file is already in the cache."
233                                      << std::endl;
234                 // First test for timestamp
235                 if (timestamp == item->timestamp) {
236                         lyxerr[Debug::FILES] << "Same timestamp."
237                                              << std::endl;
238                         return;
239                 } else {
240                         // Maybe the contents is still the same?
241                         item->timestamp = timestamp;
242                         unsigned long const checksum = support::sum(orig_from);
243                         if (checksum == item->checksum) {
244                                 lyxerr[Debug::FILES] << "Same checksum."
245                                                      << std::endl;
246                                 return;
247                         }
248                         item->checksum = checksum;
249                 }
250                 if (!mover.copy(converted_file, item->cache_name, 0600))
251                         lyxerr[Debug::FILES] << "ConverterCache::add("
252                                              << orig_from << "):\n"
253                                                 "Could not copy file."
254                                              << std::endl;
255         } else {
256                 CacheItem new_item = CacheItem(orig_from, to_format, timestamp,
257                                 support::sum(orig_from));
258                 if (mover.copy(converted_file, new_item.cache_name, 0600))
259                         pimpl_->cache[orig_from][to_format] = new_item;
260                 else
261                         lyxerr[Debug::FILES] << "ConverterCache::add("
262                                              << orig_from << "):\n"
263                                                 "Could not copy file."
264                                              << std::endl;
265         }
266 }
267
268
269 void ConverterCache::remove(string const & orig_from,
270                 string const & to_format) const
271 {
272         if (!lyxrc.use_converter_cache)
273                 return;
274         lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
275                              << ' ' << to_format << std::endl;
276         BOOST_ASSERT(absolutePath(orig_from));
277
278         CacheType::iterator const it1 = pimpl_->cache.find(orig_from);
279         if (it1 == pimpl_->cache.end())
280                 return;
281         FormatCacheType::iterator const it2 = it1->second.find(to_format);
282         if (it2 == it1->second.end())
283                 return;
284
285         it1->second.erase(it2);
286         if (it1->second.empty())
287                 pimpl_->cache.erase(it1);
288 }
289
290
291 bool ConverterCache::inCache(string const & orig_from,
292                 string const & to_format) const
293 {
294         if (!lyxrc.use_converter_cache)
295                 return false;
296         lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
297                              << ' ' << to_format << std::endl;
298         BOOST_ASSERT(absolutePath(orig_from));
299
300         CacheItem * const item = pimpl_->find(orig_from, to_format);
301         if (!item) {
302                 lyxerr[Debug::FILES] << "not in cache." << std::endl;
303                 return false;
304         }
305         time_t const timestamp = fs::last_write_time(orig_from);
306         if (item->timestamp == timestamp) {
307                 lyxerr[Debug::FILES] << "identical timestamp." << std::endl;
308                 return true;
309         }
310         if (item->checksum == support::sum(orig_from)) {
311                 item->timestamp = timestamp;
312                 lyxerr[Debug::FILES] << "identical checksum." << std::endl;
313                 return true;
314         }
315         lyxerr[Debug::FILES] << "in cache, but too old." << std::endl;
316         return false;
317 }
318
319
320 string const ConverterCache::cacheName(string const & orig_from,
321                 string const & to_format) const
322 {
323         lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
324                              << ' ' << to_format << std::endl;
325         BOOST_ASSERT(absolutePath(orig_from));
326
327         CacheItem * const item = pimpl_->find(orig_from, to_format);
328         BOOST_ASSERT(item);
329         return item->cache_name;
330 }
331
332
333 bool ConverterCache::copy(string const & orig_from, string const & to_format,
334                 string const & dest) const
335 {
336         if (!lyxrc.use_converter_cache)
337                 return false;
338         lyxerr[Debug::FILES] << BOOST_CURRENT_FUNCTION << ' ' << orig_from
339                              << ' ' << to_format << ' ' << dest << std::endl;
340         BOOST_ASSERT(absolutePath(orig_from));
341         BOOST_ASSERT(absolutePath(dest));
342
343         CacheItem * const item = pimpl_->find(orig_from, to_format);
344         BOOST_ASSERT(item);
345         Mover const & mover = movers(to_format);
346         return mover.copy(item->cache_name, dest);
347 }
348
349 } // namespace lyx