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"
17 #include "BufferParams.h"
18 #include "Paragraph.h"
19 #include "InsetIterator.h"
24 #include "ErrorList.h"
26 #include "frontends/alert.h"
28 #include <boost/filesystem/operations.hpp>
30 #include "support/filetools.h"
31 #include "support/fs_extras.h"
32 #include "support/convert.h"
33 #include "support/lyxlib.h"
34 #include "support/lstrings.h"
52 using std::istringstream;
56 namespace fs = boost::filesystem;
57 namespace Alert = frontend::Alert;
59 using support::FileName;
60 using support::DocFileName;
61 using support::makeAbsPath;
62 using support::addName;
63 using support::onlyPath;
64 using support::absolutePath;
65 using support::onlyFilename;
66 using support::makeRelPath;
67 using support::changeExtension;
68 using support::bformat;
69 using support::prefixIs;
71 using support::makedir;
74 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
75 bool embed, Inset const * inset)
76 : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
77 valid_(true), inset_list_()
80 inset_list_.push_back(inset);
84 string EmbeddedFile::embeddedFile(Buffer const * buf) const
86 return addName(buf->temppath(), inzip_name_);
90 void EmbeddedFile::addInset(Inset const * inset)
92 inset_list_.push_back(inset);
96 Inset const * EmbeddedFile::inset(int idx) const
98 BOOST_ASSERT(idx < refCount());
99 // some embedded file do not have a valid par iterator
100 return inset_list_[idx];
104 void EmbeddedFile::saveBookmark(Buffer const * buf, int idx) const
106 Inset const * ptr = inset(idx);
107 // This might not be the most efficient method ...
108 for (InsetIterator it = inset_iterator_begin(buf->inset()); it; ++it)
110 // this is basically BufferView::saveBookmark(0)
111 LyX::ref().session().bookmarks().save(
112 FileName(buf->fileName()),
120 // this inset can not be located. There is something wrong that needs
126 string EmbeddedFile::availableFile(Buffer const * buf) const
129 return embeddedFile(buf);
131 return absFilename();
135 void EmbeddedFile::invalidate()
137 // Clear inset_list_ because they will be registered again.
143 bool EmbeddedFile::extract(Buffer const * buf) const
146 string ext_file = absFilename();
147 string emb_file = embeddedFile(buf);
149 if (!fs::exists(emb_file))
152 // if external file already exists ...
153 if (fs::exists(ext_file)) {
154 // no need to copy if the files are the same
155 if (sum(*this) == sum(FileName(emb_file)))
157 // otherwise, ask if overwrite
158 int ret = Alert::prompt(
159 _("Overwrite external file?"),
160 bformat(_("External file %1$s already exists, do you want to overwrite it"),
161 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
163 // if the user does not want to overwrite, we still consider it
164 // a successful operation.
169 // need to make directory?
170 string path = onlyPath(ext_file);
171 if (!fs::is_directory(path))
172 makedir(const_cast<char*>(path.c_str()), 0755);
173 fs::copy_file(emb_file, ext_file, false);
175 } catch (fs::filesystem_error const & fe) {
176 Alert::error(_("Copy file failure"),
177 bformat(_("Cannot copy file %1$s to %2$s.\n"
178 "Please check whether the directory exists and is writeable."),
179 from_utf8(emb_file), from_utf8(ext_file)));
180 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
186 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
188 string ext_file = absFilename();
189 string emb_file = embeddedFile(buf);
191 if (!fs::exists(ext_file))
194 // if embedded file already exists ...
195 if (fs::exists(emb_file)) {
196 // no need to copy if the files are the same
197 if (sum(*this) == sum(FileName(emb_file)))
199 // other wise, ask if overwrite
200 int const ret = Alert::prompt(
201 _("Update embedded file?"),
202 bformat(_("Embeddedl file %1$s already exists, do you want to overwrite it"),
203 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
205 // if the user does not want to overwrite, we still consider it
206 // a successful operation.
211 // need to make directory?
212 string path = onlyPath(emb_file);
213 if (!fs::is_directory(path))
214 makedir(const_cast<char*>(path.c_str()), 0755);
215 fs::copy_file(ext_file, emb_file, false);
217 } catch (fs::filesystem_error const & fe) {
218 Alert::error(_("Copy file failure"),
219 bformat(_("Cannot copy file %1$s to %2$s.\n"
220 "Please check whether the directory exists and is writeable."),
221 from_utf8(ext_file), from_utf8(emb_file)));
222 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
228 void EmbeddedFile::updateInsets(Buffer const * buf) const
230 vector<Inset const *>::const_iterator it = inset_list_.begin();
231 vector<Inset const *>::const_iterator it_end = inset_list_.end();
232 for (; it != it_end; ++it)
233 const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
237 bool EmbeddedFiles::enabled() const
239 return buffer_->params().embedded;
243 void EmbeddedFiles::enable(bool flag)
245 if (enabled() != flag) {
246 // if enable, copy all files to temppath()
247 // if disable, extract all files
248 if ((flag && !updateFromExternalFile()) || (!flag && !extract()))
250 // if operation is successful
251 buffer_->markDirty();
252 buffer_->params().embedded = flag;
259 EmbeddedFile & EmbeddedFiles::registerFile(string const & filename,
260 bool embed, Inset const * inset, string const & inzipName)
262 // filename can be relative or absolute, translate to absolute filename
263 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
264 // try to find this file from the list
265 EmbeddedFileList::iterator it = file_list_.begin();
266 EmbeddedFileList::iterator it_end = file_list_.end();
267 for (; it != it_end; ++it)
268 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
270 // find this filename, keep the original embedding status
271 if (it != file_list_.end()) {
276 // try to be more careful
277 file_list_.push_back(EmbeddedFile(abs_filename,
278 getInzipName(abs_filename, inzipName), embed, inset));
279 return file_list_.back();
283 void EmbeddedFiles::update()
285 // invalidate all files, obsolete files will then not be validated by the
286 // following document scan. These files will still be kept though, because
287 // they may be added later and their embedding status will be meaningful
288 // again (thinking of cut/paste of an InsetInclude).
289 EmbeddedFileList::iterator it = file_list_.begin();
290 EmbeddedFileList::iterator it_end = file_list_.end();
291 for (; it != it_end; ++it)
292 // we do not update items that are manually inserted
293 if (it->refCount() > 0)
296 for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
297 it->registerEmbeddedFiles(*buffer_, *this);
301 bool EmbeddedFiles::writeFile(DocFileName const & filename)
303 // file in the temporary path has the content
304 string const content = FileName(addName(buffer_->temppath(),
305 "content.lyx")).toFilesystemEncoding();
307 vector<pair<string, string> > filenames;
308 // add content.lyx to filenames
309 filenames.push_back(make_pair(content, "content.lyx"));
310 // prepare list of embedded file
311 EmbeddedFileList::iterator it = file_list_.begin();
312 EmbeddedFileList::iterator it_end = file_list_.end();
313 for (; it != it_end; ++it) {
314 if (it->valid() && it->embedded()) {
315 string file = it->availableFile(buffer_);
317 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
319 filenames.push_back(make_pair(file, it->inzipName()));
322 // write a zip file with all these files. Write to a temp file first, to
323 // avoid messing up the original file in case something goes terribly wrong.
324 DocFileName zipfile(addName(buffer_->temppath(),
325 onlyFilename(changeExtension(
326 filename.toFilesystemEncoding(), ".zip"))));
328 ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
331 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
332 } catch (fs::filesystem_error const & fe) {
333 Alert::error(_("Save failure"),
334 bformat(_("Cannot create file %1$s.\n"
335 "Please check whether the directory exists and is writeable."),
336 from_utf8(filename.absFilename())));
337 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
343 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
345 EmbeddedFileList::const_iterator it = file_list_.begin();
346 EmbeddedFileList::const_iterator it_end = file_list_.end();
347 for (; it != it_end; ++it)
348 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)
350 return file_list_.end();
354 bool EmbeddedFiles::extract() const
356 EmbeddedFileList::const_iterator it = file_list_.begin();
357 EmbeddedFileList::const_iterator it_end = file_list_.end();
358 for (; it != it_end; ++it)
359 if (it->valid() && it->embedded())
360 if(!it->extract(buffer_))
366 bool EmbeddedFiles::updateFromExternalFile() const
368 EmbeddedFileList::const_iterator it = file_list_.begin();
369 EmbeddedFileList::const_iterator it_end = file_list_.end();
370 for (; it != it_end; ++it)
371 if (it->valid() && it->embedded())
372 if (!it->updateFromExternalFile(buffer_))
378 string const EmbeddedFiles::getInzipName(string const & abs_filename, string const & name)
380 // register a new one, using relative file path as inzip_name
381 string inzip_name = name;
383 inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
384 from_utf8(buffer_->filePath())));
385 // if inzip_name is an absolute path, use filename only to avoid
386 // leaking of filesystem information in inzip_name
387 // The second case covers cases '../path/file' and '.'
388 if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
389 inzip_name = onlyFilename(abs_filename);
390 // if this name has been used...
391 // use _1_name, _2_name etc
392 string tmp = inzip_name;
393 EmbeddedFileList::iterator it;
394 EmbeddedFileList::iterator it_end = file_list_.end();
395 bool unique_name = false;
397 while (!unique_name) {
400 inzip_name = convert<string>(i) + "_" + tmp;
401 it = file_list_.begin();
402 for (; it != it_end; ++it)
403 if (it->inzipName() == inzip_name) {
413 bool EmbeddedFiles::readManifest(Lexer & lex, ErrorList & errorList)
416 int begin_manifest_line = -1;
421 bool embedded = false;
425 string const token = lex.getString();
430 if (token == "\\end_manifest")
434 if (token == "\\begin_manifest") {
435 begin_manifest_line = line;
439 LYXERR(Debug::PARSER) << "Handling document manifest token: `"
440 << token << '\'' << endl;
442 if (token == "\\filename")
444 else if (token == "\\inzipName")
446 else if (token == "\\embed") {
448 registerFile(filename, embedded, NULL, inzipName);
452 docstring const s = _("\\begin_file is missing");
453 errorList.push_back(ErrorItem(_("Manifest error"),
457 if (begin_manifest_line) {
458 docstring const s = _("\\begin_manifest is missing");
459 errorList.push_back(ErrorItem(_("Manifest error"),
466 void EmbeddedFiles::writeManifest(ostream & os) const
468 EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
469 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
470 for (; it != it_end; ++it) {
473 // save the relative path
475 << to_utf8(makeRelPath(from_utf8(it->absFilename()),
476 from_utf8(buffer_->filePath()))) << '\n'
477 << "\\inzipName " << it->inzipName() << '\n'
478 << "\\embed " << (it->embedded() ? "true" : "false") << '\n';
483 void EmbeddedFiles::updateInsets() const
485 EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
486 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
487 for (; it != it_end; ++it)
488 if (it->valid() && it->refCount() > 0)
489 it->updateInsets(buffer_);