]> git.lyx.org Git - features.git/blob - src/EmbeddedFiles.cpp
Embedding patch two: read/write embedded files'
[features.git] / src / EmbeddedFiles.cpp
1 // -*- C++ -*-
2 /**
3  * \file EmbeddedFiles.cpp
4  * This file is part of LyX, the document processor.
5  * Licence details can be found in the file COPYING.
6  *
7  * \author Bo Peng
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  */
12
13 #include <config.h>
14
15 #include "EmbeddedFiles.h"
16 #include "Buffer.h"
17 #include "BufferParams.h"
18 #include "Paragraph.h"
19 #include "ParIterator.h"
20 #include "debug.h"
21 #include "gettext.h"
22 #include "Format.h"
23
24 #include "frontends/alert.h"
25
26 #include <boost/filesystem/operations.hpp>
27
28 #include "support/filetools.h"
29 #include "support/fs_extras.h"
30 #include "support/convert.h"
31
32 #include <sstream>
33 #include <fstream>
34 #include <utility>
35
36 using std::ofstream;
37 using std::endl;
38 using std::vector;
39 using std::string;
40 using std::pair;
41 using std::make_pair;
42 using std::istream;
43 using std::ostream;
44 using std::getline;
45 using std::istringstream;
46
47 namespace lyx {
48
49 namespace fs = boost::filesystem;
50 namespace Alert = frontend::Alert;
51
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
64
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)
69 {}
70
71
72 string EmbeddedFile::embeddedFile(Buffer const * buf) const
73 {
74         return addName(buf->temppath(), inzip_name_);
75 }
76
77
78 void EmbeddedFile::setParIter(ParConstIterator const & pit)
79 {
80         par_it_ = pit;
81 }
82
83
84 bool EmbeddedFiles::enabled() const
85 {
86         return buffer_->params().embedded;
87 }
88
89
90 void EmbeddedFiles::enable(bool flag)
91 {
92         if (enabled() != flag) {
93                 // file will be changed
94                 buffer_->markDirty();
95                 buffer_->params().embedded = flag;
96         }
97 }
98
99
100 void EmbeddedFiles::registerFile(string const & filename,
101         EmbeddedFile::STATUS status, ParConstIterator const & pit)
102 {
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)
109                         break;
110         // find this filename
111         if (it != file_list_.end()) {
112                 it->setParIter(pit);
113                 it->setStatus(status);
114                 it->validate();
115                 return;
116         }
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)) {
127                 size_t i = 1;
128                 string tmp = inzip_name;
129                 do {
130                         inzip_name = convert<string>(i) + "_" + tmp;
131                 } while (!validInzipName(inzip_name));
132         }
133         file_list_.push_back(EmbeddedFile(abs_filename, inzip_name, status, pit));
134 }
135
136
137 void EmbeddedFiles::update()
138 {
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)
146                 it->invalidate();
147
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);
157                 }
158         }
159         LYXERR(Debug::FILES) << "Manifest updated: " << endl
160                 << *this
161                 << "End Manifest" << endl;
162 }
163
164
165 bool EmbeddedFiles::write(DocFileName const & filename)
166 {
167         // file in the temporary path has the content
168         string const content = FileName(addName(buffer_->temppath(),
169                 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
170
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();
175
176         // write a manifest file
177         ofstream os(manifest.c_str());
178         os << *this;
179         os.close();
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()));
191                         else
192                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
193                 }
194         }
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"))));
203
204         zipFiles(zipfile, filenames);
205         // copy file back
206         try {
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;
214         }
215         return true;
216 }
217
218
219 bool EmbeddedFiles::validInzipName(string const & name)
220 {
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)
225                                 return false;
226         return true;
227 }
228
229
230 istream & operator>> (istream & is, EmbeddedFiles & files)
231 {
232         files.clear();
233         string tmp;
234         getline(is, tmp);
235         // get version
236         istringstream itmp(tmp);
237         int version;
238         itmp.ignore(string("# LyX manifest version ").size());
239         itmp >> version;
240
241         if (version != 1) {
242                 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
243                 return is;
244         }
245
246         getline(is, tmp);
247         if (tmp != "<manifest>") {
248                 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
249                 return is;
250         }
251         // manifest file may be messed up, be carefully
252         while (is.good()) {
253                 getline(is, tmp);
254                 if (tmp != "<file>")
255                         break;
256
257                 string fname;
258                 getline(is, fname);
259                 string inzip_name;
260                 getline(is, inzip_name);
261                 getline(is, tmp);
262                 istringstream itmp(tmp);
263                 int status;
264                 itmp >> status;
265
266                 getline(is, tmp);
267                 if (tmp != "</file>") {
268                         lyxerr << "Invalid manifest file, lacking </file>" << endl;
269                         break;
270                 }
271
272                 files.registerFile(fname, static_cast<EmbeddedFile::STATUS>(status));
273         };
274         // the last line must be </manifest>
275         if (tmp != "</manifest>") {
276                 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
277                 return is;
278         }
279         return is;
280 }
281
282
283 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
284 {
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) {
292                 if (!it->valid())
293                         continue;
294                 // use differnt lines to make reading easier.
295                 os << "<file>\n"
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'
301                         << "</file>\n";
302         }
303         os << "</manifest>\n";
304         return os;
305 }
306
307 }