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 ActiveFileMonitorPtr 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 void CacheItem::checkModifiedAsync() const
184 if (!pimpl_->monitor_)
186 pimpl_->monitor_->checkModifiedAsync();
190 Image const * CacheItem::image() const
192 return pimpl_->image_.get();
196 ImageStatus CacheItem::status() const
198 return pimpl_->status_;
202 boost::signals2::connection CacheItem::connect(slot_type const & slot) const
204 return pimpl_->statusChanged.connect(slot);
208 //------------------------------
209 // Implementation details follow
210 //------------------------------
213 CacheItem::Impl::Impl(FileName const & file, FileName const & doc_file)
214 : filename_(file), doc_file_(doc_file),
216 remove_loaded_file_(false),
217 status_(WaitingToLoad)
221 void CacheItem::Impl::startMonitor()
225 monitor_ = FileSystemWatcher::activeMonitor(filename_);
226 monitor_->connect([=](){ startLoading(); });
230 void CacheItem::Impl::startLoading()
232 if (status_ != WaitingToLoad)
235 convertToDisplayFormat();
239 void CacheItem::Impl::reset()
242 if (!unzipped_filename_.empty())
243 unzipped_filename_.removeFile();
244 unzipped_filename_.erase();
246 if (remove_loaded_file_ && !file_to_load_.empty())
247 file_to_load_.removeFile();
248 remove_loaded_file_ = false;
249 file_to_load_.erase();
255 status_ = WaitingToLoad;
265 void CacheItem::Impl::setStatus(ImageStatus new_status)
267 if (status_ == new_status)
270 status_ = new_status;
275 void CacheItem::Impl::imageConverted(bool success)
277 string const text = success ? "succeeded" : "failed";
278 LYXERR(Debug::GRAPHICS, "Image conversion " << text << '.');
280 file_to_load_ = converter_ ? FileName(converter_->convertedFile())
285 success = !file_to_load_.empty() && file_to_load_.isReadableFile();
288 LYXERR(Debug::GRAPHICS, "Unable to find converted file!");
289 setStatus(ErrorConverting);
292 unzipped_filename_.removeFile();
297 // Add the converted file to the file cache
298 ConverterCache::get().add(filename_, to_, file_to_load_);
300 setStatus(loadImage() ? Loaded : ErrorLoading);
304 // This function gets called from the callback after the image has been
305 // converted successfully.
306 bool CacheItem::Impl::loadImage()
308 LYXERR(Debug::GRAPHICS, "Loading image.");
310 image_.reset(newImage());
312 bool success = image_->load(file_to_load_);
313 string const text = success ? "succeeded" : "failed";
314 LYXERR(Debug::GRAPHICS, "Image loading " << text << '.');
316 // Clean up after loading.
318 unzipped_filename_.removeFile();
320 if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
321 file_to_load_.removeFile();
327 typedef vector<string> FormatList;
329 static string const findTargetFormat(FormatList const & format_list, string const & from)
331 // There must be a format to load from.
332 LASSERT(!theFormats().empty(), return string());
334 // Use the standard converter if we don't know the format to load
337 return string("ppm");
339 // First ascertain if we can load directly with no conversion
340 FormatList::const_iterator it = format_list.begin();
341 FormatList::const_iterator end = format_list.end();
342 for (; it != end; ++it) {
347 // So, we have to convert to a loadable format. Can we?
348 it = format_list.begin();
349 for (; it != end; ++it) {
350 if (lyx::graphics::Converter::isReachable(from, *it))
353 LYXERR(Debug::GRAPHICS, "Unable to convert from " << from
357 // Failed! so we have to try to convert it to PPM format
358 // with the standard converter
359 return string("ppm");
363 bool CacheItem::Impl::tryDisplayFormat(FileName & filename, string & from)
365 // First, check that the file exists!
367 if (!filename_.isReadableFile()) {
368 if (status_ != ErrorNoFile) {
369 status_ = ErrorNoFile;
370 LYXERR(Debug::GRAPHICS, "\tThe file is not readable");
375 zipped_ = theFormats().isZippedFile(filename_);
377 string tempname = unzippedFileName(filename_.toFilesystemEncoding());
378 string const ext = getExtension(tempname);
379 tempname = changeExtension(tempname, "") + "-XXXXXX";
381 tempname = addExtension(tempname, ext);
382 TempFile tempfile(tempname);
383 tempfile.setAutoRemove(false);
384 unzipped_filename_ = tempfile.name();
385 if (unzipped_filename_.empty()) {
386 status_ = ErrorConverting;
387 LYXERR(Debug::GRAPHICS, "\tCould not create temporary file.");
390 filename = unzipFile(filename_, unzipped_filename_.toFilesystemEncoding());
392 filename = filename_;
395 docstring const displayed_filename = makeDisplayPath(filename_.absFileName());
396 LYXERR(Debug::GRAPHICS, "[CacheItem::Impl::convertToDisplayFormat]\n"
397 << "\tAttempting to convert image file: " << filename
398 << "\n\twith displayed filename: " << to_utf8(displayed_filename));
400 from = theFormats().getFormatFromFile(filename);
402 status_ = ErrorConverting;
403 LYXERR(Debug::GRAPHICS, "\tCould not determine file format.");
405 LYXERR(Debug::GRAPHICS, "\n\tThe file contains " << from << " format data.");
406 to_ = findTargetFormat(Cache::get().loadableFormats(), from);
409 // No conversion needed!
410 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (from == to)!");
411 file_to_load_ = filename;
412 status_ = loadImage() ? Loaded : ErrorLoading;
416 if (ConverterCache::get().inCache(filename, to_)) {
417 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (file in file cache)!");
418 file_to_load_ = ConverterCache::get().cacheName(filename, to_);
419 status_ = loadImage() ? Loaded : ErrorLoading;
426 void CacheItem::Impl::convertToDisplayFormat()
428 LYXERR(Debug::GRAPHICS, "\tConverting it to " << to_ << " format.");
430 // Make a local copy in case we unzip it
433 if (!tryDisplayFormat(filename, from)) {
434 // The image status has changed, tell it to the outside world.
439 // We will need a conversion, tell it to the outside world.
440 setStatus(Converting);
442 // Add some stuff to create a uniquely named temporary file.
443 // This file is deleted in loadImage after it is loaded into memory.
444 TempFile tempfile("CacheItem");
445 tempfile.setAutoRemove(false);
446 FileName const to_file_base = tempfile.name();
447 remove_loaded_file_ = true;
449 // Connect a signal to this->imageConverted and pass this signal to
450 // the graphics converter so that we can load the modified file
451 // on completion of the conversion process.
452 converter_ = make_unique<Converter>(doc_file_, filename, to_file_base.absFileName(),
454 converter_->connect(bind(&Impl::imageConverted, this, _1));
455 converter_->startConversion();
458 } // namespace graphics