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 valid_(true), par_it_(pit)
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 // FIXME: there are much more to do here.
93 // If we enable embedding, it is maybe a good idea to copy embedded files
95 // if we disable embedding, embedded files need to be copied to their
96 // original positions.
97 if (enabled() != flag) {
98 // file will be changed
100 buffer_->params().embedded = flag;
105 void EmbeddedFiles::registerFile(string const & filename,
106 EmbeddedFile::STATUS status, ParConstIterator const & pit)
108 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
109 // try to find this file from the list
110 EmbeddedFileList::iterator it = file_list_.begin();
111 EmbeddedFileList::iterator it_end = file_list_.end();
112 for (; it != it_end; ++it)
113 if (it->absFilename() == abs_filename)
115 // find this filename
116 if (it != file_list_.end()) {
118 it->setStatus(status);
122 // register a new one, using relative file path as inzip_name
123 string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
124 from_utf8(buffer_->fileName())));
125 // if inzip_name is an absolute path, use filename only to avoid
126 // leaking of filesystem information in inzip_name
127 if (absolutePath(inzip_name))
128 inzip_name = onlyFilename(inzip_name);
129 // if this name has been used...
130 // use _1_name, _2_name etc
131 if (!validInzipName(inzip_name)) {
133 string tmp = inzip_name;
135 inzip_name = convert<string>(i) + "_" + tmp;
136 } while (!validInzipName(inzip_name));
138 file_list_.push_back(EmbeddedFile(abs_filename, inzip_name, status, pit));
142 void EmbeddedFiles::update()
144 // invalidate all files, obsolete files will then not be validated by the
145 // following document scan. These files will still be kept though, because
146 // they may be added later and their embedding status will be meaningful
147 // again (thinking of cut/paste of an InsetInclude).
148 EmbeddedFileList::iterator it = file_list_.begin();
149 EmbeddedFileList::iterator it_end = file_list_.end();
150 for (; it != it_end; ++it)
153 ParIterator pit = buffer_->par_iterator_begin();
154 ParIterator pit_end = buffer_->par_iterator_end();
155 for (; pit != pit_end; ++pit) {
156 // For each paragraph, traverse its insets and register embedded files
157 InsetList::const_iterator iit = pit->insetlist.begin();
158 InsetList::const_iterator iit_end = pit->insetlist.end();
159 for (; iit != iit_end; ++iit) {
160 Inset & inset = *iit->inset;
161 inset.registerEmbeddedFiles(*buffer_, *this, pit);
164 LYXERR(Debug::FILES) << "Manifest updated: " << endl
166 << "End Manifest" << endl;
170 bool EmbeddedFiles::write(DocFileName const & filename)
172 // file in the temporary path has the content
173 string const content = FileName(addName(buffer_->temppath(),
174 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
176 // get a file list and write a manifest file
177 vector<pair<string, string> > filenames;
178 string const manifest = FileName(
179 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
181 // write a manifest file
182 ofstream os(manifest.c_str());
185 // prepare list of embedded file
186 EmbeddedFileList::iterator it = file_list_.begin();
187 EmbeddedFileList::iterator it_end = file_list_.end();
188 for (; it != it_end; ++it) {
189 if (it->valid() && it->embedded()) {
190 // use external file if possible
191 if (it->status() != EmbeddedFile::EMBEDDED && fs::exists(it->absFilename()))
192 filenames.push_back(make_pair(it->absFilename(), it->inzipName()));
193 // use embedded file (AUTO or EMBEDDED mode)
194 else if(fs::exists(it->embeddedFile(buffer_)))
195 filenames.push_back(make_pair(it->embeddedFile(buffer_), it->inzipName()));
197 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
200 // add filename (.lyx) and manifest to filenames
201 filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
202 filenames.push_back(make_pair(manifest, "manifest.txt"));
203 // write a zip file with all these files. Write to a temp file first, to
204 // avoid messing up the original file in case something goes terribly wrong.
205 DocFileName zipfile(addName(buffer_->temppath(),
206 onlyFilename(changeExtension(
207 filename.toFilesystemEncoding(), ".zip"))));
209 zipFiles(zipfile, filenames);
212 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
213 } catch (fs::filesystem_error const & fe) {
214 Alert::error(_("Save failure"),
215 bformat(_("Cannot create file %1$s.\n"
216 "Please check whether the directory exists and is writeable."),
217 from_utf8(filename.absFilename())));
218 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
224 string EmbeddedFiles::filename(size_t idx) const
226 return (file_list_.begin() + idx)->absFilename();
230 EmbeddedFile::STATUS EmbeddedFiles::status(size_t idx) const
232 return (file_list_.begin() + idx)->status();
236 void EmbeddedFiles::setStatus(size_t idx, EmbeddedFile::STATUS status)
238 if ((file_list_.begin() + idx)->status() != status) {
239 // file will be changed
240 buffer_->markDirty();
241 (file_list_.begin() + idx)->setStatus(status);
246 bool EmbeddedFiles::validInzipName(string const & name)
248 EmbeddedFileList::iterator it = file_list_.begin();
249 EmbeddedFileList::iterator it_end = file_list_.end();
250 for (; it != it_end; ++it)
251 if (it->inzipName() == name)
257 istream & operator>> (istream & is, EmbeddedFiles & files)
263 istringstream itmp(tmp);
265 itmp.ignore(string("# LyX manifest version ").size());
269 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
274 if (tmp != "<manifest>") {
275 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
278 // manifest file may be messed up, be carefully
287 getline(is, inzip_name);
289 istringstream itmp(tmp);
294 if (tmp != "</file>") {
295 lyxerr << "Invalid manifest file, lacking </file>" << endl;
299 files.registerFile(fname, static_cast<EmbeddedFile::STATUS>(status));
301 // the last line must be </manifest>
302 if (tmp != "</manifest>") {
303 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
310 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
312 // store a version so that operator >> can read later versions
313 // using version information.
314 os << "# lyx manifest version 1\n";
315 os << "<manifest>\n";
316 EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
317 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
318 for (; it != it_end; ++it) {
321 // use differnt lines to make reading easier.
323 // save the relative path
324 << to_utf8(makeRelPath(from_utf8(it->absFilename()),
325 from_utf8(files.buffer_->filePath()))) << '\n'
326 << it->inzipName() << '\n'
327 << it->status() << '\n'
330 os << "</manifest>\n";