]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Embedding: adjust how InsetGraphics interacts with EmbeddedFiles, the ?th overhaul...
[lyx.git] / src / EmbeddedFiles.cpp
1 // -*- C++ -*-
2 /**
3  * \file EmbeddedFiles.cpp
4  * This file is part of LyX, the document processor.
5  * Licence details can be found in the file COPYING.
6  *
7  * \author Bo Peng
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  */
12
13 #include <config.h>
14
15 #include "EmbeddedFiles.h"
16
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "ErrorList.h"
20 #include "Format.h"
21 #include "InsetIterator.h"
22 #include "Lexer.h"
23 #include "LyX.h"
24 #include "Paragraph.h"
25 #include "Session.h"
26
27 #include "frontends/alert.h"
28
29 #include "support/debug.h"
30 #include "support/filetools.h"
31 #include "support/gettext.h"
32 #include "support/convert.h"
33 #include "support/lstrings.h"
34 #include "support/ExceptionMessage.h"
35 #include "support/FileZipListDir.h"
36
37 #include <sstream>
38 #include <fstream>
39 #include <utility>
40
41 using namespace std;
42 using namespace lyx::support;
43
44 namespace lyx {
45
46 /** Dir name used for ".." in the bundled file.
47
48 Under the lyx temp directory, content.lyx and its embedded files are usually
49 saved as
50
51 $temp/file.lyx
52 $temp/figure1.png     for ./figure1.png)
53 $temp/sub/figure2.png for ./sub/figure2.png)
54
55 This works fine for embedded files that are in the current or deeper directory
56 of the document directory, but not for files such as ../figures/figure.png.
57 A unique name $upDirName is chosen to represent .. in such filenames so that
58 'up' directories can be stored 'down' the directory tree:
59
60 $temp/$upDirName/figures/figure.png     for ../figures/figure.png
61 $temp/$upDirName/$upDirName/figure.png  for ../../figure.png
62
63 This name has to be fixed because it is used in lyx bundled .zip file.
64
65 Note that absolute files are not embeddable because there is no easy
66 way to put them under $temp.
67 */
68 const std::string upDirName = "LyX.Embed.Dir.Up";
69
70 namespace Alert = frontend::Alert;
71
72 EmbeddedFile::EmbeddedFile(string const & file, std::string const & buffer_path)
73         : DocFileName("", false), inzip_name_(""), embedded_(false), inset_list_()
74 {
75         set(file, buffer_path);
76 }
77
78
79 void EmbeddedFile::set(std::string const & filename, std::string const & buffer_path)
80 {
81         DocFileName::set(filename, buffer_path);
82         if (filename.empty())
83                 return;
84
85         inzip_name_ = to_utf8(makeRelPath(from_utf8(absFilename()),
86                         from_utf8(buffer_path)));
87         // if inzip_name_ is an absolute path, this file is not embeddable
88         if (FileName(inzip_name_).isAbsolute())
89                 inzip_name_ = "";
90         // replace .. by upDirName
91         if (prefixIs(inzip_name_, "."))
92                 inzip_name_ = subst(inzip_name_, "..", upDirName);
93         LYXERR(Debug::FILES, "Create embedded file " << filename <<
94                 " with in zip name " << inzip_name_ << endl);
95 }
96
97
98 string EmbeddedFile::embeddedFile(Buffer const * buf) const
99 {
100         BOOST_ASSERT(embeddable());
101         string temp = buf->temppath();
102         if (!suffixIs(temp, '/'))
103                 temp += '/';
104         return temp + inzip_name_;
105 }
106
107
108 string EmbeddedFile::availableFile(Buffer const * buf) const
109 {
110         return embedded() ? embeddedFile(buf) : absFilename();
111 }
112
113
114 void EmbeddedFile::addInset(Inset const * inset)
115 {
116         if (inset != NULL)
117                 inset_list_.push_back(inset);
118 }
119
120
121 Inset const * EmbeddedFile::inset(int idx) const
122 {
123         BOOST_ASSERT(idx < refCount());
124         // some embedded file do not have a valid par iterator
125         return inset_list_[idx];
126 }
127
128
129 void EmbeddedFile::setEmbed(bool embed)
130 {
131         if (!embeddable() && embed) {
132                 Alert::error(_("Embedding failed."), bformat(
133                         _("Cannot embed file %1$s because its path is not relative to document path."),
134                         from_utf8(absFilename())));
135                 return;
136         }
137         embedded_ = embed;
138 }
139
140
141 bool EmbeddedFile::extract(Buffer const * buf) const
142 {
143         BOOST_ASSERT(embeddable());
144
145         string ext_file = absFilename();
146         string emb_file = embeddedFile(buf);
147
148         FileName emb(emb_file);
149         FileName ext(ext_file);
150
151         if (!emb.exists())
152                 throw ExceptionMessage(ErrorException, _("Failed to extract file"),
153                         bformat(_("Cannot extract file '%1$s'.\n"
154                         "Source file %2$s does not exist"),
155                         from_utf8(outputFilename()), from_utf8(emb_file)));
156
157         // if external file already exists ...
158         if (ext.exists()) {
159                 // no need to copy if the files are the same
160                 if (checksum() == FileName(emb_file).checksum())
161                         return true;
162                 // otherwise, ask if overwrite
163                 int ret = Alert::prompt(
164                         _("Overwrite external file?"),
165                         bformat(_("External file %1$s already exists, do you want to overwrite it"),
166                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
167                 if (ret != 0)
168                         // if the user does not want to overwrite, we still consider it
169                         // a successful operation.
170                         return true;
171         }
172         // copy file
173
174         // need to make directory?
175         FileName path = ext.onlyPath();
176         if (!path.createPath()) {
177                 throw ExceptionMessage(ErrorException, _("Copy file failure"),
178                         bformat(_("Cannot create file path '%1$s'.\n"
179                         "Please check whether the path is writeable."),
180                         from_utf8(path.absFilename())));
181                 return false;
182         }
183
184         if (emb.copyTo(ext)) {
185                 LYXERR(Debug::FILES, "Extract file " << emb_file << " to " << ext_file << endl);
186                 return true;
187         }
188
189         throw ExceptionMessage(ErrorException, _("Copy file failure"),
190                  bformat(_("Cannot copy file %1$s to %2$s.\n"
191                                  "Please check whether the directory exists and is writeable."),
192                                 from_utf8(emb_file), from_utf8(ext_file)));
193         return false;
194 }
195
196
197 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
198 {
199         BOOST_ASSERT(embeddable());
200
201         string ext_file = absFilename();
202         string emb_file = embeddedFile(buf);
203
204         FileName emb(emb_file);
205         FileName ext(ext_file);
206
207         if (!ext.exists()) {
208                 // no need to update
209                 if (emb.exists())
210                         return true;
211                 // no external and internal file
212                 throw ExceptionMessage(ErrorException,
213                         _("Failed to embed file"),
214                         bformat(_("Failed to embed file %1$s.\n"
215                            "Please check whether this file exists and is readable."),
216                                 from_utf8(ext_file)));
217         }
218
219         // if embedded file already exists ...
220         if (emb.exists()) {
221                 // no need to copy if the files are the same
222                 if (checksum() == FileName(emb_file).checksum())
223                         return true;
224                 // other wise, ask if overwrite
225                 int const ret = Alert::prompt(
226                         _("Update embedded file?"),
227                         bformat(_("Embedded file %1$s already exists, do you want to overwrite it"),
228                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
229                 if (ret != 0)
230                         // if the user does not want to overwrite, we still consider it
231                         // a successful operation.
232                         return true;
233         }
234         // copy file
235         // need to make directory?
236         FileName path = emb.onlyPath();
237         if (!path.isDirectory())
238                 path.createPath();
239         if (ext.copyTo(emb))
240                 return true;
241         throw ExceptionMessage(ErrorException,
242                 _("Copy file failure"),
243                 bformat(_("Cannot copy file %1$s to %2$s.\n"
244                            "Please check whether the directory exists and is writeable."),
245                                 from_utf8(ext_file), from_utf8(emb_file)));
246         //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
247         return false;
248 }
249
250
251 void EmbeddedFile::updateInsets(Buffer const * buf) const
252 {
253         vector<Inset const *>::const_iterator it = inset_list_.begin();
254         vector<Inset const *>::const_iterator it_end = inset_list_.end();
255         for (; it != it_end; ++it)
256                 const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
257 }
258
259
260 bool operator==(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
261 {
262         return lhs.absFilename() == rhs.absFilename()
263                 && lhs.saveAbsPath() == rhs.saveAbsPath()
264                 && lhs.embedded() == rhs.embedded();
265 }
266
267
268 bool operator!=(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
269 {
270         return !(lhs == rhs);
271 }
272
273
274 bool EmbeddedFiles::enabled() const
275 {
276         return buffer_->params().embedded;
277 }
278
279
280 void EmbeddedFiles::enable(bool flag)
281 {
282         if (enabled() != flag) {
283                 // update embedded file list
284                 update();
285                 // An exception may be thrown.
286                 if (flag)
287                         // if enable, copy all files to temppath()
288                         updateFromExternalFile();
289                 else
290                         // if disable, extract all files
291                         extractAll();
292                 // if operation is successful (no exception is thrown)
293                 buffer_->markDirty();
294                 buffer_->params().embedded = flag;
295                 if (flag)
296                         updateInsets();
297         }
298 }
299
300
301 EmbeddedFile & EmbeddedFiles::registerFile(EmbeddedFile const & file, Inset const * inset)
302 {
303         // try to find this file from the list
304         EmbeddedFileList::iterator it = file_list_.begin();
305         EmbeddedFileList::iterator it_end = file_list_.end();
306         for (; it != it_end; ++it)
307                 if (it->absFilename() == file.absFilename()) {
308                         if (it->embedded() != file.embedded()) {
309                                 Alert::error(_("Wrong embedding status."),
310                                         bformat(_("File %1$s is included in more than one insets, "
311                                                 "but with different embedding status. Assuming embedding status."),
312                                                 from_utf8(it->outputFilename())));
313                                 it->setEmbed(true);
314                         }
315                         it->addInset(inset);
316                         return *it;
317                 }
318         //
319         file_list_.push_back(file);
320         file_list_.back().addInset(inset);
321         return file_list_.back();
322 }
323
324
325 void EmbeddedFiles::update()
326 {
327         file_list_.clear();
328
329         for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
330                 it->registerEmbeddedFiles(*buffer_, *this);
331 }
332
333
334 bool EmbeddedFiles::writeFile(DocFileName const & filename)
335 {
336         // file in the temporary path has the content
337         string const content = FileName(addName(buffer_->temppath(),
338                 "content.lyx")).toFilesystemEncoding();
339
340         vector<pair<string, string> > filenames;
341         // add content.lyx to filenames
342         filenames.push_back(make_pair(content, "content.lyx"));
343         // prepare list of embedded file
344         EmbeddedFileList::iterator it = file_list_.begin();
345         EmbeddedFileList::iterator it_end = file_list_.end();
346         for (; it != it_end; ++it) {
347                 if (it->embedded()) {
348                         string file = it->embeddedFile(buffer_);
349                         if (!FileName(file).exists())
350                                 throw ExceptionMessage(ErrorException, _("Failed to write file"),
351                                         bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"),
352                                                 it->displayName()));
353                         filenames.push_back(make_pair(file, it->inzipName()));
354                         LYXERR(Debug::FILES, "Writing file " << it->outputFilename()
355                                 << " as " << it->inzipName() << endl);
356                 }
357         }
358         // write a zip file with all these files. Write to a temp file first, to
359         // avoid messing up the original file in case something goes terribly wrong.
360         DocFileName zipfile(addName(buffer_->temppath(),
361                 onlyFilename(changeExtension(
362                         filename.toFilesystemEncoding(), ".zip"))));
363
364         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
365         // copy file back
366         if (!zipfile.copyTo(filename)) {
367                 Alert::error(_("Save failure"),
368                                  bformat(_("Cannot create file %1$s.\n"
369                                            "Please check whether the directory exists and is writeable."),
370                                          from_utf8(filename.absFilename())));
371                 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
372         }
373         return true;
374 }
375
376
377 EmbeddedFiles::EmbeddedFileList::const_iterator
378 EmbeddedFiles::find(string filename) const
379 {
380         EmbeddedFileList::const_iterator it = file_list_.begin();
381         EmbeddedFileList::const_iterator it_end = file_list_.end();
382         for (; it != it_end; ++it)
383                 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)
384                         return it;
385         return file_list_.end();
386 }
387
388
389 bool EmbeddedFiles::extractAll() const
390 {
391         EmbeddedFileList::const_iterator it = file_list_.begin();
392         EmbeddedFileList::const_iterator it_end = file_list_.end();
393         int count_extracted = 0;
394         int count_external = 0;
395         for (; it != it_end; ++it)
396                 if (it->embedded()) {
397                         if(!it->extract(buffer_)) {
398                                 throw ExceptionMessage(ErrorException,
399                                         _("Failed to extract file"),
400                                         bformat(_("Error: can not extract file %1$s.\n"), it->displayName()));
401                         } else
402                                 count_extracted += 1;
403                 } else
404                         count_external += 1;
405         docstring const msg = bformat(_("%1$d external or non-embeddable files are ignored.\n"
406                 "%2$d embedded files are extracted.\n"), count_external, count_extracted);
407         Alert::information(_("Unpacking all files"), msg);
408         return true;
409 }
410
411
412 bool EmbeddedFiles::updateFromExternalFile() const
413 {
414         EmbeddedFileList::const_iterator it = file_list_.begin();
415         EmbeddedFileList::const_iterator it_end = file_list_.end();
416         int count_embedded = 0;
417         int count_external = 0;
418         for (; it != it_end; ++it)
419                 if (it->embedded()) {
420                         if (!it->updateFromExternalFile(buffer_)) {
421                                 throw ExceptionMessage(ErrorException,
422                                         _("Failed to embed file"),
423                                         bformat(_("Error: can not embed file %1$s.\n"), it->displayName()));
424                                 return false;
425                         } else
426                                 count_embedded += 1;
427                 } else
428                         count_external += 1;
429         docstring const msg = bformat(_("%1$d external or non-embeddable files are ignored.\n"
430                 "%2$d embeddable files are embedded.\n"), count_external, count_embedded);
431         Alert::information(_("Packing all files"), msg);
432         return true;
433 }
434
435
436 void EmbeddedFiles::updateInsets() const
437 {
438         EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
439         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
440         for (; it != it_end; ++it)
441                 if (it->refCount() > 0)
442                         it->updateInsets(buffer_);
443 }
444
445 }