]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsCacheItem.cpp
Merge branch 'master' into biblatex2
[lyx.git] / src / graphics / GraphicsCacheItem.cpp
1 /**
2  * \file GraphicsCacheItem.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 Herbert Voß
8  * \author Angus Leeming
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "GraphicsCacheItem.h"
16
17 #include "Buffer.h"
18 #include "GraphicsCache.h"
19 #include "GraphicsConverter.h"
20 #include "GraphicsImage.h"
21
22 #include "ConverterCache.h"
23 #include "Format.h"
24
25 #include "support/debug.h"
26 #include "support/FileName.h"
27 #include "support/filetools.h"
28 #include "support/FileMonitor.h"
29 #include "support/lassert.h"
30 #include "support/unique_ptr.h"
31
32 #include "support/bind.h"
33 #include "support/TempFile.h"
34
35 using namespace std;
36 using namespace lyx::support;
37
38 namespace lyx {
39
40 namespace graphics {
41
42 class CacheItem::Impl : public boost::signals2::trackable {
43 public:
44
45         ///
46         Impl(FileName const & file, FileName const & doc_file);
47
48         /**
49          *  If no file conversion is needed, then tryDisplayFormat() calls
50          *  loadImage() directly.
51          * \return true if a conversion is necessary and no error occurred. 
52          */
53         bool tryDisplayFormat(FileName & filename, string & from);
54
55         /** Start the image conversion process, checking first that it is
56          *  necessary. If it is necessary, then a conversion task is started.
57          *  CacheItem asumes that the conversion is asynchronous and so
58          *  passes a Signal to the converting routine. When the conversion
59          *  is finished, this Signal is emitted, returning the converted
60          *  file to this->imageConverted.
61          *
62          *  convertToDisplayFormat() will set the loading status flag as
63          *  approriate through calls to setStatus().
64          */
65         void convertToDisplayFormat();
66
67         /** Load the image into memory. This is called either from
68          *  convertToDisplayFormat() direct or from imageConverted().
69          */
70         bool loadImage();
71
72         /** Get a notification when the image conversion is done.
73          *  Connected to a signal on_finish_ which is passed to
74          *  Converter::convert.
75          */
76         void imageConverted(bool);
77
78         /** Sets the status of the loading process. Also notifies
79          *  listeners that the status has changed.
80          */
81         void setStatus(ImageStatus new_status);
82
83         /** Can be invoked directly by the user, but is also connected to the
84          *  FileMonitor and so is invoked when the file is changed
85          *  (if monitoring is taking place).
86          */
87         void startLoading();
88
89         /** If we are asked to load the file for a second or further time,
90          *  (because the file has changed), then we'll have to first reset
91          *  many of the variables below.
92          */
93         void reset();
94
95         /// The filename we refer too.
96         FileName const filename_;
97         /// The document filename this graphic item belongs to
98         FileName const & doc_file_;
99         ///
100         FileMonitor const monitor_;
101
102         /// Is the file compressed?
103         bool zipped_;
104         /// If so, store the uncompressed file in this temporary file.
105         FileName unzipped_filename_;
106         /// The target format
107         string to_;
108         /// What file are we trying to load?
109         FileName file_to_load_;
110         /** Should we delete the file after loading? True if the file is
111          *  the result of a conversion process.
112          */
113         bool remove_loaded_file_;
114
115         /// The image and its loading status.
116         std::shared_ptr<Image> image_;
117         ///
118         ImageStatus status_;
119
120         /// This signal is emitted when the image loading status changes.
121         boost::signals2::signal<void()> statusChanged;
122
123         /// The connection of the signal ConvProcess::finishedConversion,
124         boost::signals2::connection cc_;
125
126         ///
127         unique_ptr<Converter> converter_;
128 };
129
130
131 CacheItem::CacheItem(FileName const & file, FileName const & doc_file)
132   : pimpl_(new Impl(file,doc_file))
133 {}
134
135
136 CacheItem::~CacheItem()
137 {
138         delete pimpl_;
139 }
140
141
142 FileName const & CacheItem::filename() const
143 {
144         return pimpl_->filename_;
145 }
146
147
148 bool CacheItem::tryDisplayFormat() const
149 {
150         if (pimpl_->status_ != WaitingToLoad)
151                 pimpl_->reset();
152         FileName filename;
153         string from;
154         bool const conversion_needed = pimpl_->tryDisplayFormat(filename, from);
155         bool const success = status() == Loaded && !conversion_needed;
156         if (!success)
157                 pimpl_->reset();
158         return success;
159 }
160
161
162 void CacheItem::startLoading() const
163 {
164         pimpl_->startLoading();
165 }
166
167
168 void CacheItem::startMonitoring() const
169 {
170         if (!pimpl_->monitor_.monitoring())
171                 pimpl_->monitor_.start();
172 }
173
174
175 bool CacheItem::monitoring() const
176 {
177         return pimpl_->monitor_.monitoring();
178 }
179
180
181 unsigned long CacheItem::checksum() const
182 {
183         return pimpl_->monitor_.checksum();
184 }
185
186
187 Image const * CacheItem::image() const
188 {
189         return pimpl_->image_.get();
190 }
191
192
193 ImageStatus CacheItem::status() const
194 {
195         return pimpl_->status_;
196 }
197
198
199 boost::signals2::connection CacheItem::connect(slot_type const & slot) const
200 {
201         return pimpl_->statusChanged.connect(slot);
202 }
203
204
205 //------------------------------
206 // Implementation details follow
207 //------------------------------
208
209
210 CacheItem::Impl::Impl(FileName const & file, FileName const & doc_file)
211         : filename_(file), doc_file_(doc_file),
212           monitor_(file, 2000),
213           zipped_(false),
214           remove_loaded_file_(false),
215           status_(WaitingToLoad)
216 {
217         monitor_.connect(bind(&Impl::startLoading, this));
218 }
219
220
221 void CacheItem::Impl::startLoading()
222 {
223         if (status_ != WaitingToLoad)
224                 reset();
225
226         convertToDisplayFormat();
227 }
228
229
230 void CacheItem::Impl::reset()
231 {
232         zipped_ = false;
233         if (!unzipped_filename_.empty())
234                 unzipped_filename_.removeFile();
235         unzipped_filename_.erase();
236
237         if (remove_loaded_file_ && !file_to_load_.empty())
238                 file_to_load_.removeFile();
239         remove_loaded_file_ = false;
240         file_to_load_.erase();
241         to_.erase();
242
243         if (image_)
244                 image_.reset();
245
246         status_ = WaitingToLoad;
247
248         if (cc_.connected())
249                 cc_.disconnect();
250
251         if (converter_)
252                 converter_.reset();
253 }
254
255
256 void CacheItem::Impl::setStatus(ImageStatus new_status)
257 {
258         if (status_ == new_status)
259                 return;
260
261         status_ = new_status;
262         statusChanged();
263 }
264
265
266 void CacheItem::Impl::imageConverted(bool success)
267 {
268         string const text = success ? "succeeded" : "failed";
269         LYXERR(Debug::GRAPHICS, "Image conversion " << text << '.');
270
271         file_to_load_ = converter_ ? FileName(converter_->convertedFile())
272                                        : FileName();
273         converter_.reset();
274         cc_.disconnect();
275
276         success = !file_to_load_.empty() && file_to_load_.isReadableFile();
277
278         if (!success) {
279                 LYXERR(Debug::GRAPHICS, "Unable to find converted file!");
280                 setStatus(ErrorConverting);
281
282                 if (zipped_)
283                         unzipped_filename_.removeFile();
284
285                 return;
286         }
287
288         // Add the converted file to the file cache
289         ConverterCache::get().add(filename_, to_, file_to_load_);
290
291         setStatus(loadImage() ? Loaded : ErrorLoading);
292 }
293
294
295 // This function gets called from the callback after the image has been
296 // converted successfully.
297 bool CacheItem::Impl::loadImage()
298 {
299         LYXERR(Debug::GRAPHICS, "Loading image.");
300
301         image_.reset(newImage());
302
303         bool success = image_->load(file_to_load_);
304         string const text = success ? "succeeded" : "failed";
305         LYXERR(Debug::GRAPHICS, "Image loading " << text << '.');
306
307         // Clean up after loading.
308         if (zipped_)
309                 unzipped_filename_.removeFile();
310
311         if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
312                 file_to_load_.removeFile();
313
314         return success;
315 }
316
317
318 typedef vector<string> FormatList;
319
320 static string const findTargetFormat(FormatList const & formats, string const & from)
321 {
322          // There must be a format to load from.
323         LASSERT(!formats.empty(), return string());
324
325         // Use the standard converter if we don't know the format to load
326         // from.
327         if (from.empty())
328                 return string("ppm");
329
330         // First ascertain if we can load directly with no conversion
331         FormatList::const_iterator it  = formats.begin();
332         FormatList::const_iterator end = formats.end();
333         for (; it != end; ++it) {
334                 if (from == *it)
335                         return *it;
336         }
337
338         // So, we have to convert to a loadable format. Can we?
339         it = formats.begin();
340         for (; it != end; ++it) {
341                 if (lyx::graphics::Converter::isReachable(from, *it))
342                         return *it;
343                 else
344                         LYXERR(Debug::GRAPHICS, "Unable to convert from " << from
345                                 << " to " << *it);
346         }
347
348         // Failed! so we have to try to convert it to PPM format
349         // with the standard converter
350         return string("ppm");
351 }
352
353
354 bool CacheItem::Impl::tryDisplayFormat(FileName & filename, string & from)
355 {
356         // First, check that the file exists!
357         filename_.refresh();
358         if (!filename_.isReadableFile()) {
359                 if (status_ != ErrorNoFile) {
360                         status_ = ErrorNoFile;
361                         LYXERR(Debug::GRAPHICS, "\tThe file is not readable");
362                 }
363                 return false;
364         }
365
366         zipped_ = formats.isZippedFile(filename_);
367         if (zipped_) {
368                 string tempname = unzippedFileName(filename_.toFilesystemEncoding());
369                 string const ext = getExtension(tempname);
370                 tempname = changeExtension(tempname, "") + "-XXXXXX";
371                 if (!ext.empty())
372                         tempname = addExtension(tempname, ext);
373                 TempFile tempfile(tempname);
374                 tempfile.setAutoRemove(false);
375                 unzipped_filename_ = tempfile.name();
376                 if (unzipped_filename_.empty()) {
377                         status_ = ErrorConverting;
378                         LYXERR(Debug::GRAPHICS, "\tCould not create temporary file.");
379                         return false;
380                 }
381                 filename = unzipFile(filename_, unzipped_filename_.toFilesystemEncoding());
382         } else {
383                 filename = filename_;
384         }
385
386         docstring const displayed_filename = makeDisplayPath(filename_.absFileName());
387         LYXERR(Debug::GRAPHICS, "[CacheItem::Impl::convertToDisplayFormat]\n"
388                 << "\tAttempting to convert image file: " << filename
389                 << "\n\twith displayed filename: " << to_utf8(displayed_filename));
390
391         from = formats.getFormatFromFile(filename);
392         if (from.empty()) {
393                 status_ = ErrorConverting;
394                 LYXERR(Debug::GRAPHICS, "\tCould not determine file format.");
395         }
396         LYXERR(Debug::GRAPHICS, "\n\tThe file contains " << from << " format data.");
397         to_ = findTargetFormat(Cache::get().loadableFormats(), from);
398
399         if (from == to_) {
400                 // No conversion needed!
401                 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (from == to)!");
402                 file_to_load_ = filename;
403                 status_ = loadImage() ? Loaded : ErrorLoading;
404                 return false;
405         }
406
407         if (ConverterCache::get().inCache(filename, to_)) {
408                 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (file in file cache)!");
409                 file_to_load_ = ConverterCache::get().cacheName(filename, to_);
410                 status_ = loadImage() ? Loaded : ErrorLoading;
411                 return false;
412         }
413         return true;
414 }
415
416
417 void CacheItem::Impl::convertToDisplayFormat()
418 {
419         LYXERR(Debug::GRAPHICS, "\tConverting it to " << to_ << " format.");
420
421         // Make a local copy in case we unzip it
422         FileName filename;
423         string from;
424         if (!tryDisplayFormat(filename, from)) {
425                 // The image status has changed, tell it to the outside world.
426                 statusChanged();
427                 return;
428         }
429
430         // We will need a conversion, tell it to the outside world.
431         setStatus(Converting);
432
433         // Add some stuff to create a uniquely named temporary file.
434         // This file is deleted in loadImage after it is loaded into memory.
435         TempFile tempfile("CacheItem");
436         tempfile.setAutoRemove(false);
437         FileName const to_file_base = tempfile.name();
438         remove_loaded_file_ = true;
439
440         // Connect a signal to this->imageConverted and pass this signal to
441         // the graphics converter so that we can load the modified file
442         // on completion of the conversion process.
443         converter_ = make_unique<Converter>(doc_file_, filename, to_file_base.absFileName(),
444                                             from, to_);
445         converter_->connect(bind(&Impl::imageConverted, this, _1));
446         converter_->startConversion();
447 }
448
449 } // namespace graphics
450 } // namespace lyx