]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Embedding: update related insets when the embedding status of a fileitem is changed
[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 "InsetIterator.h"
20 #include "debug.h"
21 #include "gettext.h"
22 #include "Format.h"
23 #include "Lexer.h"
24 #include "ErrorList.h"
25
26 #include "frontends/alert.h"
27
28 #include <boost/filesystem/operations.hpp>
29
30 #include "support/filetools.h"
31 #include "support/fs_extras.h"
32 #include "support/convert.h"
33 #include "support/lyxlib.h"
34 #include "support/lstrings.h"
35
36 #include "LyX.h"
37 #include "Session.h"
38
39 #include <sstream>
40 #include <fstream>
41 #include <utility>
42
43 using std::ofstream;
44 using std::endl;
45 using std::vector;
46 using std::string;
47 using std::pair;
48 using std::make_pair;
49 using std::istream;
50 using std::ostream;
51 using std::getline;
52 using std::istringstream;
53
54 namespace lyx {
55
56 namespace fs = boost::filesystem;
57 namespace Alert = frontend::Alert;
58
59 using support::FileName;
60 using support::DocFileName;
61 using support::makeAbsPath;
62 using support::addName;
63 using support::onlyPath;
64 using support::absolutePath;
65 using support::onlyFilename;
66 using support::makeRelPath;
67 using support::changeExtension;
68 using support::bformat;
69 using support::prefixIs;
70 using support::sum;
71 using support::makedir;
72
73
74 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
75         bool embed, Inset const * inset)
76         : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
77                 valid_(true), inset_list_()
78 {
79         if (inset != NULL)
80                 inset_list_.push_back(inset);
81 }
82
83
84 string EmbeddedFile::embeddedFile(Buffer const * buf) const
85 {
86         return addName(buf->temppath(), inzip_name_);
87 }
88
89
90 void EmbeddedFile::addInset(Inset const * inset)
91 {
92         inset_list_.push_back(inset);
93 }
94
95
96 Inset const * EmbeddedFile::inset(int idx) const
97 {
98         BOOST_ASSERT(idx < refCount());
99         // some embedded file do not have a valid par iterator
100         return inset_list_[idx];
101 }
102
103
104 void EmbeddedFile::saveBookmark(Buffer const * buf, int idx) const
105 {
106         Inset const * ptr = inset(idx);
107         // This might not be the most efficient method ... 
108         for (InsetIterator it = inset_iterator_begin(buf->inset()); it; ++it)
109                 if (&(*it) == ptr) {
110                         // this is basically BufferView::saveBookmark(0)
111                         LyX::ref().session().bookmarks().save(
112                                 FileName(buf->fileName()),
113                                 it.bottom().pit(),
114                                 it.bottom().pos(),
115                                 it.paragraph().id(),
116                                 it.pos(),
117                                 0
118                         );
119                 }
120         // this inset can not be located. There is something wrong that needs
121         // to be fixed.
122         BOOST_ASSERT(true);
123 }
124
125
126 string EmbeddedFile::availableFile(Buffer const * buf) const
127 {
128         if (embedded())
129                 return embeddedFile(buf);
130         else
131                 return absFilename();
132 }
133
134
135 void EmbeddedFile::invalidate()
136 {
137         // Clear inset_list_ because they will be registered again.
138         inset_list_.clear();
139         valid_ = false;
140 }
141
142
143 bool EmbeddedFile::extract(Buffer const * buf) const
144 {
145
146         string ext_file = absFilename();
147         string emb_file = embeddedFile(buf);
148
149         if (!fs::exists(emb_file))
150                 return false;
151
152         // if external file already exists ...
153         if (fs::exists(ext_file)) {
154                 // no need to copy if the files are the same
155                 if (sum(*this) == sum(FileName(emb_file)))
156                         return true;
157                 // otherwise, ask if overwrite
158                 int ret = Alert::prompt(
159                         _("Overwrite external file?"),
160                         bformat(_("External file %1$s already exists, do you want to overwrite it"),
161                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
162                 if (ret != 0)
163                         // if the user does not want to overwrite, we still consider it
164                         // a successful operation.
165                         return true;
166         }
167         // copy file
168         try {
169                 // need to make directory?
170                 string path = onlyPath(ext_file);
171                 if (!fs::is_directory(path))
172                         makedir(const_cast<char*>(path.c_str()), 0755);
173                 fs::copy_file(emb_file, ext_file, false);
174                 return true;
175         } catch (fs::filesystem_error const & fe) {
176                 Alert::error(_("Copy file failure"),
177                          bformat(_("Cannot copy file %1$s to %2$s.\n"
178                                    "Please check whether the directory exists and is writeable."),
179                                         from_utf8(emb_file), from_utf8(ext_file)));
180                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
181         }
182         return false;
183 }
184
185
186 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
187 {
188         string ext_file = absFilename();
189         string emb_file = embeddedFile(buf);
190
191         if (!fs::exists(ext_file))
192                 return false;
193         
194         // if embedded file already exists ...
195         if (fs::exists(emb_file)) {
196                 // no need to copy if the files are the same
197                 if (sum(*this) == sum(FileName(emb_file)))
198                         return true;
199                 // other wise, ask if overwrite
200                 int const ret = Alert::prompt(
201                         _("Update embedded file?"),
202                         bformat(_("Embeddedl file %1$s already exists, do you want to overwrite it"),
203                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
204                 if (ret != 0)
205                         // if the user does not want to overwrite, we still consider it
206                         // a successful operation.
207                         return true;
208         }
209         // copy file
210         try {
211                 // need to make directory?
212                 string path = onlyPath(emb_file);
213                 if (!fs::is_directory(path))
214                         makedir(const_cast<char*>(path.c_str()), 0755);
215                 fs::copy_file(ext_file, emb_file, false);
216                 return true;
217         } catch (fs::filesystem_error const & fe) {
218                 Alert::error(_("Copy file failure"),
219                          bformat(_("Cannot copy file %1$s to %2$s.\n"
220                                    "Please check whether the directory exists and is writeable."),
221                                         from_utf8(ext_file), from_utf8(emb_file)));
222                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
223         }
224         return false;
225 }
226
227
228 void EmbeddedFile::updateInsets(Buffer const * buf) const
229 {
230         vector<Inset const *>::const_iterator it = inset_list_.begin();
231         vector<Inset const *>::const_iterator it_end = inset_list_.end();
232         for (; it != it_end; ++it)
233                 const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
234 }
235
236
237 bool EmbeddedFiles::enabled() const
238 {
239         return buffer_->params().embedded;
240 }
241
242
243 void EmbeddedFiles::enable(bool flag)
244 {
245         if (enabled() != flag) {
246                 // if enable, copy all files to temppath()
247                 // if disable, extract all files
248                 if ((flag && !updateFromExternalFile()) || (!flag && !extract()))
249                         return;
250                 // if operation is successful
251                 buffer_->markDirty();
252                 buffer_->params().embedded = flag;
253                 if (flag)
254                         updateInsets();
255         }
256 }
257
258
259 void EmbeddedFiles::registerFile(string const & filename,
260         bool embed, Inset const * inset, string const & inzipName)
261 {
262         // filename can be relative or absolute, translate to absolute filename
263         string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
264         // try to find this file from the list
265         EmbeddedFileList::iterator it = file_list_.begin();
266         EmbeddedFileList::iterator it_end = file_list_.end();
267         for (; it != it_end; ++it)
268                 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
269                         break;
270         // find this filename, keep the original embedding status
271         if (it != file_list_.end()) {
272                 it->addInset(inset);
273                 it->validate();
274                 return;
275         }
276         // try to be more careful
277         file_list_.push_back(EmbeddedFile(abs_filename, 
278                 getInzipName(abs_filename, inzipName), embed, inset));
279 }
280
281
282 void EmbeddedFiles::update()
283 {
284         // invalidate all files, obsolete files will then not be validated by the
285         // following document scan. These files will still be kept though, because
286         // they may be added later and their embedding status will be meaningful
287         // again (thinking of cut/paste of an InsetInclude).
288         EmbeddedFileList::iterator it = file_list_.begin();
289         EmbeddedFileList::iterator it_end = file_list_.end();
290         for (; it != it_end; ++it)
291                 // we do not update items that are manually inserted
292                 if (it->refCount() > 0)
293                         it->invalidate();
294
295         for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
296                 it->registerEmbeddedFiles(*buffer_, *this);
297 }
298
299
300 bool EmbeddedFiles::writeFile(DocFileName const & filename)
301 {
302         // file in the temporary path has the content
303         string const content = FileName(addName(buffer_->temppath(),
304                 "content.lyx")).toFilesystemEncoding();
305
306         vector<pair<string, string> > filenames;
307         // add content.lyx to filenames
308         filenames.push_back(make_pair(content, "content.lyx"));
309         // prepare list of embedded file
310         EmbeddedFileList::iterator it = file_list_.begin();
311         EmbeddedFileList::iterator it_end = file_list_.end();
312         for (; it != it_end; ++it) {
313                 if (it->valid() && it->embedded()) {
314                         string file = it->availableFile(buffer_);
315                         if (file.empty())
316                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
317                         else
318                                 filenames.push_back(make_pair(file, it->inzipName()));
319                 }
320         }
321         // write a zip file with all these files. Write to a temp file first, to
322         // avoid messing up the original file in case something goes terribly wrong.
323         DocFileName zipfile(addName(buffer_->temppath(),
324                 onlyFilename(changeExtension(
325                         filename.toFilesystemEncoding(), ".zip"))));
326
327         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
328         // copy file back
329         try {
330                 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
331         } catch (fs::filesystem_error const & fe) {
332                 Alert::error(_("Save failure"),
333                                  bformat(_("Cannot create file %1$s.\n"
334                                            "Please check whether the directory exists and is writeable."),
335                                          from_utf8(filename.absFilename())));
336                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
337         }
338         return true;
339 }
340
341
342 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
343 {
344         EmbeddedFileList::const_iterator it = file_list_.begin();
345         EmbeddedFileList::const_iterator it_end = file_list_.end();
346         for (; it != it_end; ++it)
347                 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)     
348                         return it;
349         return file_list_.end();
350 }
351
352
353 bool EmbeddedFiles::extract() const
354 {
355         EmbeddedFileList::const_iterator it = file_list_.begin();
356         EmbeddedFileList::const_iterator it_end = file_list_.end();
357         for (; it != it_end; ++it)
358                 if (it->valid() && it->embedded())
359                         if(!it->extract(buffer_))
360                                 return false;
361         return true;
362 }
363
364
365 bool EmbeddedFiles::updateFromExternalFile() const
366 {
367         EmbeddedFileList::const_iterator it = file_list_.begin();
368         EmbeddedFileList::const_iterator it_end = file_list_.end();
369         for (; it != it_end; ++it)
370                 if (it->valid() && it->embedded())
371                         if (!it->updateFromExternalFile(buffer_))
372                                 return false;
373         return true;
374 }
375
376
377 string const EmbeddedFiles::getInzipName(string const & abs_filename, string const & name)
378 {
379         // register a new one, using relative file path as inzip_name
380         string inzip_name = name;
381         if (name.empty())
382                 inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
383                         from_utf8(buffer_->filePath())));
384         // if inzip_name is an absolute path, use filename only to avoid
385         // leaking of filesystem information in inzip_name
386         // The second case covers cases '../path/file' and '.'
387         if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
388                 inzip_name = onlyFilename(abs_filename);
389         // if this name has been used...
390         // use _1_name, _2_name etc
391         string tmp = inzip_name;
392         EmbeddedFileList::iterator it;
393         EmbeddedFileList::iterator it_end = file_list_.end();
394         bool unique_name = false;
395         size_t i = 0;
396         while (!unique_name) {
397                 unique_name = true;
398                 if (i > 0)
399                         inzip_name = convert<string>(i) + "_" + tmp;
400                 it = file_list_.begin();
401                 for (; it != it_end; ++it)
402                         if (it->inzipName() == inzip_name) {
403                                 unique_name = false;
404                                 ++i;
405                                 break;
406                         }
407         }
408         return inzip_name;
409 }
410
411
412 bool EmbeddedFiles::readManifest(Lexer & lex, ErrorList & errorList)
413 {
414         int line = -1;
415         int begin_manifest_line = -1;
416
417         file_list_.clear();
418         string filename;
419         string inzipName;
420         bool embedded = false;
421
422         while (lex.isOK()) {
423                 lex.next();
424                 string const token = lex.getString();
425
426                 if (token.empty())
427                         continue;
428
429                 if (token == "\\end_manifest")
430                         break;
431
432                 ++line;
433                 if (token == "\\begin_manifest") {
434                         begin_manifest_line = line;
435                         continue;
436                 }
437                 
438                 LYXERR(Debug::PARSER) << "Handling document manifest token: `"
439                                       << token << '\'' << endl;
440
441                 if (token == "\\filename")
442                         lex >> filename;
443                 else if (token == "\\inzipName")
444                         lex >> inzipName;
445                 else if (token == "\\embed") {
446                         lex >> embedded;
447                         registerFile(filename, embedded, NULL, inzipName);
448                         filename = "";
449                         inzipName = "";
450                 } else {
451                         docstring const s = _("\\begin_file is missing");
452                         errorList.push_back(ErrorItem(_("Manifest error"),
453                                 s, -1, 0, 0));
454                 }
455         }
456         if (begin_manifest_line) {
457                 docstring const s = _("\\begin_manifest is missing");
458                 errorList.push_back(ErrorItem(_("Manifest error"),
459                         s, -1, 0, 0));
460         }
461         return true;
462 }
463
464
465 void EmbeddedFiles::writeManifest(ostream & os) const
466 {
467         EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
468         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
469         for (; it != it_end; ++it) {
470                 if (!it->valid())
471                         continue;
472                 // save the relative path
473                 os << "\\filename "
474                         << to_utf8(makeRelPath(from_utf8(it->absFilename()),
475                                 from_utf8(buffer_->filePath()))) << '\n'
476                         << "\\inzipName " << it->inzipName() << '\n'
477                         << "\\embed " << (it->embedded() ? "true" : "false") << '\n';
478         }
479 }
480
481
482 void EmbeddedFiles::updateInsets() const
483 {
484         EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
485         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
486         for (; it != it_end; ++it)
487                 if (it->valid() && it->refCount() > 0)
488                         it->updateInsets(buffer_);
489 }
490
491
492 }