]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Embedding: add embedding support for InsetInclude. (params["embed"] is added)
[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/$embDirName/file.lyx
52 $temp/$embDirName/figure1.png     for ./figure1.png)
53 $temp/$embDirName/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/$embDirName/$upDirName/figures/figure.png     for ../figures/figure.png
61 $temp/$embDirName/$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 Using a similar trick, we use $absDirName for absolute path so that
66 an absolute filename can be saved as
67
68 $temp/$embDirName/$absDirName/a/absolute/path for /a/absolute/path
69
70 */
71 const std::string embDirName = "LyX.Embedded.Files";
72 const std::string upDirName = "LyX.Embed.Dir.Up";
73 const std::string absDirName = "LyX.Embed.Dir.Abs";
74
75 namespace Alert = frontend::Alert;
76
77 EmbeddedFile::EmbeddedFile(string const & file, std::string const & buffer_path)
78         : DocFileName("", false), inzip_name_(""), embedded_(false), inset_list_(),
79         temp_path_("")
80 {
81         set(file, buffer_path);
82 }
83
84
85 void EmbeddedFile::set(std::string const & filename, std::string const & buffer_path)
86 {
87         DocFileName::set(filename, buffer_path);
88         if (filename.empty())
89                 return;
90
91         inzip_name_ = to_utf8(makeRelPath(from_utf8(absFilename()),
92                         from_utf8(buffer_path)));
93         
94         if (FileName(inzip_name_).isAbsolute())
95                 inzip_name_ = absDirName + '/' + inzip_name_;
96
97         // replace .. by upDirName
98         if (prefixIs(inzip_name_, "."))
99                 inzip_name_ = subst(inzip_name_, "..", upDirName);
100
101         // to avoid name conflict between $docu_path/file and $temp_path/file
102         // embedded files are in a subdirectory of $temp_path.
103         inzip_name_ = embDirName + '/' + inzip_name_;
104 }
105
106
107 string EmbeddedFile::embeddedFile() const
108 {
109         BOOST_ASSERT(enabled());
110         return temp_path_ + inzip_name_;
111 }
112
113
114 FileName EmbeddedFile::availableFile() const
115 {
116         if (enabled() && embedded())
117                 return FileName(embeddedFile());
118         else
119                 return *this;
120 }
121
122
123 string EmbeddedFile::latexFilename(std::string const & buffer_path) const
124 {
125         return (enabled() && embedded()) ? inzip_name_ : relFilename(buffer_path);
126 }
127
128
129 void EmbeddedFile::addInset(Inset const * inset)
130 {
131         if (inset != NULL)
132                 inset_list_.push_back(inset);
133 }
134
135
136 void EmbeddedFile::setEmbed(bool embed)
137 {
138         embedded_ = embed;
139 }
140
141
142 void EmbeddedFile::enable(bool flag, Buffer const * buf)
143 {
144         if (enabled() == flag)
145                 return;
146         
147         if (flag) {
148                 temp_path_ = buf->temppath();
149                 if (!suffixIs(temp_path_, '/'))
150                         temp_path_ += '/';
151                 if (embedded())
152                         updateFromExternalFile();
153         } else {
154                 extract();
155                 temp_path_ = "";
156         }
157 }
158
159
160 bool EmbeddedFile::extract() const
161 {
162         BOOST_ASSERT(enabled());
163
164         string ext_file = absFilename();
165         string emb_file = embeddedFile();
166
167         FileName emb(emb_file);
168         FileName ext(ext_file);
169
170         if (!emb.exists()) {
171                 if (ext.exists())
172                         return true;
173                 else
174                         throw ExceptionMessage(ErrorException, _("Failed to extract file"),
175                                 bformat(_("Cannot extract file '%1$s'.\n"
176                                 "Source file %2$s does not exist"),
177                                 from_utf8(outputFilename()), from_utf8(emb_file)));
178         }
179
180         // if external file already exists ...
181         if (ext.exists()) {
182                 // no need to copy if the files are the same
183                 if (checksum() == FileName(emb_file).checksum())
184                         return true;
185                 // otherwise, ask if overwrite
186                 int ret = Alert::prompt(
187                         _("Overwrite external file?"),
188                         bformat(_("External file %1$s already exists, do you want to overwrite it"),
189                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
190                 if (ret != 0)
191                         // if the user does not want to overwrite, we still consider it
192                         // a successful operation.
193                         return true;
194         }
195         // copy file
196
197         // need to make directory?
198         FileName path = ext.onlyPath();
199         if (!path.createPath()) {
200                 throw ExceptionMessage(ErrorException, _("Copy file failure"),
201                         bformat(_("Cannot create file path '%1$s'.\n"
202                         "Please check whether the path is writeable."),
203                         from_utf8(path.absFilename())));
204                 return false;
205         }
206
207         if (emb.copyTo(ext)) {
208                 LYXERR(Debug::FILES, "Extract file " << emb_file << " to " << ext_file << endl);
209                 return true;
210         }
211
212         throw ExceptionMessage(ErrorException, _("Copy file failure"),
213                  bformat(_("Cannot copy file %1$s to %2$s.\n"
214                                  "Please check whether the directory exists and is writeable."),
215                                 from_utf8(emb_file), from_utf8(ext_file)));
216         return false;
217 }
218
219
220 bool EmbeddedFile::updateFromExternalFile() const
221 {
222         BOOST_ASSERT(enabled());
223
224         string ext_file = absFilename();
225         string emb_file = embeddedFile();
226
227         FileName emb(emb_file);
228         FileName ext(ext_file);
229
230         if (!ext.exists()) {
231                 // no need to update
232                 if (emb.exists())
233                         return true;
234                 // no external and internal file
235                 throw ExceptionMessage(ErrorException,
236                         _("Failed to embed file"),
237                         bformat(_("Failed to embed file %1$s.\n"
238                            "Please check whether this file exists and is readable."),
239                                 from_utf8(ext_file)));
240         }
241
242         // if embedded file already exists ...
243         if (emb.exists()) {
244                 // no need to copy if the files are the same
245                 if (checksum() == FileName(emb_file).checksum())
246                         return true;
247                 // other wise, ask if overwrite
248                 int const ret = Alert::prompt(
249                         _("Update embedded file?"),
250                         bformat(_("Embedded file %1$s already exists, do you want to overwrite it"),
251                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
252                 if (ret != 0)
253                         // if the user does not want to overwrite, we still consider it
254                         // a successful operation.
255                         return true;
256         }
257         // copy file
258         // need to make directory?
259         FileName path = emb.onlyPath();
260         if (!path.isDirectory())
261                 path.createPath();
262         if (ext.copyTo(emb))
263                 return true;
264         throw ExceptionMessage(ErrorException,
265                 _("Copy file failure"),
266                 bformat(_("Cannot copy file %1$s to %2$s.\n"
267                            "Please check whether the directory exists and is writeable."),
268                                 from_utf8(ext_file), from_utf8(emb_file)));
269         //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
270         return false;
271 }
272
273
274 void EmbeddedFile::updateInsets(Buffer const * buf) const
275 {
276         vector<Inset const *>::const_iterator it = inset_list_.begin();
277         vector<Inset const *>::const_iterator it_end = inset_list_.end();
278         for (; it != it_end; ++it)
279                 const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
280 }
281
282
283 bool EmbeddedFile::isReadableFile() const
284 {
285         return availableFile().isReadableFile();
286 }
287
288
289 unsigned long EmbeddedFile::checksum() const
290 {
291         return availableFile().checksum();
292 }
293
294
295 bool operator==(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
296 {
297         return lhs.absFilename() == rhs.absFilename()
298                 && lhs.saveAbsPath() == rhs.saveAbsPath()
299                 && lhs.embedded() == rhs.embedded();
300 }
301
302
303 bool operator!=(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
304 {
305         return !(lhs == rhs);
306 }
307
308
309 bool EmbeddedFiles::enabled() const
310 {
311         return buffer_->params().embedded;
312 }
313
314
315 void EmbeddedFiles::enable(bool flag)
316 {
317         if (enabled() == flag)
318                 return;
319         
320         // update embedded file list
321         update();
322         
323         int count_embedded = 0;
324         int count_external = 0;
325         EmbeddedFileList::iterator it = file_list_.begin();
326         EmbeddedFileList::iterator it_end = file_list_.end();
327         // an exception may be thrown
328         for (; it != it_end; ++it) {
329                 it->enable(flag, buffer_);
330                 if (it->embedded())
331                         count_embedded ++;
332                 else
333                         count_external ++;
334         }
335         // if operation is successful (no exception is thrown)
336         buffer_->markDirty();
337         buffer_->params().embedded = flag;
338
339         // if the operation is successful, update insets
340         for (it = file_list_.begin(); it != it_end; ++it)
341                 it->updateInsets(buffer_);
342         
343         // show result
344         if (flag) {
345                 docstring const msg = bformat(_("%1$d external files are ignored.\n"
346                         "%2$d embeddable files are embedded.\n"), count_external, count_embedded);
347                 Alert::information(_("Packing all files"), msg);
348         } else {
349                 docstring const msg = bformat(_("%1$d external files are ignored.\n"
350                         "%2$d embedded files are extracted.\n"), count_external, count_embedded);
351                 Alert::information(_("Unpacking all files"), msg);
352         }
353 }
354
355
356 void EmbeddedFiles::registerFile(EmbeddedFile const & file, Inset const * inset)
357 {
358         BOOST_ASSERT(!enabled() || file.availableFile().exists());
359         BOOST_ASSERT(!enabled() || file.enabled());
360
361         // try to find this file from the list
362         EmbeddedFileList::iterator it = file_list_.begin();
363         EmbeddedFileList::iterator it_end = file_list_.end();
364         for (; it != it_end; ++it)
365                 if (it->absFilename() == file.absFilename()) {
366                         if (it->embedded() != file.embedded()) {
367                                 Alert::error(_("Wrong embedding status."),
368                                         bformat(_("File %1$s is included in more than one insets, "
369                                                 "but with different embedding status. Assuming embedding status."),
370                                                 from_utf8(it->outputFilename())));
371                                 it->setEmbed(true);
372                                 // update the inset with this embedding status.
373                                 const_cast<Inset*>(inset)->updateEmbeddedFile(*buffer_, *it);
374                         }
375                         it->addInset(inset);
376                         return;
377                 }
378         //
379         file_list_.push_back(file);
380         file_list_.back().addInset(inset);
381 }
382
383
384 void EmbeddedFiles::update()
385 {
386         file_list_.clear();
387
388         for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
389                 it->registerEmbeddedFiles(*buffer_, *this);
390 }
391
392
393 bool EmbeddedFiles::writeFile(DocFileName const & filename)
394 {
395         // file in the temporary path has the content
396         string const content = FileName(addName(buffer_->temppath(),
397                 "content.lyx")).toFilesystemEncoding();
398
399         vector<pair<string, string> > filenames;
400         // add content.lyx to filenames
401         filenames.push_back(make_pair(content, "content.lyx"));
402         // prepare list of embedded file
403         update();
404         EmbeddedFileList::iterator it = file_list_.begin();
405         EmbeddedFileList::iterator it_end = file_list_.end();
406         for (; it != it_end; ++it) {
407                 if (it->embedded()) {
408                         string file = it->embeddedFile();
409                         if (!FileName(file).exists())
410                                 throw ExceptionMessage(ErrorException, _("Failed to write file"),
411                                         bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"),
412                                                 it->displayName()));
413                         filenames.push_back(make_pair(file, it->inzipName()));
414                         LYXERR(Debug::FILES, "Writing file " << it->outputFilename()
415                                 << " as " << it->inzipName() << endl);
416                 }
417         }
418         // write a zip file with all these files. Write to a temp file first, to
419         // avoid messing up the original file in case something goes terribly wrong.
420         DocFileName zipfile(addName(buffer_->temppath(),
421                 onlyFilename(changeExtension(
422                         filename.toFilesystemEncoding(), ".zip"))));
423
424         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
425         // copy file back
426         if (!zipfile.copyTo(filename)) {
427                 Alert::error(_("Save failure"),
428                                  bformat(_("Cannot create file %1$s.\n"
429                                            "Please check whether the directory exists and is writeable."),
430                                          from_utf8(filename.absFilename())));
431                 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
432         }
433         return true;
434 }
435
436 }