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 "ParIterator.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"
47 using std::istringstream;
51 namespace fs = boost::filesystem;
52 namespace Alert = frontend::Alert;
54 using support::FileName;
55 using support::DocFileName;
56 using support::makeAbsPath;
57 using support::addName;
58 using support::onlyPath;
59 using support::absolutePath;
60 using support::onlyFilename;
61 using support::makeRelPath;
62 using support::changeExtension;
63 using support::bformat;
64 using support::prefixIs;
66 using support::makedir;
69 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
70 bool embed, ParConstIterator const & pit)
71 : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
72 valid_(true), par_it_(pit)
76 string EmbeddedFile::embeddedFile(Buffer const * buf) const
78 return addName(buf->temppath(), inzip_name_);
82 int const EmbeddedFile::parID() const
84 // some embedded file do not have a valid par iterator
85 return par_it_ == ParConstIterator() ? 0 : par_it_->id();
89 void EmbeddedFile::setParIter(ParConstIterator const & pit)
95 string EmbeddedFile::availableFile(Buffer const * buf) const
98 return embeddedFile(buf);
100 return absFilename();
104 bool EmbeddedFile::extract(Buffer const * buf) const
109 string ext_file = absFilename();
110 string emb_file = embeddedFile(buf);
111 bool copyFile = false;
112 // both files exist, are different
113 if (fs::exists(ext_file) && fs::exists(emb_file)
114 && sum(*this) != sum(FileName(emb_file))) {
115 int const ret = Alert::prompt(
116 _("Overwrite external file?"),
117 bformat(_("External file %1$s already exists, do you want to overwrite it"),
118 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
121 // copy file in the previous case, and a new case
122 if (copyFile || (!fs::exists(ext_file) && fs::exists(emb_file))) {
124 // need to make directory?
125 string path = onlyPath(ext_file);
126 if (!fs::is_directory(path))
127 makedir(const_cast<char*>(path.c_str()), 0755);
128 fs::copy_file(emb_file, ext_file, false);
130 } catch (fs::filesystem_error const & fe) {
131 Alert::error(_("Copy file failure"),
132 bformat(_("Cannot copy file %1$s to %2$s.\n"
133 "Please check whether the directory exists and is writeable."),
134 from_utf8(emb_file), from_utf8(ext_file)));
135 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
142 bool EmbeddedFile::embed(Buffer const * buf)
144 string ext_file = absFilename();
145 string emb_file = embeddedFile(buf);
146 bool copyFile = false;
147 // both files exist, are different
148 if (fs::exists(ext_file) && fs::exists(emb_file)
149 && sum(*this) != sum(FileName(emb_file))) {
150 int const ret = Alert::prompt(
151 _("Update embedded file?"),
152 bformat(_("Embeddedl file %1$s already exists, do you want to overwrite it"),
153 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
156 // copy file in the previous case, and a new case
157 if (copyFile || (fs::exists(ext_file) && !fs::exists(emb_file))) {
159 // need to make directory?
160 string path = onlyPath(emb_file);
161 if (!fs::is_directory(path))
162 makedir(const_cast<char*>(path.c_str()), 0755);
163 fs::copy_file(ext_file, emb_file, false);
165 } catch (fs::filesystem_error const & fe) {
166 Alert::error(_("Copy file failure"),
167 bformat(_("Cannot copy file %1$s to %2$s.\n"
168 "Please check whether the directory exists and is writeable."),
169 from_utf8(ext_file), from_utf8(emb_file)));
170 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
178 bool EmbeddedFiles::enabled() const
180 return buffer_->params().embedded;
184 void EmbeddedFiles::enable(bool flag)
186 if (enabled() != flag) {
187 // if disable embedding, first extract all embedded files
188 if (flag || (!flag && extractAll())) {
189 // file will be changed
190 buffer_->markDirty();
191 buffer_->params().embedded = flag;
197 void EmbeddedFiles::registerFile(string const & filename,
198 bool embed, ParConstIterator const & pit)
200 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
201 // try to find this file from the list
202 EmbeddedFileList::iterator it = file_list_.begin();
203 EmbeddedFileList::iterator it_end = file_list_.end();
204 for (; it != it_end; ++it)
205 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
207 // find this filename
208 if (it != file_list_.end()) {
214 file_list_.push_back(EmbeddedFile(abs_filename,
215 getInzipName(abs_filename), embed, pit));
219 void EmbeddedFiles::update()
221 // invalidate all files, obsolete files will then not be validated by the
222 // following document scan. These files will still be kept though, because
223 // they may be added later and their embedding status will be meaningful
224 // again (thinking of cut/paste of an InsetInclude).
225 EmbeddedFileList::iterator it = file_list_.begin();
226 EmbeddedFileList::iterator it_end = file_list_.end();
227 for (; it != it_end; ++it)
230 ParIterator pit = buffer_->par_iterator_begin();
231 ParIterator pit_end = buffer_->par_iterator_end();
232 for (; pit != pit_end; ++pit) {
233 // For each paragraph, traverse its insets and register embedded files
234 InsetList::const_iterator iit = pit->insetlist.begin();
235 InsetList::const_iterator iit_end = pit->insetlist.end();
236 for (; iit != iit_end; ++iit) {
237 Inset & inset = *iit->inset;
238 inset.registerEmbeddedFiles(*buffer_, *this, pit);
241 LYXERR(Debug::FILES) << "Manifest updated: " << endl
243 << "End Manifest" << endl;
247 bool EmbeddedFiles::write(DocFileName const & filename)
249 // file in the temporary path has the content
250 string const content = FileName(addName(buffer_->temppath(),
251 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
253 // get a file list and write a manifest file
254 vector<pair<string, string> > filenames;
255 string const manifest = FileName(
256 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
258 // write a manifest file
259 ofstream os(manifest.c_str());
262 // prepare list of embedded file
263 EmbeddedFileList::iterator it = file_list_.begin();
264 EmbeddedFileList::iterator it_end = file_list_.end();
265 for (; it != it_end; ++it) {
266 if (it->valid() && it->embedded()) {
267 string file = it->availableFile(buffer_);
269 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
271 filenames.push_back(make_pair(file, it->inzipName()));
274 // add filename (.lyx) and manifest to filenames
275 filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
276 filenames.push_back(make_pair(manifest, "manifest.txt"));
277 // write a zip file with all these files. Write to a temp file first, to
278 // avoid messing up the original file in case something goes terribly wrong.
279 DocFileName zipfile(addName(buffer_->temppath(),
280 onlyFilename(changeExtension(
281 filename.toFilesystemEncoding(), ".zip"))));
283 ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
286 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
287 } catch (fs::filesystem_error const & fe) {
288 Alert::error(_("Save failure"),
289 bformat(_("Cannot create file %1$s.\n"
290 "Please check whether the directory exists and is writeable."),
291 from_utf8(filename.absFilename())));
292 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
298 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
300 EmbeddedFileList::const_iterator it = file_list_.begin();
301 EmbeddedFileList::const_iterator it_end = file_list_.end();
302 for (; it != it_end; ++it)
303 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)
305 return file_list_.end();
309 bool EmbeddedFiles::extractAll() const
311 EmbeddedFileList::const_iterator it = file_list_.begin();
312 EmbeddedFileList::const_iterator it_end = file_list_.end();
313 // FIXME: the logic here is hard to decide, we should allow cancel for
314 // 'do not overwrite' this file, and cancel for 'cancel extract all files'.
315 // I am not sure how to do this now.
316 for (; it != it_end; ++it)
317 if (it->valid() && it->embedded())
318 it->extract(buffer_);
323 string const EmbeddedFiles::getInzipName(string const & abs_filename)
325 // register a new one, using relative file path as inzip_name
326 string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
327 from_utf8(buffer_->fileName())));
328 // if inzip_name is an absolute path, use filename only to avoid
329 // leaking of filesystem information in inzip_name
330 // The second case covers cases '../path/file' and '.'
331 if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
332 inzip_name = onlyFilename(abs_filename);
333 // if this name has been used...
334 // use _1_name, _2_name etc
335 string tmp = inzip_name;
336 EmbeddedFileList::iterator it;
337 EmbeddedFileList::iterator it_end = file_list_.end();
338 bool unique_name = false;
340 while (!unique_name) {
343 inzip_name = convert<string>(i) + "_" + tmp;
344 it = file_list_.begin();
345 for (; it != it_end; ++it)
346 if (it->inzipName() == inzip_name) {
356 istream & operator>> (istream & is, EmbeddedFiles & files)
362 istringstream itmp(tmp);
364 itmp.ignore(string("# LyX manifest version ").size());
368 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
373 if (tmp != "<manifest>") {
374 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
377 // manifest file may be messed up, be carefully
386 getline(is, inzip_name);
388 istringstream itmp(tmp);
393 if (tmp != "</file>") {
394 lyxerr << "Invalid manifest file, lacking </file>" << endl;
398 files.registerFile(fname, embed);
400 // the last line must be </manifest>
401 if (tmp != "</manifest>") {
402 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
409 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
411 // store a version so that operator >> can read later versions
412 // using version information.
413 os << "# lyx manifest version 1\n";
414 os << "<manifest>\n";
415 EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
416 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
417 for (; it != it_end; ++it) {
420 // use differnt lines to make reading easier.
422 // save the relative path
423 << to_utf8(makeRelPath(from_utf8(it->absFilename()),
424 from_utf8(files.buffer_->filePath()))) << '\n'
425 << it->inzipName() << '\n'
426 << it->embedded() << '\n'
429 os << "</manifest>\n";