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 namespace Alert = frontend::Alert;
49 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
50 bool embed, Inset const * inset)
51 : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
55 inset_list_.push_back(inset);
59 string EmbeddedFile::embeddedFile(Buffer const * buf) const
61 return addName(buf->temppath(), inzip_name_);
65 void EmbeddedFile::addInset(Inset const * inset)
67 inset_list_.push_back(inset);
71 Inset const * EmbeddedFile::inset(int idx) const
73 BOOST_ASSERT(idx < refCount());
74 // some embedded file do not have a valid par iterator
75 return inset_list_[idx];
79 void EmbeddedFile::saveBookmark(Buffer const * buf, int idx) const
81 Inset const * ptr = inset(idx);
82 // This might not be the most efficient method ...
83 for (InsetIterator it = inset_iterator_begin(buf->inset()); it; ++it)
85 // this is basically BufferView::saveBookmark(0)
86 LyX::ref().session().bookmarks().save(
87 FileName(buf->absFileName()),
95 // this inset can not be located. There is something wrong that needs
101 string EmbeddedFile::availableFile(Buffer const * buf) const
103 return embedded() ? embeddedFile(buf) : absFilename();
107 bool EmbeddedFile::extract(Buffer const * buf) const
109 string ext_file = absFilename();
110 string emb_file = embeddedFile(buf);
112 FileName emb(emb_file);
113 FileName ext(ext_file);
118 // if external file already exists ...
120 // no need to copy if the files are the same
121 if (checksum() == FileName(emb_file).checksum())
123 // otherwise, ask if overwrite
124 int ret = Alert::prompt(
125 _("Overwrite external file?"),
126 bformat(_("External file %1$s already exists, do you want to overwrite it"),
127 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
129 // if the user does not want to overwrite, we still consider it
130 // a successful operation.
135 // need to make directory?
136 FileName path = ext.onlyPath();
137 if (!path.createPath()) {
138 throw ExceptionMessage(ErrorException, _("Copy file failure"),
139 bformat(_("Cannot create file path '%1$s'.\n"
140 "Please check whether the path is writeable."),
141 from_utf8(path.absFilename())));
148 throw ExceptionMessage(ErrorException, _("Copy file failure"),
149 bformat(_("Cannot copy file %1$s to %2$s.\n"
150 "Please check whether the directory exists and is writeable."),
151 from_utf8(emb_file), from_utf8(ext_file)));
156 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
158 string ext_file = absFilename();
159 string emb_file = embeddedFile(buf);
161 FileName emb(emb_file);
162 FileName ext(ext_file);
167 // if embedded file already exists ...
169 // no need to copy if the files are the same
170 if (checksum() == FileName(emb_file).checksum())
172 // other wise, ask if overwrite
173 int const ret = Alert::prompt(
174 _("Update embedded file?"),
175 bformat(_("Embedded file %1$s already exists, do you want to overwrite it"),
176 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
178 // if the user does not want to overwrite, we still consider it
179 // a successful operation.
183 // need to make directory?
184 FileName path = emb.onlyPath();
185 if (!path.isDirectory())
189 throw ExceptionMessage(ErrorException,
190 _("Copy file failure"),
191 bformat(_("Cannot copy file %1$s to %2$s.\n"
192 "Please check whether the directory exists and is writeable."),
193 from_utf8(ext_file), from_utf8(emb_file)));
194 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
199 void EmbeddedFile::updateInsets(Buffer const * buf) const
201 vector<Inset const *>::const_iterator it = inset_list_.begin();
202 vector<Inset const *>::const_iterator it_end = inset_list_.end();
203 for (; it != it_end; ++it)
204 const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
208 bool EmbeddedFiles::enabled() const
210 return buffer_->params().embedded;
214 void EmbeddedFiles::enable(bool flag)
216 if (enabled() != flag) {
217 // update embedded file list
219 // An exception may be thrown.
221 // if enable, copy all files to temppath()
222 updateFromExternalFile();
224 // if disable, extract all files
226 // if operation is successful (no exception is thrown)
227 buffer_->markDirty();
228 buffer_->params().embedded = flag;
235 EmbeddedFile & EmbeddedFiles::registerFile(string const & filename,
236 bool embed, Inset const * inset, string const & inzipName)
238 // filename can be relative or absolute, translate to absolute filename
239 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
240 // try to find this file from the list
241 EmbeddedFileList::iterator it = file_list_.begin();
242 EmbeddedFileList::iterator it_end = file_list_.end();
243 for (; it != it_end; ++it)
244 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
246 // find this filename, keep the original embedding status
247 if (it != file_list_.end()) {
252 file_list_.push_back(EmbeddedFile(abs_filename,
253 getInzipName(abs_filename, inzipName), embed, inset));
254 return file_list_.back();
258 void EmbeddedFiles::update()
262 for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
263 it->registerEmbeddedFiles(*buffer_, *this);
267 bool EmbeddedFiles::writeFile(DocFileName const & filename)
269 // file in the temporary path has the content
270 string const content = FileName(addName(buffer_->temppath(),
271 "content.lyx")).toFilesystemEncoding();
273 vector<pair<string, string> > filenames;
274 // add content.lyx to filenames
275 filenames.push_back(make_pair(content, "content.lyx"));
276 // prepare list of embedded file
277 EmbeddedFileList::iterator it = file_list_.begin();
278 EmbeddedFileList::iterator it_end = file_list_.end();
279 for (; it != it_end; ++it) {
280 if (it->embedded()) {
281 string file = it->availableFile(buffer_);
283 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
285 filenames.push_back(make_pair(file, it->inzipName()));
288 // write a zip file with all these files. Write to a temp file first, to
289 // avoid messing up the original file in case something goes terribly wrong.
290 DocFileName zipfile(addName(buffer_->temppath(),
291 onlyFilename(changeExtension(
292 filename.toFilesystemEncoding(), ".zip"))));
294 ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
296 if (!zipfile.copyTo(filename)) {
297 Alert::error(_("Save failure"),
298 bformat(_("Cannot create file %1$s.\n"
299 "Please check whether the directory exists and is writeable."),
300 from_utf8(filename.absFilename())));
301 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
307 EmbeddedFiles::EmbeddedFileList::const_iterator
308 EmbeddedFiles::find(string filename) const
310 EmbeddedFileList::const_iterator it = file_list_.begin();
311 EmbeddedFileList::const_iterator it_end = file_list_.end();
312 for (; it != it_end; ++it)
313 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)
315 return file_list_.end();
319 bool EmbeddedFiles::extractAll() const
321 EmbeddedFileList::const_iterator it = file_list_.begin();
322 EmbeddedFileList::const_iterator it_end = file_list_.end();
323 int count_extracted = 0;
324 int count_external = 0;
325 for (; it != it_end; ++it)
326 if (it->embedded()) {
327 if(!it->extract(buffer_)) {
328 throw ExceptionMessage(ErrorException,
329 _("Failed to extract file"),
330 bformat(_("Error: can not extract file %1$s.\n"), it->displayName()));
332 count_extracted += 1;
335 docstring const msg = bformat(_("%1$d external files are ignored.\n"
336 "%2$d embedded files are extracted.\n"), count_external, count_extracted);
337 Alert::information(_("Unpacking all files"), msg);
342 bool EmbeddedFiles::updateFromExternalFile() const
344 EmbeddedFileList::const_iterator it = file_list_.begin();
345 EmbeddedFileList::const_iterator it_end = file_list_.end();
346 int count_embedded = 0;
347 int count_external = 0;
348 for (; it != it_end; ++it)
349 if (it->embedded()) {
350 if (!it->updateFromExternalFile(buffer_)) {
351 throw ExceptionMessage(ErrorException,
352 _("Failed to embed file"),
353 bformat(_("Error: can not embed file %1$s.\n"), it->displayName()));
359 docstring const msg = bformat(_("%1$d external files are ignored.\n"
360 "%2$d embeddable files are embedded.\n"), count_external, count_embedded);
361 Alert::information(_("Packing all files"), msg);
366 string const EmbeddedFiles::getInzipName(string const & abs_filename, string const & name)
368 // register a new one, using relative file path as inzip_name
369 string inzip_name = name;
371 inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
372 from_utf8(buffer_->filePath())));
373 // if inzip_name is an absolute path, use filename only to avoid
374 // leaking of filesystem information in inzip_name
375 // The second case covers cases '../path/file' and '.'
376 if (FileName(inzip_name).isAbsolute() || prefixIs(inzip_name, "."))
377 inzip_name = onlyFilename(abs_filename);
378 // if this name has been used...
379 // use _1_name, _2_name etc
380 string tmp = inzip_name;
381 EmbeddedFileList::iterator it;
382 EmbeddedFileList::iterator it_end = file_list_.end();
383 bool unique_name = false;
385 while (!unique_name) {
388 inzip_name = convert<string>(i) + "_" + tmp;
389 it = file_list_.begin();
390 for (; it != it_end; ++it)
391 if (it->inzipName() == inzip_name) {
401 void EmbeddedFiles::updateInsets() const
403 EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
404 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
405 for (; it != it_end; ++it)
406 if (it->refCount() > 0)
407 it->updateInsets(buffer_);