]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Embedding feature patch 3: basic gui support
[lyx.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                 valid_(true), par_it_(pit)
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         // FIXME: there are much more to do here. 
93         // If we enable embedding, it is maybe a good idea to copy embedded files
94         // to temppath()
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
99                 buffer_->markDirty();
100                 buffer_->params().embedded = flag;
101         }
102 }
103
104
105 void EmbeddedFiles::registerFile(string const & filename,
106         EmbeddedFile::STATUS status, ParConstIterator const & pit)
107 {
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)
114                         break;
115         // find this filename
116         if (it != file_list_.end()) {
117                 it->setParIter(pit);
118                 it->setStatus(status);
119                 it->validate();
120                 return;
121         }
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)) {
132                 size_t i = 1;
133                 string tmp = inzip_name;
134                 do {
135                         inzip_name = convert<string>(i) + "_" + tmp;
136                 } while (!validInzipName(inzip_name));
137         }
138         file_list_.push_back(EmbeddedFile(abs_filename, inzip_name, status, pit));
139 }
140
141
142 void EmbeddedFiles::update()
143 {
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)
151                 it->invalidate();
152
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);
162                 }
163         }
164         LYXERR(Debug::FILES) << "Manifest updated: " << endl
165                 << *this
166                 << "End Manifest" << endl;
167 }
168
169
170 bool EmbeddedFiles::write(DocFileName const & filename)
171 {
172         // file in the temporary path has the content
173         string const content = FileName(addName(buffer_->temppath(),
174                 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
175
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();
180
181         // write a manifest file
182         ofstream os(manifest.c_str());
183         os << *this;
184         os.close();
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()));
196                         else
197                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
198                 }
199         }
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"))));
208
209         zipFiles(zipfile, filenames);
210         // copy file back
211         try {
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;
219         }
220         return true;
221 }
222
223
224 string EmbeddedFiles::filename(size_t idx) const
225 {
226         return (file_list_.begin() + idx)->absFilename();
227 }
228
229
230 EmbeddedFile::STATUS EmbeddedFiles::status(size_t idx) const
231 {
232         return (file_list_.begin() + idx)->status();
233 }
234
235
236 void EmbeddedFiles::setStatus(size_t idx, EmbeddedFile::STATUS status)
237 {
238         if ((file_list_.begin() + idx)->status() != status) {
239                 // file will be changed
240                 buffer_->markDirty();
241                 (file_list_.begin() + idx)->setStatus(status);
242         }
243 }
244
245
246 bool EmbeddedFiles::validInzipName(string const & name)
247 {
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)
252                                 return false;
253         return true;
254 }
255
256
257 istream & operator>> (istream & is, EmbeddedFiles & files)
258 {
259         files.clear();
260         string tmp;
261         getline(is, tmp);
262         // get version
263         istringstream itmp(tmp);
264         int version;
265         itmp.ignore(string("# LyX manifest version ").size());
266         itmp >> version;
267
268         if (version != 1) {
269                 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
270                 return is;
271         }
272
273         getline(is, tmp);
274         if (tmp != "<manifest>") {
275                 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
276                 return is;
277         }
278         // manifest file may be messed up, be carefully
279         while (is.good()) {
280                 getline(is, tmp);
281                 if (tmp != "<file>")
282                         break;
283
284                 string fname;
285                 getline(is, fname);
286                 string inzip_name;
287                 getline(is, inzip_name);
288                 getline(is, tmp);
289                 istringstream itmp(tmp);
290                 int status;
291                 itmp >> status;
292
293                 getline(is, tmp);
294                 if (tmp != "</file>") {
295                         lyxerr << "Invalid manifest file, lacking </file>" << endl;
296                         break;
297                 }
298
299                 files.registerFile(fname, static_cast<EmbeddedFile::STATUS>(status));
300         };
301         // the last line must be </manifest>
302         if (tmp != "</manifest>") {
303                 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
304                 return is;
305         }
306         return is;
307 }
308
309
310 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
311 {
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) {
319                 if (!it->valid())
320                         continue;
321                 // use differnt lines to make reading easier.
322                 os << "<file>\n"
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'
328                         << "</file>\n";
329         }
330         os << "</manifest>\n";
331         return os;
332 }
333
334 }