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