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;
63 using support::prefixIs;
66 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
67 STATUS status, ParConstIterator const & pit)
68 : DocFileName(file, true), inzip_name_(inzip_name), status_(status),
69 valid_(true), par_it_(pit)
73 string EmbeddedFile::embeddedFile(Buffer const * buf) const
75 return addName(buf->temppath(), inzip_name_);
79 int const EmbeddedFile::parID() const
81 // some embedded file do not have a valid par iterator
82 return par_it_ == ParConstIterator() ? 0 : par_it_->id();
86 void EmbeddedFile::setParIter(ParConstIterator const & pit)
92 bool EmbeddedFiles::enabled() const
94 return buffer_->params().embedded;
98 void EmbeddedFiles::enable(bool flag)
100 // FIXME: there are much more to do here.
101 // If we enable embedding, it is maybe a good idea to copy embedded files
103 // if we disable embedding, embedded files need to be copied to their
104 // original positions.
105 if (enabled() != flag) {
106 // file will be changed
107 buffer_->markDirty();
108 buffer_->params().embedded = flag;
113 void EmbeddedFiles::registerFile(string const & filename,
114 EmbeddedFile::STATUS status, ParConstIterator const & pit)
116 string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
117 // try to find this file from the list
118 EmbeddedFileList::iterator it = file_list_.begin();
119 EmbeddedFileList::iterator it_end = file_list_.end();
120 for (; it != it_end; ++it)
121 if (it->absFilename() == abs_filename)
123 // find this filename
124 if (it != file_list_.end()) {
126 it->setStatus(status);
130 file_list_.push_back(EmbeddedFile(abs_filename,
131 getInzipName(abs_filename), status, pit));
135 void EmbeddedFiles::update()
137 // invalidate all files, obsolete files will then not be validated by the
138 // following document scan. These files will still be kept though, because
139 // they may be added later and their embedding status will be meaningful
140 // again (thinking of cut/paste of an InsetInclude).
141 EmbeddedFileList::iterator it = file_list_.begin();
142 EmbeddedFileList::iterator it_end = file_list_.end();
143 for (; it != it_end; ++it)
144 // if the status of a file is EMBEDDED, it will be there
145 // even if it is not referred by a document.
146 if (it->status() != EmbeddedFile::EMBEDDED)
149 ParIterator pit = buffer_->par_iterator_begin();
150 ParIterator pit_end = buffer_->par_iterator_end();
151 for (; pit != pit_end; ++pit) {
152 // For each paragraph, traverse its insets and register embedded files
153 InsetList::const_iterator iit = pit->insetlist.begin();
154 InsetList::const_iterator iit_end = pit->insetlist.end();
155 for (; iit != iit_end; ++iit) {
156 Inset & inset = *iit->inset;
157 inset.registerEmbeddedFiles(*buffer_, *this, pit);
160 LYXERR(Debug::FILES) << "Manifest updated: " << endl
162 << "End Manifest" << endl;
166 bool EmbeddedFiles::write(DocFileName const & filename)
168 // file in the temporary path has the content
169 string const content = FileName(addName(buffer_->temppath(),
170 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
172 // get a file list and write a manifest file
173 vector<pair<string, string> > filenames;
174 string const manifest = FileName(
175 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
177 // write a manifest file
178 ofstream os(manifest.c_str());
181 // prepare list of embedded file
182 EmbeddedFileList::iterator it = file_list_.begin();
183 EmbeddedFileList::iterator it_end = file_list_.end();
184 for (; it != it_end; ++it) {
185 if (it->valid() && it->embedded()) {
186 string ext_file = it->absFilename();
187 string emb_file = it->embeddedFile(buffer_);
188 if (it->status() == EmbeddedFile::AUTO) {
189 // use external file first
190 if (fs::exists(ext_file))
191 filenames.push_back(make_pair(ext_file, it->inzipName()));
192 else if (fs::exists(emb_file))
193 filenames.push_back(make_pair(emb_file, it->inzipName()));
195 lyxerr << "File " << ext_file << " does not exist. Skip embedding it. " << endl;
196 } else if (it->status() == EmbeddedFile::EMBEDDED) {
197 // use embedded file first
198 if (fs::exists(emb_file))
199 filenames.push_back(make_pair(emb_file, it->inzipName()));
200 else if (fs::exists(ext_file))
201 filenames.push_back(make_pair(ext_file, it->inzipName()));
203 lyxerr << "File " << ext_file << " does not exist. Skip embedding it. " << endl;
207 // add filename (.lyx) and manifest to filenames
208 filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
209 filenames.push_back(make_pair(manifest, "manifest.txt"));
210 // write a zip file with all these files. Write to a temp file first, to
211 // avoid messing up the original file in case something goes terribly wrong.
212 DocFileName zipfile(addName(buffer_->temppath(),
213 onlyFilename(changeExtension(
214 filename.toFilesystemEncoding(), ".zip"))));
216 zipFiles(zipfile, filenames);
219 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
220 } catch (fs::filesystem_error const & fe) {
221 Alert::error(_("Save failure"),
222 bformat(_("Cannot create file %1$s.\n"
223 "Please check whether the directory exists and is writeable."),
224 from_utf8(filename.absFilename())));
225 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
231 string const EmbeddedFiles::getInzipName(string const & abs_filename)
233 // register a new one, using relative file path as inzip_name
234 string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
235 from_utf8(buffer_->fileName())));
236 // if inzip_name is an absolute path, use filename only to avoid
237 // leaking of filesystem information in inzip_name
238 if (absolutePath(inzip_name) || prefixIs(inzip_name, ".."))
239 inzip_name = onlyFilename(inzip_name);
240 // if this name has been used...
241 // use _1_name, _2_name etc
242 string tmp = inzip_name;
243 EmbeddedFileList::iterator it;
244 EmbeddedFileList::iterator it_end = file_list_.end();
245 bool unique_name = false;
246 while (!unique_name) {
250 inzip_name = convert<string>(i) + "_" + tmp;
251 it = file_list_.begin();
252 for (; it != it_end; ++it)
253 if (it->inzipName() == inzip_name) {
263 istream & operator>> (istream & is, EmbeddedFiles & files)
269 istringstream itmp(tmp);
271 itmp.ignore(string("# LyX manifest version ").size());
275 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
280 if (tmp != "<manifest>") {
281 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
284 // manifest file may be messed up, be carefully
293 getline(is, inzip_name);
295 istringstream itmp(tmp);
300 if (tmp != "</file>") {
301 lyxerr << "Invalid manifest file, lacking </file>" << endl;
305 files.registerFile(fname, static_cast<EmbeddedFile::STATUS>(status));
307 // the last line must be </manifest>
308 if (tmp != "</manifest>") {
309 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
316 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
318 // store a version so that operator >> can read later versions
319 // using version information.
320 os << "# lyx manifest version 1\n";
321 os << "<manifest>\n";
322 EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
323 EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
324 for (; it != it_end; ++it) {
327 // use differnt lines to make reading easier.
329 // save the relative path
330 << to_utf8(makeRelPath(from_utf8(it->absFilename()),
331 from_utf8(files.buffer_->filePath()))) << '\n'
332 << it->inzipName() << '\n'
333 << it->status() << '\n'
336 os << "</manifest>\n";