]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
adjust
[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 EmbeddedFile & 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 *it;
275         }
276         // try to be more careful
277         file_list_.push_back(EmbeddedFile(abs_filename, 
278                 getInzipName(abs_filename, inzipName), embed, inset));
279         return file_list_.back();
280 }
281
282
283 void EmbeddedFiles::update()
284 {
285         // invalidate all files, obsolete files will then not be validated by the
286         // following document scan. These files will still be kept though, because
287         // they may be added later and their embedding status will be meaningful
288         // again (thinking of cut/paste of an InsetInclude).
289         EmbeddedFileList::iterator it = file_list_.begin();
290         EmbeddedFileList::iterator it_end = file_list_.end();
291         for (; it != it_end; ++it)
292                 // we do not update items that are manually inserted
293                 if (it->refCount() > 0)
294                         it->invalidate();
295
296         for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
297                 it->registerEmbeddedFiles(*buffer_, *this);
298 }
299
300
301 bool EmbeddedFiles::writeFile(DocFileName const & filename)
302 {
303         // file in the temporary path has the content
304         string const content = FileName(addName(buffer_->temppath(),
305                 "content.lyx")).toFilesystemEncoding();
306
307         vector<pair<string, string> > filenames;
308         // add content.lyx to filenames
309         filenames.push_back(make_pair(content, "content.lyx"));
310         // prepare list of embedded file
311         EmbeddedFileList::iterator it = file_list_.begin();
312         EmbeddedFileList::iterator it_end = file_list_.end();
313         for (; it != it_end; ++it) {
314                 if (it->valid() && it->embedded()) {
315                         string file = it->availableFile(buffer_);
316                         if (file.empty())
317                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
318                         else
319                                 filenames.push_back(make_pair(file, it->inzipName()));
320                 }
321         }
322         // write a zip file with all these files. Write to a temp file first, to
323         // avoid messing up the original file in case something goes terribly wrong.
324         DocFileName zipfile(addName(buffer_->temppath(),
325                 onlyFilename(changeExtension(
326                         filename.toFilesystemEncoding(), ".zip"))));
327
328         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
329         // copy file back
330         try {
331                 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
332         } catch (fs::filesystem_error const & fe) {
333                 Alert::error(_("Save failure"),
334                                  bformat(_("Cannot create file %1$s.\n"
335                                            "Please check whether the directory exists and is writeable."),
336                                          from_utf8(filename.absFilename())));
337                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
338         }
339         return true;
340 }
341
342
343 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
344 {
345         EmbeddedFileList::const_iterator it = file_list_.begin();
346         EmbeddedFileList::const_iterator it_end = file_list_.end();
347         for (; it != it_end; ++it)
348                 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)     
349                         return it;
350         return file_list_.end();
351 }
352
353
354 bool EmbeddedFiles::extract() const
355 {
356         EmbeddedFileList::const_iterator it = file_list_.begin();
357         EmbeddedFileList::const_iterator it_end = file_list_.end();
358         for (; it != it_end; ++it)
359                 if (it->valid() && it->embedded())
360                         if(!it->extract(buffer_))
361                                 return false;
362         return true;
363 }
364
365
366 bool EmbeddedFiles::updateFromExternalFile() const
367 {
368         EmbeddedFileList::const_iterator it = file_list_.begin();
369         EmbeddedFileList::const_iterator it_end = file_list_.end();
370         for (; it != it_end; ++it)
371                 if (it->valid() && it->embedded())
372                         if (!it->updateFromExternalFile(buffer_))
373                                 return false;
374         return true;
375 }
376
377
378 string const EmbeddedFiles::getInzipName(string const & abs_filename, string const & name)
379 {
380         // register a new one, using relative file path as inzip_name
381         string inzip_name = name;
382         if (name.empty())
383                 inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
384                         from_utf8(buffer_->filePath())));
385         // if inzip_name is an absolute path, use filename only to avoid
386         // leaking of filesystem information in inzip_name
387         // The second case covers cases '../path/file' and '.'
388         if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
389                 inzip_name = onlyFilename(abs_filename);
390         // if this name has been used...
391         // use _1_name, _2_name etc
392         string tmp = inzip_name;
393         EmbeddedFileList::iterator it;
394         EmbeddedFileList::iterator it_end = file_list_.end();
395         bool unique_name = false;
396         size_t i = 0;
397         while (!unique_name) {
398                 unique_name = true;
399                 if (i > 0)
400                         inzip_name = convert<string>(i) + "_" + tmp;
401                 it = file_list_.begin();
402                 for (; it != it_end; ++it)
403                         if (it->inzipName() == inzip_name) {
404                                 unique_name = false;
405                                 ++i;
406                                 break;
407                         }
408         }
409         return inzip_name;
410 }
411
412
413 bool EmbeddedFiles::readManifest(Lexer & lex, ErrorList & errorList)
414 {
415         int line = -1;
416         int begin_manifest_line = -1;
417
418         file_list_.clear();
419         string filename;
420         string inzipName;
421         bool embedded = false;
422
423         while (lex.isOK()) {
424                 lex.next();
425                 string const token = lex.getString();
426
427                 if (token.empty())
428                         continue;
429
430                 if (token == "\\end_manifest")
431                         break;
432
433                 ++line;
434                 if (token == "\\begin_manifest") {
435                         begin_manifest_line = line;
436                         continue;
437                 }
438                 
439                 LYXERR(Debug::PARSER) << "Handling document manifest token: `"
440                                       << token << '\'' << endl;
441
442                 if (token == "\\filename")
443                         lex >> filename;
444                 else if (token == "\\inzipName")
445                         lex >> inzipName;
446                 else if (token == "\\embed") {
447                         lex >> embedded;
448                         registerFile(filename, embedded, NULL, inzipName);
449                         filename = "";
450                         inzipName = "";
451                 } else {
452                         docstring const s = _("\\begin_file is missing");
453                         errorList.push_back(ErrorItem(_("Manifest error"),
454                                 s, -1, 0, 0));
455                 }
456         }
457         if (begin_manifest_line) {
458                 docstring const s = _("\\begin_manifest is missing");
459                 errorList.push_back(ErrorItem(_("Manifest error"),
460                         s, -1, 0, 0));
461         }
462         return true;
463 }
464
465
466 void EmbeddedFiles::writeManifest(ostream & os) const
467 {
468         EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
469         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
470         for (; it != it_end; ++it) {
471                 // only saved 'extra' files. Other embedded files are saved
472                 // with insets.
473                 if (!it->valid() || it->refCount() > 0)
474                         continue;
475                 // save the relative path
476                 os << "\\filename "
477                         << to_utf8(makeRelPath(from_utf8(it->absFilename()),
478                                 from_utf8(buffer_->filePath()))) << '\n'
479                         << "\\inzipName " << it->inzipName() << '\n'
480                         << "\\embed " << (it->embedded() ? "true" : "false") << '\n';
481         }
482 }
483
484
485 void EmbeddedFiles::updateInsets() const
486 {
487         EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
488         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
489         for (; it != it_end; ++it)
490                 if (it->valid() && it->refCount() > 0)
491                         it->updateInsets(buffer_);
492 }
493
494
495 }