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 "frontends/alert.h"
26 #include <boost/filesystem/operations.hpp>
28 #include "support/filetools.h"
29 #include "support/fs_extras.h"
30 #include "support/convert.h"
31 #include "support/lyxlib.h"
32 #include "support/lstrings.h"
50 using std::istringstream;
54 namespace fs = boost::filesystem;
55 namespace Alert = frontend::Alert;
57 using support::FileName;
58 using support::DocFileName;
59 using support::makeAbsPath;
60 using support::addName;
61 using support::onlyPath;
62 using support::absolutePath;
63 using support::onlyFilename;
64 using support::makeRelPath;
65 using support::changeExtension;
66 using support::bformat;
67 using support::prefixIs;
69 using support::makedir;
72 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
73 bool embed, Inset const * inset)
74 : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
75 valid_(true), inset_list_()
78 inset_list_.push_back(inset);
82 string EmbeddedFile::embeddedFile(Buffer const * buf) const
84 return addName(buf->temppath(), inzip_name_);
88 void EmbeddedFile::addInset(Inset const * inset)
90 inset_list_.push_back(inset);
94 Inset const * EmbeddedFile::inset(int idx) const
96 BOOST_ASSERT(idx < refCount());
97 // some embedded file do not have a valid par iterator
98 return inset_list_[idx];
102 void EmbeddedFile::saveBookmark(Buffer const * buf, int idx) const
104 Inset const * ptr = inset(idx);
105 // This might not be the most efficient method ...
106 for (InsetIterator it = inset_iterator_begin(buf->inset()); it; ++it)
108 // this is basically BufferView::saveBookmark(0)
109 LyX::ref().session().bookmarks().save(
110 FileName(buf->fileName()),
118 // this inset can not be located. There is something wrong that needs
124 string EmbeddedFile::availableFile(Buffer const * buf) const
127 return embeddedFile(buf);
129 return absFilename();
133 void EmbeddedFile::invalidate()
135 // Clear inset_list_ because they will be registered again.
141 bool EmbeddedFile::extract(Buffer const * buf) const
144 string ext_file = absFilename();
145 string emb_file = embeddedFile(buf);
147 if (!fs::exists(emb_file))
150 // if external file already exists ...
151 if (fs::exists(ext_file)) {
152 // no need to copy if the files are the same
153 if (sum(*this) == sum(FileName(emb_file)))
155 // otherwise, ask if overwrite
156 int ret = Alert::prompt(
157 _("Overwrite external file?"),
158 bformat(_("External file %1$s already exists, do you want to overwrite it"),
159 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
161 // if the user does not want to overwrite, we still consider it
162 // a successful operation.
167 // need to make directory?
168 string path = onlyPath(ext_file);
169 if (!fs::is_directory(path))
170 makedir(const_cast<char*>(path.c_str()), 0755);
171 fs::copy_file(emb_file, ext_file, false);
173 } catch (fs::filesystem_error const & fe) {
174 Alert::error(_("Copy file failure"),
175 bformat(_("Cannot copy file %1$s to %2$s.\n"
176 "Please check whether the directory exists and is writeable."),
177 from_utf8(emb_file), from_utf8(ext_file)));
178 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
184 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
186 string ext_file = absFilename();
187 string emb_file = embeddedFile(buf);
189 if (!fs::exists(ext_file))
192 // if embedded file already exists ...
193 if (fs::exists(emb_file)) {
194 // no need to copy if the files are the same
195 if (sum(*this) == sum(FileName(emb_file)))
197 // other wise, ask if overwrite
198 int const ret = Alert::prompt(
199 _("Update embedded file?"),
200 bformat(_("Embeddedl file %1$s already exists, do you want to overwrite it"),
201 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
203 // if the user does not want to overwrite, we still consider it
204 // a successful operation.
209 // need to make directory?
210 string path = onlyPath(emb_file);
211 if (!fs::is_directory(path))
212 makedir(const_cast<char*>(path.c_str()), 0755);
213 fs::copy_file(ext_file, emb_file, false);
215 } catch (fs::filesystem_error const & fe) {
216 Alert::error(_("Copy file failure"),
217 bformat(_("Cannot copy file %1$s to %2$s.\n"
218 "Please check whether the directory exists and is writeable."),
219 from_utf8(ext_file), from_utf8(emb_file)));
220 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
226 bool EmbeddedFiles::enabled() const
228 return buffer_->params().embedded;
232 bool EmbeddedFiles::enable(bool flag)
234 if (enabled() != flag) {
235 // if enable, copy all files to temppath()
236 // if disable, extract all files
237 if ((flag && !updateFromExternalFile()) || (!flag && !extract()))
239 // if operation is successful
240 buffer_->markDirty();
241 buffer_->params().embedded = flag;
246 void EmbeddedFiles::registerFile(string const & filename,
247 bool embed, Inset const * inset)
249 // filename can be relative or absolute, translate to absolute filename
250 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
251 // try to find this file from the list
252 EmbeddedFileList::iterator it = file_list_.begin();
253 EmbeddedFileList::iterator it_end = file_list_.end();
254 for (; it != it_end; ++it)
255 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
257 // find this filename, keep the original embedding status
258 if (it != file_list_.end()) {
260 // if the file is embedded, the embedded file should have exist
261 // check for this to ensure that our logic is correct
263 BOOST_ASSERT(fs::exists(it->embeddedFile(buffer_)));
267 // try to be more careful
268 file_list_.push_back(EmbeddedFile(abs_filename,
269 getInzipName(abs_filename), embed, inset));
270 // validate if things are OK
271 BOOST_ASSERT(fs::exists(file_list_.back().availableFile(buffer_)));
275 void EmbeddedFiles::update()
277 // invalidate all files, obsolete files will then not be validated by the
278 // following document scan. These files will still be kept though, because
279 // they may be added later and their embedding status will be meaningful
280 // again (thinking of cut/paste of an InsetInclude).
281 EmbeddedFileList::iterator it = file_list_.begin();
282 EmbeddedFileList::iterator it_end = file_list_.end();
283 for (; it != it_end; ++it)
284 // we do not update items that are manually inserted
285 if (it->refCount() > 0)
288 for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
289 it->registerEmbeddedFiles(*buffer_, *this);
291 LYXERR(Debug::FILES) << "Manifest updated: " << endl
293 << "End Manifest" << endl;
297 bool EmbeddedFiles::write(DocFileName const & filename)
299 // file in the temporary path has the content
300 string const content = FileName(addName(buffer_->temppath(),
301 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
303 // get a file list and write a manifest file
304 vector<pair<string, string> > filenames;
305 string const manifest = FileName(
306 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
308 // write a manifest file
309 ofstream os(manifest.c_str());
312 // prepare list of embedded file
313 EmbeddedFileList::iterator it = file_list_.begin();
314 EmbeddedFileList::iterator it_end = file_list_.end();
315 for (; it != it_end; ++it) {
316 if (it->valid() && it->embedded()) {
317 string file = it->availableFile(buffer_);
319 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
321 filenames.push_back(make_pair(file, it->inzipName()));
324 // add filename (.lyx) and manifest to filenames
325 filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
326 filenames.push_back(make_pair(manifest, "manifest.txt"));
327 // write a zip file with all these files. Write to a temp file first, to
328 // avoid messing up the original file in case something goes terribly wrong.
329 DocFileName zipfile(addName(buffer_->temppath(),
330 onlyFilename(changeExtension(
331 filename.toFilesystemEncoding(), ".zip"))));
333 ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
336 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
337 } catch (fs::filesystem_error const & fe) {
338 Alert::error(_("Save failure"),
339 bformat(_("Cannot create file %1$s.\n"
340 "Please check whether the directory exists and is writeable."),
341 from_utf8(filename.absFilename())));
342 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
348 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
350 EmbeddedFileList::const_iterator it = file_list_.begin();
351 EmbeddedFileList::const_iterator it_end = file_list_.end();
352 for (; it != it_end; ++it)
353 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)
355 return file_list_.end();
359 bool EmbeddedFiles::extract() const
361 EmbeddedFileList::const_iterator it = file_list_.begin();
362 EmbeddedFileList::const_iterator it_end = file_list_.end();
363 for (; it != it_end; ++it)
364 if (it->valid() && it->embedded())
365 if(!it->extract(buffer_))
371 bool EmbeddedFiles::updateFromExternalFile() const
373 EmbeddedFileList::const_iterator it = file_list_.begin();
374 EmbeddedFileList::const_iterator it_end = file_list_.end();
375 for (; it != it_end; ++it)
376 if (it->valid() && it->embedded())
377 if (!it->updateFromExternalFile(buffer_))
383 string const EmbeddedFiles::getInzipName(string const & abs_filename)
385 // register a new one, using relative file path as inzip_name
386 string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
387 from_utf8(buffer_->fileName())));
388 // if inzip_name is an absolute path, use filename only to avoid
389 // leaking of filesystem information in inzip_name
390 // The second case covers cases '../path/file' and '.'
391 if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
392 inzip_name = onlyFilename(abs_filename);
393 // if this name has been used...
394 // use _1_name, _2_name etc
395 string tmp = inzip_name;
396 EmbeddedFileList::iterator it;
397 EmbeddedFileList::iterator it_end = file_list_.end();
398 bool unique_name = false;
400 while (!unique_name) {
403 inzip_name = convert<string>(i) + "_" + tmp;
404 it = file_list_.begin();
405 for (; it != it_end; ++it)
406 if (it->inzipName() == inzip_name) {
416 istream & operator>> (istream & is, EmbeddedFiles & files)
422 istringstream itmp(tmp);
424 itmp.ignore(string("# LyX manifest version ").size());
428 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
433 if (tmp != "<manifest>") {
434 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
437 // manifest file may be messed up, be carefully
446 getline(is, inzip_name);
448 istringstream itmp(tmp);
453 if (tmp != "</file>") {
454 lyxerr << "Invalid manifest file, lacking </file>" << endl;
458 files.registerFile(fname, embed);
460 // the last line must be </manifest>
461 if (tmp != "</manifest>") {
462 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
469 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
471 // store a version so that operator >> can read later versions
472 // using version information.
473 os << "# lyx manifest version 1\n";
474 os << "<manifest>\n";
475 EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
476 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
477 for (; it != it_end; ++it) {
480 // use differnt lines to make reading easier.
482 // save the relative path
483 << to_utf8(makeRelPath(from_utf8(it->absFilename()),
484 from_utf8(files.buffer_->filePath()))) << '\n'
485 << it->inzipName() << '\n'
486 << it->embedded() << '\n'
489 os << "</manifest>\n";