3 * \file EmbeddedFiles.cpp
4 * This file is part of LyX, the document processor.
5 * Licence details can be found in the file COPYING.
9 * Full author contact details are available in file CREDITS.
15 #include "EmbeddedFiles.h"
18 #include "BufferParams.h"
19 #include "ErrorList.h"
21 #include "InsetIterator.h"
24 #include "Paragraph.h"
27 #include "frontends/alert.h"
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"
42 using namespace lyx::support;
46 /** Dir name used for ".." in the bundled file.
48 Under the lyx temp directory, content.lyx and its embedded files are usually
52 $temp/figure1.png for ./figure1.png)
53 $temp/sub/figure2.png for ./sub/figure2.png)
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:
60 $temp/$upDirName/figures/figure.png for ../figures/figure.png
61 $temp/$upDirName/$upDirName/figure.png for ../../figure.png
63 This name has to be fixed because it is used in lyx bundled .zip file.
65 Note that absolute files are not embeddable because there is no easy
66 way to put them under $temp.
68 const std::string upDirName = "LyX.Embed.Dir.Up";
70 namespace Alert = frontend::Alert;
72 EmbeddedFile::EmbeddedFile(string const & file, std::string const & buffer_path)
73 : DocFileName("", false), inzip_name_(""), embedded_(false), inset_list_()
75 set(file, buffer_path);
79 void EmbeddedFile::set(std::string const & filename, std::string const & buffer_path)
81 DocFileName::set(filename, buffer_path);
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())
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);
98 string EmbeddedFile::embeddedFile(Buffer const * buf) const
100 BOOST_ASSERT(embeddable());
101 string temp = buf->temppath();
102 if (!suffixIs(temp, '/'))
104 return temp + inzip_name_;
108 string EmbeddedFile::availableFile(Buffer const * buf) const
110 return embedded() ? embeddedFile(buf) : absFilename();
114 void EmbeddedFile::addInset(Inset const * inset)
117 inset_list_.push_back(inset);
121 Inset const * EmbeddedFile::inset(int idx) const
123 BOOST_ASSERT(idx < refCount());
124 // some embedded file do not have a valid par iterator
125 return inset_list_[idx];
129 void EmbeddedFile::setEmbed(bool embed)
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())));
141 bool EmbeddedFile::extract(Buffer const * buf) const
143 BOOST_ASSERT(embeddable());
145 string ext_file = absFilename();
146 string emb_file = embeddedFile(buf);
148 FileName emb(emb_file);
149 FileName ext(ext_file);
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)));
157 // if external file already exists ...
159 // no need to copy if the files are the same
160 if (checksum() == FileName(emb_file).checksum())
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"));
168 // if the user does not want to overwrite, we still consider it
169 // a successful operation.
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())));
184 if (emb.copyTo(ext)) {
185 LYXERR(Debug::FILES, "Extract file " << emb_file << " to " << ext_file << endl);
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)));
197 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
199 BOOST_ASSERT(embeddable());
201 string ext_file = absFilename();
202 string emb_file = embeddedFile(buf);
204 FileName emb(emb_file);
205 FileName ext(ext_file);
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)));
219 // if embedded file already exists ...
221 // no need to copy if the files are the same
222 if (checksum() == FileName(emb_file).checksum())
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"));
230 // if the user does not want to overwrite, we still consider it
231 // a successful operation.
235 // need to make directory?
236 FileName path = emb.onlyPath();
237 if (!path.isDirectory())
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());
251 void EmbeddedFile::updateInsets(Buffer const * buf) const
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);
260 bool operator==(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
262 return lhs.absFilename() == rhs.absFilename()
263 && lhs.saveAbsPath() == rhs.saveAbsPath()
264 && lhs.embedded() == rhs.embedded();
268 bool operator!=(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
270 return !(lhs == rhs);
274 bool EmbeddedFiles::enabled() const
276 return buffer_->params().embedded;
280 void EmbeddedFiles::enable(bool flag)
282 if (enabled() != flag) {
283 // update embedded file list
285 // An exception may be thrown.
287 // if enable, copy all files to temppath()
288 updateFromExternalFile();
290 // if disable, extract all files
292 // if operation is successful (no exception is thrown)
293 buffer_->markDirty();
294 buffer_->params().embedded = flag;
301 EmbeddedFile & EmbeddedFiles::registerFile(EmbeddedFile const & file, Inset const * inset)
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())));
319 file_list_.push_back(file);
320 file_list_.back().addInset(inset);
321 return file_list_.back();
325 void EmbeddedFiles::update()
329 for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
330 it->registerEmbeddedFiles(*buffer_, *this);
334 bool EmbeddedFiles::writeFile(DocFileName const & filename)
336 // file in the temporary path has the content
337 string const content = FileName(addName(buffer_->temppath(),
338 "content.lyx")).toFilesystemEncoding();
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?"),
353 filenames.push_back(make_pair(file, it->inzipName()));
354 LYXERR(Debug::FILES, "Writing file " << it->outputFilename()
355 << " as " << it->inzipName() << endl);
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"))));
364 ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
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());
377 EmbeddedFiles::EmbeddedFileList::const_iterator
378 EmbeddedFiles::find(string filename) const
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)
385 return file_list_.end();
389 bool EmbeddedFiles::extractAll() const
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()));
402 count_extracted += 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);
412 bool EmbeddedFiles::updateFromExternalFile() const
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()));
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);
436 void EmbeddedFiles::updateInsets() const
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_);