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"
17 #include "GraphicsCache.h"
18 #include "GraphicsConverter.h"
19 #include "GraphicsImage.h"
21 #include "ConverterCache.h"
24 #include "support/debug.h"
25 #include "support/FileName.h"
26 #include "support/filetools.h"
27 #include "support/FileMonitor.h"
29 #include <boost/bind.hpp>
32 using namespace lyx::support
;
38 class CacheItem::Impl
: public boost::signals::trackable
{
42 Impl(FileName
const & file
);
45 * If no file conversion is needed, then tryDisplayFormat() calls
46 * loadImage() directly.
47 * \return true if a conversion is necessary and no error occurred.
49 bool tryDisplayFormat(FileName
& filename
, string
& from
);
51 /** Start the image conversion process, checking first that it is
52 * necessary. If it is necessary, then a conversion task is started.
53 * CacheItem asumes that the conversion is asynchronous and so
54 * passes a Signal to the converting routine. When the conversion
55 * is finished, this Signal is emitted, returning the converted
56 * file to this->imageConverted.
58 * convertToDisplayFormat() will set the loading status flag as
59 * approriate through calls to setStatus().
61 void convertToDisplayFormat();
63 /** Load the image into memory. This is called either from
64 * convertToDisplayFormat() direct or from imageConverted().
68 /** Get a notification when the image conversion is done.
69 * Connected to a signal on_finish_ which is passed to
72 void imageConverted(bool);
74 /** Sets the status of the loading process. Also notifies
75 * listeners that the status has changed.
77 void setStatus(ImageStatus new_status
);
79 /** Can be invoked directly by the user, but is also connected to the
80 * FileMonitor and so is invoked when the file is changed
81 * (if monitoring is taking place).
85 /** If we are asked to load the file for a second or further time,
86 * (because the file has changed), then we'll have to first reset
87 * many of the variables below.
91 /// The filename we refer too.
92 FileName
const filename_
;
94 FileMonitor
const monitor_
;
96 /// Is the file compressed?
98 /// If so, store the uncompressed file in this temporary file.
99 FileName unzipped_filename_
;
100 /// The target format
102 /// What file are we trying to load?
103 FileName file_to_load_
;
104 /** Should we delete the file after loading? True if the file is
105 * the result of a conversion process.
107 bool remove_loaded_file_
;
109 /// The image and its loading status.
110 boost::shared_ptr
<Image
> image_
;
114 /// This signal is emitted when the image loading status changes.
115 boost::signal
<void()> statusChanged
;
117 /// The connection of the signal ConvProcess::finishedConversion,
118 boost::signals::connection cc_
;
121 boost::scoped_ptr
<Converter
> converter_
;
125 CacheItem::CacheItem(FileName
const & file
)
126 : pimpl_(new Impl(file
))
130 CacheItem::~CacheItem()
136 FileName
const & CacheItem::filename() const
138 return pimpl_
->filename_
;
142 bool CacheItem::tryDisplayFormat() const
144 if (pimpl_
->status_
!= WaitingToLoad
)
148 bool const conversion_needed
= pimpl_
->tryDisplayFormat(filename
, from
);
149 bool const success
= status() == Loaded
&& !conversion_needed
;
156 void CacheItem::startLoading() const
158 pimpl_
->startLoading();
162 void CacheItem::startMonitoring() const
164 if (!pimpl_
->monitor_
.monitoring())
165 pimpl_
->monitor_
.start();
169 bool CacheItem::monitoring() const
171 return pimpl_
->monitor_
.monitoring();
175 unsigned long CacheItem::checksum() const
177 return pimpl_
->monitor_
.checksum();
181 Image
const * CacheItem::image() const
183 return pimpl_
->image_
.get();
187 ImageStatus
CacheItem::status() const
189 return pimpl_
->status_
;
193 boost::signals::connection
CacheItem::connect(slot_type
const & slot
) const
195 return pimpl_
->statusChanged
.connect(slot
);
199 //------------------------------
200 // Implementation details follow
201 //------------------------------
204 CacheItem::Impl::Impl(FileName
const & file
)
206 monitor_(file
, 2000),
208 remove_loaded_file_(false),
209 status_(WaitingToLoad
)
211 monitor_
.connect(boost::bind(&Impl::startLoading
, this));
215 void CacheItem::Impl::startLoading()
217 if (status_
!= WaitingToLoad
)
220 convertToDisplayFormat();
224 void CacheItem::Impl::reset()
227 if (!unzipped_filename_
.empty())
228 unzipped_filename_
.removeFile();
229 unzipped_filename_
.erase();
231 if (remove_loaded_file_
&& !file_to_load_
.empty())
232 file_to_load_
.removeFile();
233 remove_loaded_file_
= false;
234 file_to_load_
.erase();
240 status_
= WaitingToLoad
;
245 if (converter_
.get())
250 void CacheItem::Impl::setStatus(ImageStatus new_status
)
252 if (status_
== new_status
)
255 status_
= new_status
;
260 void CacheItem::Impl::imageConverted(bool success
)
262 string
const text
= success
? "succeeded" : "failed";
263 LYXERR(Debug::GRAPHICS
, "Image conversion " << text
<< '.');
265 file_to_load_
= converter_
.get() ?
266 FileName(converter_
->convertedFile()) : FileName();
270 success
= !file_to_load_
.empty() && file_to_load_
.isReadableFile();
273 LYXERR(Debug::GRAPHICS
, "Unable to find converted file!");
274 setStatus(ErrorConverting
);
277 unzipped_filename_
.removeFile();
282 // Add the converted file to the file cache
283 ConverterCache::get().add(filename_
, to_
, file_to_load_
);
285 setStatus(loadImage() ? Loaded
: ErrorLoading
);
289 // This function gets called from the callback after the image has been
290 // converted successfully.
291 bool CacheItem::Impl::loadImage()
293 LYXERR(Debug::GRAPHICS
, "Loading image.");
295 image_
.reset(newImage());
297 bool success
= image_
->load(file_to_load_
);
298 string
const text
= success
? "succeeded" : "failed";
299 LYXERR(Debug::GRAPHICS
, "Image loading " << text
<< '.');
301 // Clean up after loading.
303 unzipped_filename_
.removeFile();
305 if (remove_loaded_file_
&& unzipped_filename_
!= file_to_load_
)
306 file_to_load_
.removeFile();
312 static string
const findTargetFormat(string
const & from
)
314 typedef vector
<string
> FormatList
;
315 FormatList
const & formats
= Cache::get().loadableFormats();
317 // There must be a format to load from.
318 LASSERT(!formats
.empty(), /**/);
320 // Use the standard converter if we don't know the format to load
323 return string("ppm");
325 // First ascertain if we can load directly with no conversion
326 FormatList::const_iterator it
= formats
.begin();
327 FormatList::const_iterator end
= formats
.end();
328 for (; it
!= end
; ++it
) {
333 // So, we have to convert to a loadable format. Can we?
334 it
= formats
.begin();
335 for (; it
!= end
; ++it
) {
336 if (lyx::graphics::Converter::isReachable(from
, *it
))
339 LYXERR(Debug::GRAPHICS
, "Unable to convert from " << from
343 // Failed! so we have to try to convert it to PPM format
344 // with the standard converter
345 return string("ppm");
349 bool CacheItem::Impl::tryDisplayFormat(FileName
& filename
, string
& from
)
351 // First, check that the file exists!
353 filename_
.lastModified();
354 if (!filename_
.isReadableFile()) {
355 if (status_
!= ErrorNoFile
) {
356 status_
= ErrorNoFile
;
357 LYXERR(Debug::GRAPHICS
, "\tThe file is not readable");
362 zipped_
= filename_
.isZippedFile();
364 unzipped_filename_
= FileName::tempName(
365 filename_
.toFilesystemEncoding());
366 if (unzipped_filename_
.empty()) {
367 status_
= ErrorConverting
;
368 LYXERR(Debug::GRAPHICS
, "\tCould not create temporary file.");
371 filename
= unzipFile(filename_
, unzipped_filename_
.toFilesystemEncoding());
373 filename
= filename_
;
376 docstring
const displayed_filename
= makeDisplayPath(filename_
.absFilename());
377 LYXERR(Debug::GRAPHICS
, "[CacheItem::Impl::convertToDisplayFormat]\n"
378 << "\tAttempting to convert image file: " << filename
379 << "\n\twith displayed filename: " << to_utf8(displayed_filename
));
381 from
= formats
.getFormatFromFile(filename
);
383 status_
= ErrorConverting
;
384 LYXERR(Debug::GRAPHICS
, "\tCould not determine file format.");
386 LYXERR(Debug::GRAPHICS
, "\n\tThe file contains " << from
<< " format data.");
387 to_
= findTargetFormat(from
);
390 // No conversion needed!
391 LYXERR(Debug::GRAPHICS
, "\tNo conversion needed (from == to)!");
392 file_to_load_
= filename
;
393 status_
= loadImage() ? Loaded
: ErrorLoading
;
397 if (ConverterCache::get().inCache(filename
, to_
)) {
398 LYXERR(Debug::GRAPHICS
, "\tNo conversion needed (file in file cache)!");
399 file_to_load_
= ConverterCache::get().cacheName(filename
, to_
);
400 status_
= loadImage() ? Loaded
: ErrorLoading
;
407 void CacheItem::Impl::convertToDisplayFormat()
409 LYXERR(Debug::GRAPHICS
, "\tConverting it to " << to_
<< " format.");
411 // Make a local copy in case we unzip it
414 if (!tryDisplayFormat(filename
, from
)) {
415 // The image status has changed, tell it to the outside world.
420 // We will need a conversion, tell it to the outside world.
421 setStatus(Converting
);
423 // Add some stuff to create a uniquely named temporary file.
424 // This file is deleted in loadImage after it is loaded into memory.
425 FileName
const to_file_base
= FileName::tempName("CacheItem");
426 remove_loaded_file_
= true;
428 // Connect a signal to this->imageConverted and pass this signal to
429 // the graphics converter so that we can load the modified file
430 // on completion of the conversion process.
431 converter_
.reset(new Converter(filename
, to_file_base
.absFilename(), from
, to_
));
432 converter_
->connect(boost::bind(&Impl::imageConverted
, this, _1
));
433 converter_
->startConversion();
436 } // namespace graphics