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