2 * \file GraphicsCacheItem.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * \author Angus Leeming
10 * Full author contact details are available in file CREDITS.
15 #include "GraphicsCacheItem.h"
18 #include "GraphicsCache.h"
19 #include "GraphicsConverter.h"
20 #include "GraphicsImage.h"
22 #include "ConverterCache.h"
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"
32 #include "support/bind.h"
33 #include "support/TempFile.h"
36 using namespace lyx::support;
42 class CacheItem::Impl : public boost::signals2::trackable {
46 Impl(FileName const & file, FileName const & doc_file);
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.
53 bool tryDisplayFormat(FileName & filename, string & from);
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.
62 * convertToDisplayFormat() will set the loading status flag as
63 * approriate through calls to setStatus().
65 void convertToDisplayFormat();
67 /** Load the image into memory. This is called either from
68 * convertToDisplayFormat() direct or from imageConverted().
72 /** Get a notification when the image conversion is done.
73 * Connected to a signal on_finish_ which is passed to
76 void imageConverted(bool);
78 /** Sets the status of the loading process. Also notifies
79 * listeners that the status has changed.
81 void setStatus(ImageStatus new_status);
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).
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.
95 /// The filename we refer too.
96 FileName const filename_;
97 /// The document filename this graphic item belongs to
98 FileName const & doc_file_;
100 FileMonitor const monitor_;
102 /// Is the file compressed?
104 /// If so, store the uncompressed file in this temporary file.
105 FileName unzipped_filename_;
106 /// The target format
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.
113 bool remove_loaded_file_;
115 /// The image and its loading status.
116 std::shared_ptr<Image> image_;
120 /// This signal is emitted when the image loading status changes.
121 boost::signals2::signal<void()> statusChanged;
123 /// The connection of the signal ConvProcess::finishedConversion,
124 boost::signals2::connection cc_;
127 unique_ptr<Converter> converter_;
131 CacheItem::CacheItem(FileName const & file, FileName const & doc_file)
132 : pimpl_(new Impl(file,doc_file))
136 CacheItem::~CacheItem()
142 FileName const & CacheItem::filename() const
144 return pimpl_->filename_;
148 bool CacheItem::tryDisplayFormat() const
150 if (pimpl_->status_ != WaitingToLoad)
154 bool const conversion_needed = pimpl_->tryDisplayFormat(filename, from);
155 bool const success = status() == Loaded && !conversion_needed;
162 void CacheItem::startLoading() const
164 pimpl_->startLoading();
168 void CacheItem::startMonitoring() const
170 if (!pimpl_->monitor_.monitoring())
171 pimpl_->monitor_.start();
175 bool CacheItem::monitoring() const
177 return pimpl_->monitor_.monitoring();
181 unsigned long CacheItem::checksum() const
183 return pimpl_->monitor_.checksum();
187 Image const * CacheItem::image() const
189 return pimpl_->image_.get();
193 ImageStatus CacheItem::status() const
195 return pimpl_->status_;
199 boost::signals2::connection CacheItem::connect(slot_type const & slot) const
201 return pimpl_->statusChanged.connect(slot);
205 //------------------------------
206 // Implementation details follow
207 //------------------------------
210 CacheItem::Impl::Impl(FileName const & file, FileName const & doc_file)
211 : filename_(file), doc_file_(doc_file),
212 monitor_(file, 2000),
214 remove_loaded_file_(false),
215 status_(WaitingToLoad)
217 monitor_.connect(bind(&Impl::startLoading, this));
221 void CacheItem::Impl::startLoading()
223 if (status_ != WaitingToLoad)
226 convertToDisplayFormat();
230 void CacheItem::Impl::reset()
233 if (!unzipped_filename_.empty())
234 unzipped_filename_.removeFile();
235 unzipped_filename_.erase();
237 if (remove_loaded_file_ && !file_to_load_.empty())
238 file_to_load_.removeFile();
239 remove_loaded_file_ = false;
240 file_to_load_.erase();
246 status_ = WaitingToLoad;
256 void CacheItem::Impl::setStatus(ImageStatus new_status)
258 if (status_ == new_status)
261 status_ = new_status;
266 void CacheItem::Impl::imageConverted(bool success)
268 string const text = success ? "succeeded" : "failed";
269 LYXERR(Debug::GRAPHICS, "Image conversion " << text << '.');
271 file_to_load_ = converter_ ? FileName(converter_->convertedFile())
276 success = !file_to_load_.empty() && file_to_load_.isReadableFile();
279 LYXERR(Debug::GRAPHICS, "Unable to find converted file!");
280 setStatus(ErrorConverting);
283 unzipped_filename_.removeFile();
288 // Add the converted file to the file cache
289 ConverterCache::get().add(filename_, to_, file_to_load_);
291 setStatus(loadImage() ? Loaded : ErrorLoading);
295 // This function gets called from the callback after the image has been
296 // converted successfully.
297 bool CacheItem::Impl::loadImage()
299 LYXERR(Debug::GRAPHICS, "Loading image.");
301 image_.reset(newImage());
303 bool success = image_->load(file_to_load_);
304 string const text = success ? "succeeded" : "failed";
305 LYXERR(Debug::GRAPHICS, "Image loading " << text << '.');
307 // Clean up after loading.
309 unzipped_filename_.removeFile();
311 if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
312 file_to_load_.removeFile();
318 typedef vector<string> FormatList;
320 static string const findTargetFormat(FormatList const & formats, string const & from)
322 // There must be a format to load from.
323 LASSERT(!formats.empty(), return string());
325 // Use the standard converter if we don't know the format to load
328 return string("ppm");
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) {
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))
344 LYXERR(Debug::GRAPHICS, "Unable to convert from " << from
348 // Failed! so we have to try to convert it to PPM format
349 // with the standard converter
350 return string("ppm");
354 bool CacheItem::Impl::tryDisplayFormat(FileName & filename, string & from)
356 // First, check that the file exists!
358 if (!filename_.isReadableFile()) {
359 if (status_ != ErrorNoFile) {
360 status_ = ErrorNoFile;
361 LYXERR(Debug::GRAPHICS, "\tThe file is not readable");
366 zipped_ = formats.isZippedFile(filename_);
368 string tempname = unzippedFileName(filename_.toFilesystemEncoding());
369 string const ext = getExtension(tempname);
370 tempname = changeExtension(tempname, "") + "-XXXXXX";
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.");
381 filename = unzipFile(filename_, unzipped_filename_.toFilesystemEncoding());
383 filename = filename_;
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));
391 from = formats.getFormatFromFile(filename);
393 status_ = ErrorConverting;
394 LYXERR(Debug::GRAPHICS, "\tCould not determine file format.");
396 LYXERR(Debug::GRAPHICS, "\n\tThe file contains " << from << " format data.");
397 to_ = findTargetFormat(Cache::get().loadableFormats(), from);
400 // No conversion needed!
401 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (from == to)!");
402 file_to_load_ = filename;
403 status_ = loadImage() ? Loaded : ErrorLoading;
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;
417 void CacheItem::Impl::convertToDisplayFormat()
419 LYXERR(Debug::GRAPHICS, "\tConverting it to " << to_ << " format.");
421 // Make a local copy in case we unzip it
424 if (!tryDisplayFormat(filename, from)) {
425 // The image status has changed, tell it to the outside world.
430 // We will need a conversion, tell it to the outside world.
431 setStatus(Converting);
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;
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(),
445 converter_->connect(bind(&Impl::imageConverted, this, _1));
446 converter_->startConversion();
449 } // namespace graphics