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