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"
45 using std::istringstream;
49 namespace fs = boost::filesystem;
50 namespace Alert = frontend::Alert;
52 using support::FileName;
53 using support::DocFileName;
54 using support::makeAbsPath;
55 using support::addName;
56 using support::onlyPath;
57 using support::absolutePath;
58 using support::onlyFilename;
59 using support::makeRelPath;
60 using support::changeExtension;
61 using support::bformat;
62 using support::zipFiles;
65 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
66 STATUS status, ParConstIterator const & pit)
67 : DocFileName(file, true), inzip_name_(inzip_name), status_(status),
68 par_it_(pit), valid_(true)
72 string EmbeddedFile::embeddedFile(Buffer const * buf) const
74 return addName(buf->temppath(), inzip_name_);
78 void EmbeddedFile::setParIter(ParConstIterator const & pit)
84 bool EmbeddedFiles::enabled() const
86 return buffer_->params().embedded;
90 void EmbeddedFiles::enable(bool flag)
92 if (enabled() != flag) {
93 // file will be changed
95 buffer_->params().embedded = flag;
100 void EmbeddedFiles::registerFile(string const & filename,
101 EmbeddedFile::STATUS status, ParConstIterator const & pit)
103 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
104 // try to find this file from the list
105 EmbeddedFileList::iterator it = file_list_.begin();
106 EmbeddedFileList::iterator it_end = file_list_.end();
107 for (; it != it_end; ++it)
108 if (it->absFilename() == abs_filename)
110 // find this filename
111 if (it != file_list_.end()) {
113 it->setStatus(status);
117 // register a new one, using relative file path as inzip_name
118 string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
119 from_utf8(buffer_->fileName())));
120 // if inzip_name is an absolute path, use filename only to avoid
121 // leaking of filesystem information in inzip_name
122 if (absolutePath(inzip_name))
123 inzip_name = onlyFilename(inzip_name);
124 // if this name has been used...
125 // use _1_name, _2_name etc
126 if (!validInzipName(inzip_name)) {
128 string tmp = inzip_name;
130 inzip_name = convert<string>(i) + "_" + tmp;
131 } while (!validInzipName(inzip_name));
133 file_list_.push_back(EmbeddedFile(abs_filename, inzip_name, status, pit));
137 void EmbeddedFiles::update()
139 // invalidate all files, obsolete files will then not be validated by the
140 // following document scan. These files will still be kept though, because
141 // they may be added later and their embedding status will be meaningful
142 // again (thinking of cut/paste of an InsetInclude).
143 EmbeddedFileList::iterator it = file_list_.begin();
144 EmbeddedFileList::iterator it_end = file_list_.end();
145 for (; it != it_end; ++it)
148 ParIterator pit = buffer_->par_iterator_begin();
149 ParIterator pit_end = buffer_->par_iterator_end();
150 for (; pit != pit_end; ++pit) {
151 // For each paragraph, traverse its insets and register embedded files
152 InsetList::const_iterator iit = pit->insetlist.begin();
153 InsetList::const_iterator iit_end = pit->insetlist.end();
154 for (; iit != iit_end; ++iit) {
155 Inset & inset = *iit->inset;
156 inset.registerEmbeddedFiles(*buffer_, *this, pit);
159 LYXERR(Debug::FILES) << "Manifest updated: " << endl
161 << "End Manifest" << endl;
165 bool EmbeddedFiles::write(DocFileName const & filename)
167 // file in the temporary path has the content
168 string const content = FileName(addName(buffer_->temppath(),
169 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
171 // get a file list and write a manifest file
172 vector<pair<string, string> > filenames;
173 string const manifest = FileName(
174 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
176 // write a manifest file
177 ofstream os(manifest.c_str());
180 // prepare list of embedded file
181 EmbeddedFileList::iterator it = file_list_.begin();
182 EmbeddedFileList::iterator it_end = file_list_.end();
183 for (; it != it_end; ++it) {
184 if (it->valid() && it->embedded()) {
185 // use external file if possible
186 if (it->status() != EmbeddedFile::EMBEDDED && fs::exists(it->absFilename()))
187 filenames.push_back(make_pair(it->absFilename(), it->inzipName()));
188 // use embedded file (AUTO or EMBEDDED mode)
189 else if(fs::exists(it->embeddedFile(buffer_)))
190 filenames.push_back(make_pair(it->embeddedFile(buffer_), it->inzipName()));
192 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
195 // add filename (.lyx) and manifest to filenames
196 filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
197 filenames.push_back(make_pair(manifest, "manifest.txt"));
198 // write a zip file with all these files. Write to a temp file first, to
199 // avoid messing up the original file in case something goes terribly wrong.
200 DocFileName zipfile(addName(buffer_->temppath(),
201 onlyFilename(changeExtension(
202 filename.toFilesystemEncoding(), ".zip"))));
204 zipFiles(zipfile, filenames);
207 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
208 } catch (fs::filesystem_error const & fe) {
209 Alert::error(_("Save failure"),
210 bformat(_("Cannot create file %1$s.\n"
211 "Please check whether the directory exists and is writeable."),
212 from_utf8(filename.absFilename())));
213 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
219 bool EmbeddedFiles::validInzipName(string const & name)
221 EmbeddedFileList::iterator it = file_list_.begin();
222 EmbeddedFileList::iterator it_end = file_list_.end();
223 for (; it != it_end; ++it)
224 if (it->inzipName() == name)
230 istream & operator>> (istream & is, EmbeddedFiles & files)
236 istringstream itmp(tmp);
238 itmp.ignore(string("# LyX manifest version ").size());
242 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
247 if (tmp != "<manifest>") {
248 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
251 // manifest file may be messed up, be carefully
260 getline(is, inzip_name);
262 istringstream itmp(tmp);
267 if (tmp != "</file>") {
268 lyxerr << "Invalid manifest file, lacking </file>" << endl;
272 files.registerFile(fname, static_cast<EmbeddedFile::STATUS>(status));
274 // the last line must be </manifest>
275 if (tmp != "</manifest>") {
276 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
283 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
285 // store a version so that operator >> can read later versions
286 // using version information.
287 os << "# lyx manifest version 1\n";
288 os << "<manifest>\n";
289 EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
290 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
291 for (; it != it_end; ++it) {
294 // use differnt lines to make reading easier.
296 // save the relative path
297 << to_utf8(makeRelPath(from_utf8(it->absFilename()),
298 from_utf8(files.buffer_->filePath()))) << '\n'
299 << it->inzipName() << '\n'
300 << it->status() << '\n'
303 os << "</manifest>\n";