]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Embedding: remove AUTO feature, barely make it compile
[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 #include "support/lyxlib.h"
32 #include "support/lstrings.h"
33
34 #include <sstream>
35 #include <fstream>
36 #include <utility>
37
38 using std::ofstream;
39 using std::endl;
40 using std::vector;
41 using std::string;
42 using std::pair;
43 using std::make_pair;
44 using std::istream;
45 using std::ostream;
46 using std::getline;
47 using std::istringstream;
48
49 namespace lyx {
50
51 namespace fs = boost::filesystem;
52 namespace Alert = frontend::Alert;
53
54 using support::FileName;
55 using support::DocFileName;
56 using support::makeAbsPath;
57 using support::addName;
58 using support::onlyPath;
59 using support::absolutePath;
60 using support::onlyFilename;
61 using support::makeRelPath;
62 using support::changeExtension;
63 using support::bformat;
64 using support::prefixIs;
65 using support::sum;
66 using support::makedir;
67
68
69 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
70         bool embed, ParConstIterator const & pit)
71         : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
72                 valid_(true), par_it_(pit)
73 {}
74
75
76 string EmbeddedFile::embeddedFile(Buffer const * buf) const
77 {
78         return addName(buf->temppath(), inzip_name_);
79 }
80
81
82 int const EmbeddedFile::parID() const
83 {
84         // some embedded file do not have a valid par iterator
85         return par_it_ == ParConstIterator() ? 0 : par_it_->id();
86 }
87
88
89 void EmbeddedFile::setParIter(ParConstIterator const & pit)
90 {
91         par_it_ = pit;
92 }
93
94
95 string EmbeddedFile::availableFile(Buffer const * buf) const
96 {
97         if (embedded_)
98                 return embeddedFile(buf);
99         else
100                 return absFilename();
101 }
102
103
104 bool EmbeddedFile::extract(Buffer const * buf) const
105 {
106         if (!embedded_)
107                 return true;
108
109         string ext_file = absFilename();
110         string emb_file = embeddedFile(buf);
111         bool copyFile = false;
112         // both files exist, are different
113         if (fs::exists(ext_file) && fs::exists(emb_file)
114                 && sum(*this) != sum(FileName(emb_file))) {
115                 int const ret = Alert::prompt(
116                         _("Overwrite external file?"),
117                         bformat(_("External file %1$s already exists, do you want to overwrite it"),
118                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
119                 copyFile = ret == 0;
120         }
121         // copy file in the previous case, and a new case
122         if (copyFile || (!fs::exists(ext_file) && fs::exists(emb_file))) {
123                 try {
124                         // need to make directory?
125                         string path = onlyPath(ext_file);
126                         if (!fs::is_directory(path))
127                                 makedir(const_cast<char*>(path.c_str()), 0755);
128                         fs::copy_file(emb_file, ext_file, false);
129                         return true;
130                 } catch (fs::filesystem_error const & fe) {
131                         Alert::error(_("Copy file failure"),
132                                  bformat(_("Cannot copy file %1$s to %2$s.\n"
133                                            "Please check whether the directory exists and is writeable."),
134                                                 from_utf8(emb_file), from_utf8(ext_file)));
135                         LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
136                 }
137         }
138         return false;
139 }
140
141
142 bool EmbeddedFile::embed(Buffer const * buf)
143 {
144         string ext_file = absFilename();
145         string emb_file = embeddedFile(buf);
146         bool copyFile = false;
147         // both files exist, are different
148         if (fs::exists(ext_file) && fs::exists(emb_file)
149                 && sum(*this) != sum(FileName(emb_file))) {
150                 int const ret = Alert::prompt(
151                         _("Update embedded file?"),
152                         bformat(_("Embeddedl file %1$s already exists, do you want to overwrite it"),
153                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
154                 copyFile = ret == 0;
155         }
156         // copy file in the previous case, and a new case
157         if (copyFile || (fs::exists(ext_file) && !fs::exists(emb_file))) {
158                 try {
159                         // need to make directory?
160                         string path = onlyPath(emb_file);
161                         if (!fs::is_directory(path))
162                                 makedir(const_cast<char*>(path.c_str()), 0755);
163                         fs::copy_file(ext_file, emb_file, false);
164                         embedded_ = true;
165                 } catch (fs::filesystem_error const & fe) {
166                         Alert::error(_("Copy file failure"),
167                                  bformat(_("Cannot copy file %1$s to %2$s.\n"
168                                            "Please check whether the directory exists and is writeable."),
169                                                 from_utf8(ext_file), from_utf8(emb_file)));
170                         LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
171                         embedded_ = false;
172                 }
173         }
174         return embedded_;
175 }
176
177
178 bool EmbeddedFiles::enabled() const
179 {
180         return buffer_->params().embedded;
181 }
182
183
184 void EmbeddedFiles::enable(bool flag)
185 {
186         if (enabled() != flag) {
187                 // if disable embedding, first extract all embedded files
188                 if (flag || (!flag && extractAll())) {
189                         // file will be changed
190                         buffer_->markDirty();
191                         buffer_->params().embedded = flag;
192                 }
193         }
194 }
195
196
197 void EmbeddedFiles::registerFile(string const & filename,
198         bool embed, ParConstIterator const & pit)
199 {
200         string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
201         // try to find this file from the list
202         EmbeddedFileList::iterator it = file_list_.begin();
203         EmbeddedFileList::iterator it_end = file_list_.end();
204         for (; it != it_end; ++it)
205                 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
206                         break;
207         // find this filename
208         if (it != file_list_.end()) {
209                 it->setParIter(pit);
210                 it->embed(buffer_);
211                 it->validate();
212                 return;
213         }
214         file_list_.push_back(EmbeddedFile(abs_filename, 
215                 getInzipName(abs_filename), embed, pit));
216 }
217
218
219 void EmbeddedFiles::update()
220 {
221         // invalidate all files, obsolete files will then not be validated by the
222         // following document scan. These files will still be kept though, because
223         // they may be added later and their embedding status will be meaningful
224         // again (thinking of cut/paste of an InsetInclude).
225         EmbeddedFileList::iterator it = file_list_.begin();
226         EmbeddedFileList::iterator it_end = file_list_.end();
227         for (; it != it_end; ++it)
228                 it->invalidate();
229
230         ParIterator pit = buffer_->par_iterator_begin();
231         ParIterator pit_end = buffer_->par_iterator_end();
232         for (; pit != pit_end; ++pit) {
233                 // For each paragraph, traverse its insets and register embedded files
234                 InsetList::const_iterator iit = pit->insetlist.begin();
235                 InsetList::const_iterator iit_end = pit->insetlist.end();
236                 for (; iit != iit_end; ++iit) {
237                         Inset & inset = *iit->inset;
238                         inset.registerEmbeddedFiles(*buffer_, *this, pit);
239                 }
240         }
241         LYXERR(Debug::FILES) << "Manifest updated: " << endl
242                 << *this
243                 << "End Manifest" << endl;
244 }
245
246
247 bool EmbeddedFiles::write(DocFileName const & filename)
248 {
249         // file in the temporary path has the content
250         string const content = FileName(addName(buffer_->temppath(),
251                 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
252
253         // get a file list and write a manifest file
254         vector<pair<string, string> > filenames;
255         string const manifest = FileName(
256                 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
257
258         // write a manifest file
259         ofstream os(manifest.c_str());
260         os << *this;
261         os.close();
262         // prepare list of embedded file
263         EmbeddedFileList::iterator it = file_list_.begin();
264         EmbeddedFileList::iterator it_end = file_list_.end();
265         for (; it != it_end; ++it) {
266                 if (it->valid() && it->embedded()) {
267                         string file = it->availableFile(buffer_);
268                         if (file.empty())
269                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
270                         else
271                                 filenames.push_back(make_pair(file, it->inzipName()));
272                 }
273         }
274         // add filename (.lyx) and manifest to filenames
275         filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
276         filenames.push_back(make_pair(manifest, "manifest.txt"));
277         // write a zip file with all these files. Write to a temp file first, to
278         // avoid messing up the original file in case something goes terribly wrong.
279         DocFileName zipfile(addName(buffer_->temppath(),
280                 onlyFilename(changeExtension(
281                         filename.toFilesystemEncoding(), ".zip"))));
282
283         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
284         // copy file back
285         try {
286                 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
287         } catch (fs::filesystem_error const & fe) {
288                 Alert::error(_("Save failure"),
289                                  bformat(_("Cannot create file %1$s.\n"
290                                            "Please check whether the directory exists and is writeable."),
291                                          from_utf8(filename.absFilename())));
292                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
293         }
294         return true;
295 }
296
297
298 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
299 {
300         EmbeddedFileList::const_iterator it = file_list_.begin();
301         EmbeddedFileList::const_iterator it_end = file_list_.end();
302         for (; it != it_end; ++it)
303                 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)     
304                         return it;
305         return file_list_.end();
306 }
307
308
309 bool EmbeddedFiles::extractAll() const
310 {
311         EmbeddedFileList::const_iterator it = file_list_.begin();
312         EmbeddedFileList::const_iterator it_end = file_list_.end();
313         // FIXME: the logic here is hard to decide, we should allow cancel for
314         // 'do not overwrite' this file, and cancel for 'cancel extract all files'.
315         // I am not sure how to do this now.
316         for (; it != it_end; ++it)
317                 if (it->valid() && it->embedded())
318                         it->extract(buffer_);
319         return true;
320 }
321
322
323 string const EmbeddedFiles::getInzipName(string const & abs_filename)
324 {
325         // register a new one, using relative file path as inzip_name
326         string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
327                 from_utf8(buffer_->fileName())));
328         // if inzip_name is an absolute path, use filename only to avoid
329         // leaking of filesystem information in inzip_name
330         // The second case covers cases '../path/file' and '.'
331         if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
332                 inzip_name = onlyFilename(abs_filename);
333         // if this name has been used...
334         // use _1_name, _2_name etc
335         string tmp = inzip_name;
336         EmbeddedFileList::iterator it;
337         EmbeddedFileList::iterator it_end = file_list_.end();
338         bool unique_name = false;
339         size_t i = 0;
340         while (!unique_name) {
341                 unique_name = true;
342                 if (i > 0)
343                         inzip_name = convert<string>(i) + "_" + tmp;
344                 it = file_list_.begin();
345                 for (; it != it_end; ++it)
346                         if (it->inzipName() == inzip_name) {
347                                 unique_name = false;
348                                 ++i;
349                                 break;
350                         }
351         }
352         return inzip_name;
353 }
354
355
356 istream & operator>> (istream & is, EmbeddedFiles & files)
357 {
358         files.clear();
359         string tmp;
360         getline(is, tmp);
361         // get version
362         istringstream itmp(tmp);
363         int version;
364         itmp.ignore(string("# LyX manifest version ").size());
365         itmp >> version;
366
367         if (version != 1) {
368                 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
369                 return is;
370         }
371
372         getline(is, tmp);
373         if (tmp != "<manifest>") {
374                 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
375                 return is;
376         }
377         // manifest file may be messed up, be carefully
378         while (is.good()) {
379                 getline(is, tmp);
380                 if (tmp != "<file>")
381                         break;
382
383                 string fname;
384                 getline(is, fname);
385                 string inzip_name;
386                 getline(is, inzip_name);
387                 getline(is, tmp);
388                 istringstream itmp(tmp);
389                 int embed;
390                 itmp >> embed;
391
392                 getline(is, tmp);
393                 if (tmp != "</file>") {
394                         lyxerr << "Invalid manifest file, lacking </file>" << endl;
395                         break;
396                 }
397
398                 files.registerFile(fname, embed);
399         };
400         // the last line must be </manifest>
401         if (tmp != "</manifest>") {
402                 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
403                 return is;
404         }
405         return is;
406 }
407
408
409 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
410 {
411         // store a version so that operator >> can read later versions
412         // using version information.
413         os << "# lyx manifest version 1\n";
414         os << "<manifest>\n";
415         EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
416         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
417         for (; it != it_end; ++it) {
418                 if (!it->valid())
419                         continue;
420                 // use differnt lines to make reading easier.
421                 os << "<file>\n"
422                         // save the relative path
423                         << to_utf8(makeRelPath(from_utf8(it->absFilename()),
424                                 from_utf8(files.buffer_->filePath()))) << '\n'
425                         << it->inzipName() << '\n'
426                         << it->embedded() << '\n'
427                         << "</file>\n";
428         }
429         os << "</manifest>\n";
430         return os;
431 }
432
433 }