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);
51 * If no file conversion is needed, then tryDisplayFormat() calls
52 * loadImage() directly.
53 * \return true if a conversion is necessary and no error occurred.
55 bool tryDisplayFormat(FileName & filename, string & from);
57 /** Start the image conversion process, checking first that it is
58 * necessary. If it is necessary, then a conversion task is started.
59 * CacheItem asumes that the conversion is asynchronous and so
60 * passes a Signal to the converting routine. When the conversion
61 * is finished, this Signal is emitted, returning the converted
62 * file to this->imageConverted.
64 * convertToDisplayFormat() will set the loading status flag as
65 * approriate through calls to setStatus().
67 void convertToDisplayFormat();
69 /** Load the image into memory. This is called either from
70 * convertToDisplayFormat() direct or from imageConverted().
74 /** Get a notification when the image conversion is done.
75 * Connected to a signal on_finish_ which is passed to
78 void imageConverted(bool);
80 /** Sets the status of the loading process. Also notifies
81 * listeners that the status has changed.
83 void setStatus(ImageStatus new_status);
85 /** Can be invoked directly by the user, but is also connected to the
86 * FileMonitor and so is invoked when the file is changed
87 * (if monitoring is taking place).
91 /** If we are asked to load the file for a second or further time,
92 * (because the file has changed), then we'll have to first reset
93 * many of the variables below.
97 /// The filename we refer too.
98 FileName const filename_;
99 /// The document filename this graphic item belongs to
100 FileName const & doc_file_;
102 FileMonitorPtr monitor_;
104 /// Is the file compressed?
106 /// If so, store the uncompressed file in this temporary file.
107 FileName unzipped_filename_;
108 /// The target format
110 /// What file are we trying to load?
111 FileName file_to_load_;
112 /** Should we delete the file after loading? True if the file is
113 * the result of a conversion process.
115 bool remove_loaded_file_;
117 /// The image and its loading status.
118 std::shared_ptr<Image> image_;
122 /// This signal is emitted when the image loading status changes.
123 boost::signals2::signal<void()> statusChanged;
125 /// The connection of the signal ConvProcess::finishedConversion,
126 boost::signals2::connection cc_;
129 unique_ptr<Converter> converter_;
133 CacheItem::CacheItem(FileName const & file, FileName const & doc_file)
134 : pimpl_(new Impl(file,doc_file))
138 CacheItem::~CacheItem()
144 FileName const & CacheItem::filename() const
146 return pimpl_->filename_;
150 bool CacheItem::tryDisplayFormat() const
152 if (pimpl_->status_ != WaitingToLoad)
156 bool const conversion_needed = pimpl_->tryDisplayFormat(filename, from);
157 bool const success = status() == Loaded && !conversion_needed;
164 void CacheItem::startLoading() const
166 pimpl_->startLoading();
170 void CacheItem::startMonitoring() const
172 pimpl_->startMonitor();
176 bool CacheItem::monitoring() const
178 return (bool)pimpl_->monitor_;
182 Image const * CacheItem::image() const
184 return pimpl_->image_.get();
188 ImageStatus CacheItem::status() const
190 return pimpl_->status_;
194 boost::signals2::connection CacheItem::connect(slot_type const & slot) const
196 return pimpl_->statusChanged.connect(slot);
200 //------------------------------
201 // Implementation details follow
202 //------------------------------
205 CacheItem::Impl::Impl(FileName const & file, FileName const & doc_file)
206 : filename_(file), doc_file_(doc_file),
208 remove_loaded_file_(false),
209 status_(WaitingToLoad)
213 void CacheItem::Impl::startMonitor()
217 monitor_ = FileSystemWatcher::monitor(filename_);
218 monitor_->connect([=](){ startLoading(); });
222 void CacheItem::Impl::startLoading()
224 if (status_ != WaitingToLoad)
227 convertToDisplayFormat();
231 void CacheItem::Impl::reset()
234 if (!unzipped_filename_.empty())
235 unzipped_filename_.removeFile();
236 unzipped_filename_.erase();
238 if (remove_loaded_file_ && !file_to_load_.empty())
239 file_to_load_.removeFile();
240 remove_loaded_file_ = false;
241 file_to_load_.erase();
247 status_ = WaitingToLoad;
257 void CacheItem::Impl::setStatus(ImageStatus new_status)
259 if (status_ == new_status)
262 status_ = new_status;
267 void CacheItem::Impl::imageConverted(bool success)
269 string const text = success ? "succeeded" : "failed";
270 LYXERR(Debug::GRAPHICS, "Image conversion " << text << '.');
272 file_to_load_ = converter_ ? FileName(converter_->convertedFile())
277 success = !file_to_load_.empty() && file_to_load_.isReadableFile();
280 LYXERR(Debug::GRAPHICS, "Unable to find converted file!");
281 setStatus(ErrorConverting);
284 unzipped_filename_.removeFile();
289 // Add the converted file to the file cache
290 ConverterCache::get().add(filename_, to_, file_to_load_);
292 setStatus(loadImage() ? Loaded : ErrorLoading);
296 // This function gets called from the callback after the image has been
297 // converted successfully.
298 bool CacheItem::Impl::loadImage()
300 LYXERR(Debug::GRAPHICS, "Loading image.");
302 image_.reset(newImage());
304 bool success = image_->load(file_to_load_);
305 string const text = success ? "succeeded" : "failed";
306 LYXERR(Debug::GRAPHICS, "Image loading " << text << '.');
308 // Clean up after loading.
310 unzipped_filename_.removeFile();
312 if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
313 file_to_load_.removeFile();
319 typedef vector<string> FormatList;
321 static string const findTargetFormat(FormatList const & format_list, string const & from)
323 // There must be a format to load from.
324 LASSERT(!theFormats().empty(), return string());
326 // Use the standard converter if we don't know the format to load
329 return string("ppm");
331 // First ascertain if we can load directly with no conversion
332 FormatList::const_iterator it = format_list.begin();
333 FormatList::const_iterator end = format_list.end();
334 for (; it != end; ++it) {
339 // So, we have to convert to a loadable format. Can we?
340 it = format_list.begin();
341 for (; it != end; ++it) {
342 if (lyx::graphics::Converter::isReachable(from, *it))
345 LYXERR(Debug::GRAPHICS, "Unable to convert from " << from
349 // Failed! so we have to try to convert it to PPM format
350 // with the standard converter
351 return string("ppm");
355 bool CacheItem::Impl::tryDisplayFormat(FileName & filename, string & from)
357 // First, check that the file exists!
359 if (!filename_.isReadableFile()) {
360 if (status_ != ErrorNoFile) {
361 status_ = ErrorNoFile;
362 LYXERR(Debug::GRAPHICS, "\tThe file is not readable");
367 zipped_ = theFormats().isZippedFile(filename_);
369 string tempname = unzippedFileName(filename_.toFilesystemEncoding());
370 string const ext = getExtension(tempname);
371 tempname = changeExtension(tempname, "") + "-XXXXXX";
373 tempname = addExtension(tempname, ext);
374 TempFile tempfile(tempname);
375 tempfile.setAutoRemove(false);
376 unzipped_filename_ = tempfile.name();
377 if (unzipped_filename_.empty()) {
378 status_ = ErrorConverting;
379 LYXERR(Debug::GRAPHICS, "\tCould not create temporary file.");
382 filename = unzipFile(filename_, unzipped_filename_.toFilesystemEncoding());
384 filename = filename_;
387 docstring const displayed_filename = makeDisplayPath(filename_.absFileName());
388 LYXERR(Debug::GRAPHICS, "[CacheItem::Impl::convertToDisplayFormat]\n"
389 << "\tAttempting to convert image file: " << filename
390 << "\n\twith displayed filename: " << to_utf8(displayed_filename));
392 from = theFormats().getFormatFromFile(filename);
394 status_ = ErrorConverting;
395 LYXERR(Debug::GRAPHICS, "\tCould not determine file format.");
397 LYXERR(Debug::GRAPHICS, "\n\tThe file contains " << from << " format data.");
398 to_ = findTargetFormat(Cache::get().loadableFormats(), from);
401 // No conversion needed!
402 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (from == to)!");
403 file_to_load_ = filename;
404 status_ = loadImage() ? Loaded : ErrorLoading;
408 if (ConverterCache::get().inCache(filename, to_)) {
409 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (file in file cache)!");
410 file_to_load_ = ConverterCache::get().cacheName(filename, to_);
411 status_ = loadImage() ? Loaded : ErrorLoading;
418 void CacheItem::Impl::convertToDisplayFormat()
420 LYXERR(Debug::GRAPHICS, "\tConverting it to " << to_ << " format.");
422 // Make a local copy in case we unzip it
425 if (!tryDisplayFormat(filename, from)) {
426 // The image status has changed, tell it to the outside world.
431 // We will need a conversion, tell it to the outside world.
432 setStatus(Converting);
434 // Add some stuff to create a uniquely named temporary file.
435 // This file is deleted in loadImage after it is loaded into memory.
436 TempFile tempfile("CacheItem");
437 tempfile.setAutoRemove(false);
438 FileName const to_file_base = tempfile.name();
439 remove_loaded_file_ = true;
441 // Connect a signal to this->imageConverted and pass this signal to
442 // the graphics converter so that we can load the modified file
443 // on completion of the conversion process.
444 converter_ = make_unique<Converter>(doc_file_, filename, to_file_base.absFileName(),
446 converter_->connect(bind(&Impl::imageConverted, this, _1));
447 converter_->startConversion();
450 } // namespace graphics