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_()
74 if (pit != ParConstIterator())
75 par_it_.push_back(pit);
79 string EmbeddedFile::embeddedFile(Buffer const * buf) const
81 return addName(buf->temppath(), inzip_name_);
85 void EmbeddedFile::addParIter(ParConstIterator const & pit)
87 par_it_.push_back(pit);
91 int EmbeddedFile::parID(int idx) const
93 BOOST_ASSERT(idx < refCount());
94 // some embedded file do not have a valid par iterator
95 return par_it_[idx]->id();
99 string EmbeddedFile::availableFile(Buffer const * buf) const
102 return embeddedFile(buf);
104 return absFilename();
108 void EmbeddedFile::invalidate()
110 // Clear par_it_ because they will be registered again.
116 bool EmbeddedFile::extract(Buffer const * buf) const
119 string ext_file = absFilename();
120 string emb_file = embeddedFile(buf);
122 if (!fs::exists(emb_file))
125 // if external file already exists ...
126 if (fs::exists(ext_file)) {
127 // no need to copy if the files are the same
128 if (sum(*this) == sum(FileName(emb_file)))
130 // otherwise, ask if overwrite
131 int ret = Alert::prompt(
132 _("Overwrite external file?"),
133 bformat(_("External file %1$s already exists, do you want to overwrite it"),
134 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
136 // if the user does not want to overwrite, we still consider it
137 // a successful operation.
142 // need to make directory?
143 string path = onlyPath(ext_file);
144 if (!fs::is_directory(path))
145 makedir(const_cast<char*>(path.c_str()), 0755);
146 fs::copy_file(emb_file, ext_file, false);
148 } catch (fs::filesystem_error const & fe) {
149 Alert::error(_("Copy file failure"),
150 bformat(_("Cannot copy file %1$s to %2$s.\n"
151 "Please check whether the directory exists and is writeable."),
152 from_utf8(emb_file), from_utf8(ext_file)));
153 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
159 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
161 string ext_file = absFilename();
162 string emb_file = embeddedFile(buf);
164 if (!fs::exists(ext_file))
167 // if embedded file already exists ...
168 if (fs::exists(emb_file)) {
169 // no need to copy if the files are the same
170 if (sum(*this) == sum(FileName(emb_file)))
172 // other wise, ask if overwrite
173 int const ret = Alert::prompt(
174 _("Update embedded file?"),
175 bformat(_("Embeddedl 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.
184 // need to make directory?
185 string path = onlyPath(emb_file);
186 if (!fs::is_directory(path))
187 makedir(const_cast<char*>(path.c_str()), 0755);
188 fs::copy_file(ext_file, emb_file, false);
190 } catch (fs::filesystem_error const & fe) {
191 Alert::error(_("Copy file failure"),
192 bformat(_("Cannot copy file %1$s to %2$s.\n"
193 "Please check whether the directory exists and is writeable."),
194 from_utf8(ext_file), from_utf8(emb_file)));
195 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
201 bool EmbeddedFiles::enabled() const
203 return buffer_->params().embedded;
207 bool EmbeddedFiles::enable(bool flag)
209 if (enabled() != flag) {
210 // if enable, copy all files to temppath()
211 // if disable, extract all files
212 if ((flag && !updateFromExternalFile()) || (!flag && !extract()))
214 // if operation is successful
215 buffer_->markDirty();
216 buffer_->params().embedded = flag;
221 void EmbeddedFiles::registerFile(string const & filename,
222 bool embed, ParConstIterator const & pit)
224 // filename can be relative or absolute, translate to absolute filename
225 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
226 // try to find this file from the list
227 EmbeddedFileList::iterator it = file_list_.begin();
228 EmbeddedFileList::iterator it_end = file_list_.end();
229 for (; it != it_end; ++it)
230 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
232 // find this filename, keep the original embedding status
233 if (it != file_list_.end()) {
235 // if the file is embedded, the embedded file should have exist
236 // check for this to ensure that our logic is correct
238 BOOST_ASSERT(fs::exists(it->embeddedFile(buffer_)));
242 // try to be more careful
243 file_list_.push_back(EmbeddedFile(abs_filename,
244 getInzipName(abs_filename), embed, pit));
245 // validate if things are OK
246 BOOST_ASSERT(fs::exists(file_list_.back().availableFile(buffer_)));
250 void EmbeddedFiles::update()
252 // invalidate all files, obsolete files will then not be validated by the
253 // following document scan. These files will still be kept though, because
254 // they may be added later and their embedding status will be meaningful
255 // again (thinking of cut/paste of an InsetInclude).
256 EmbeddedFileList::iterator it = file_list_.begin();
257 EmbeddedFileList::iterator it_end = file_list_.end();
258 for (; it != it_end; ++it)
259 // we do not update items that are manually inserted
260 if (it->refCount() > 0)
263 ParIterator pit = buffer_->par_iterator_begin();
264 ParIterator pit_end = buffer_->par_iterator_end();
265 for (; pit != pit_end; ++pit) {
266 // For each paragraph, traverse its insets and register embedded files
267 InsetList::const_iterator iit = pit->insetlist.begin();
268 InsetList::const_iterator iit_end = pit->insetlist.end();
269 for (; iit != iit_end; ++iit) {
270 Inset & inset = *iit->inset;
271 inset.registerEmbeddedFiles(*buffer_, *this, pit);
274 LYXERR(Debug::FILES) << "Manifest updated: " << endl
276 << "End Manifest" << endl;
280 bool EmbeddedFiles::write(DocFileName const & filename)
282 // file in the temporary path has the content
283 string const content = FileName(addName(buffer_->temppath(),
284 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
286 // get a file list and write a manifest file
287 vector<pair<string, string> > filenames;
288 string const manifest = FileName(
289 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
291 // write a manifest file
292 ofstream os(manifest.c_str());
295 // prepare list of embedded file
296 EmbeddedFileList::iterator it = file_list_.begin();
297 EmbeddedFileList::iterator it_end = file_list_.end();
298 for (; it != it_end; ++it) {
299 if (it->valid() && it->embedded()) {
300 string file = it->availableFile(buffer_);
302 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
304 filenames.push_back(make_pair(file, it->inzipName()));
307 // add filename (.lyx) and manifest to filenames
308 filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
309 filenames.push_back(make_pair(manifest, "manifest.txt"));
310 // write a zip file with all these files. Write to a temp file first, to
311 // avoid messing up the original file in case something goes terribly wrong.
312 DocFileName zipfile(addName(buffer_->temppath(),
313 onlyFilename(changeExtension(
314 filename.toFilesystemEncoding(), ".zip"))));
316 ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
319 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
320 } catch (fs::filesystem_error const & fe) {
321 Alert::error(_("Save failure"),
322 bformat(_("Cannot create file %1$s.\n"
323 "Please check whether the directory exists and is writeable."),
324 from_utf8(filename.absFilename())));
325 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
331 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
333 EmbeddedFileList::const_iterator it = file_list_.begin();
334 EmbeddedFileList::const_iterator it_end = file_list_.end();
335 for (; it != it_end; ++it)
336 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)
338 return file_list_.end();
342 bool EmbeddedFiles::extract() const
344 EmbeddedFileList::const_iterator it = file_list_.begin();
345 EmbeddedFileList::const_iterator it_end = file_list_.end();
346 for (; it != it_end; ++it)
347 if (it->valid() && it->embedded())
348 if(!it->extract(buffer_))
354 bool EmbeddedFiles::updateFromExternalFile() 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->updateFromExternalFile(buffer_))
366 string const EmbeddedFiles::getInzipName(string const & abs_filename)
368 // register a new one, using relative file path as inzip_name
369 string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
370 from_utf8(buffer_->fileName())));
371 // if inzip_name is an absolute path, use filename only to avoid
372 // leaking of filesystem information in inzip_name
373 // The second case covers cases '../path/file' and '.'
374 if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
375 inzip_name = onlyFilename(abs_filename);
376 // if this name has been used...
377 // use _1_name, _2_name etc
378 string tmp = inzip_name;
379 EmbeddedFileList::iterator it;
380 EmbeddedFileList::iterator it_end = file_list_.end();
381 bool unique_name = false;
383 while (!unique_name) {
386 inzip_name = convert<string>(i) + "_" + tmp;
387 it = file_list_.begin();
388 for (; it != it_end; ++it)
389 if (it->inzipName() == inzip_name) {
399 istream & operator>> (istream & is, EmbeddedFiles & files)
405 istringstream itmp(tmp);
407 itmp.ignore(string("# LyX manifest version ").size());
411 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
416 if (tmp != "<manifest>") {
417 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
420 // manifest file may be messed up, be carefully
429 getline(is, inzip_name);
431 istringstream itmp(tmp);
436 if (tmp != "</file>") {
437 lyxerr << "Invalid manifest file, lacking </file>" << endl;
441 files.registerFile(fname, embed);
443 // the last line must be </manifest>
444 if (tmp != "</manifest>") {
445 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
452 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
454 // store a version so that operator >> can read later versions
455 // using version information.
456 os << "# lyx manifest version 1\n";
457 os << "<manifest>\n";
458 EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
459 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
460 for (; it != it_end; ++it) {
463 // use differnt lines to make reading easier.
465 // save the relative path
466 << to_utf8(makeRelPath(from_utf8(it->absFilename()),
467 from_utf8(files.buffer_->filePath()))) << '\n'
468 << it->inzipName() << '\n'
469 << it->embedded() << '\n'
472 os << "</manifest>\n";