]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Embedding: use a vector to store multiple ParConstIterator of multiple insets referri...
[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_()
73 {
74         if (pit != ParConstIterator())
75                 par_it_.push_back(pit);
76 }
77
78
79 string EmbeddedFile::embeddedFile(Buffer const * buf) const
80 {
81         return addName(buf->temppath(), inzip_name_);
82 }
83
84
85 void EmbeddedFile::addParIter(ParConstIterator const & pit)
86 {
87         par_it_.push_back(pit);
88 }
89
90
91 int EmbeddedFile::parID(int idx) const
92 {
93         BOOST_ASSERT(idx < refCount());
94         // some embedded file do not have a valid par iterator
95         return par_it_[idx]->id();
96 }
97
98
99 string EmbeddedFile::availableFile(Buffer const * buf) const
100 {
101         if (embedded())
102                 return embeddedFile(buf);
103         else
104                 return absFilename();
105 }
106
107
108 void EmbeddedFile::invalidate()
109 {
110         // Clear par_it_ because they will be registered again.
111         par_it_.clear();
112         valid_ = false;
113 }
114
115
116 bool EmbeddedFile::extract(Buffer const * buf) const
117 {
118
119         string ext_file = absFilename();
120         string emb_file = embeddedFile(buf);
121
122         if (!fs::exists(emb_file))
123                 return false;
124
125         // if external file already exists ...
126         if (fs::exists(ext_file)) {
127                 // no need to copy if the files are the same
128                 if (sum(*this) == sum(FileName(emb_file)))
129                         return true;
130                 // otherwise, ask if overwrite
131                 int ret = Alert::prompt(
132                         _("Overwrite external file?"),
133                         bformat(_("External file %1$s already exists, do you want to overwrite it"),
134                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
135                 if (ret != 0)
136                         // if the user does not want to overwrite, we still consider it
137                         // a successful operation.
138                         return true;
139         }
140         // copy file
141         try {
142                 // need to make directory?
143                 string path = onlyPath(ext_file);
144                 if (!fs::is_directory(path))
145                         makedir(const_cast<char*>(path.c_str()), 0755);
146                 fs::copy_file(emb_file, ext_file, false);
147                 return true;
148         } catch (fs::filesystem_error const & fe) {
149                 Alert::error(_("Copy file failure"),
150                          bformat(_("Cannot copy file %1$s to %2$s.\n"
151                                    "Please check whether the directory exists and is writeable."),
152                                         from_utf8(emb_file), from_utf8(ext_file)));
153                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
154         }
155         return false;
156 }
157
158
159 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
160 {
161         string ext_file = absFilename();
162         string emb_file = embeddedFile(buf);
163
164         if (!fs::exists(ext_file))
165                 return false;
166         
167         // if embedded file already exists ...
168         if (fs::exists(emb_file)) {
169                 // no need to copy if the files are the same
170                 if (sum(*this) == sum(FileName(emb_file)))
171                         return true;
172                 // other wise, ask if overwrite
173                 int const ret = Alert::prompt(
174                         _("Update embedded file?"),
175                         bformat(_("Embeddedl file %1$s already exists, do you want to overwrite it"),
176                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
177                 if (ret != 0)
178                         // if the user does not want to overwrite, we still consider it
179                         // a successful operation.
180                         return true;
181         }
182         // copy file
183         try {
184                 // need to make directory?
185                 string path = onlyPath(emb_file);
186                 if (!fs::is_directory(path))
187                         makedir(const_cast<char*>(path.c_str()), 0755);
188                 fs::copy_file(ext_file, emb_file, false);
189                 return true;
190         } catch (fs::filesystem_error const & fe) {
191                 Alert::error(_("Copy file failure"),
192                          bformat(_("Cannot copy file %1$s to %2$s.\n"
193                                    "Please check whether the directory exists and is writeable."),
194                                         from_utf8(ext_file), from_utf8(emb_file)));
195                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
196         }
197         return false;
198 }
199
200
201 bool EmbeddedFiles::enabled() const
202 {
203         return buffer_->params().embedded;
204 }
205
206
207 bool EmbeddedFiles::enable(bool flag)
208 {
209         if (enabled() != flag) {
210                 // if enable, copy all files to temppath()
211                 // if disable, extract all files
212                 if ((flag && !updateFromExternalFile()) || (!flag && !extract()))
213                         return false;
214                 // if operation is successful
215                 buffer_->markDirty();
216                 buffer_->params().embedded = flag;
217         }
218 }
219
220
221 void EmbeddedFiles::registerFile(string const & filename,
222         bool embed, ParConstIterator const & pit)
223 {
224         // filename can be relative or absolute, translate to absolute filename
225         string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
226         // try to find this file from the list
227         EmbeddedFileList::iterator it = file_list_.begin();
228         EmbeddedFileList::iterator it_end = file_list_.end();
229         for (; it != it_end; ++it)
230                 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
231                         break;
232         // find this filename, keep the original embedding status
233         if (it != file_list_.end()) {
234                 it->addParIter(pit);
235                 // if the file is embedded, the embedded file should have exist
236                 // check for this to ensure that our logic is correct
237                 if (it->embedded())
238                         BOOST_ASSERT(fs::exists(it->embeddedFile(buffer_)));
239                 it->validate();
240                 return;
241         }
242         // try to be more careful
243         file_list_.push_back(EmbeddedFile(abs_filename, 
244                 getInzipName(abs_filename), embed, pit));
245         // validate if things are OK
246         BOOST_ASSERT(fs::exists(file_list_.back().availableFile(buffer_)));
247 }
248
249
250 void EmbeddedFiles::update()
251 {
252         // invalidate all files, obsolete files will then not be validated by the
253         // following document scan. These files will still be kept though, because
254         // they may be added later and their embedding status will be meaningful
255         // again (thinking of cut/paste of an InsetInclude).
256         EmbeddedFileList::iterator it = file_list_.begin();
257         EmbeddedFileList::iterator it_end = file_list_.end();
258         for (; it != it_end; ++it)
259                 // we do not update items that are manually inserted
260                 if (it->refCount() > 0)
261                         it->invalidate();
262
263         ParIterator pit = buffer_->par_iterator_begin();
264         ParIterator pit_end = buffer_->par_iterator_end();
265         for (; pit != pit_end; ++pit) {
266                 // For each paragraph, traverse its insets and register embedded files
267                 InsetList::const_iterator iit = pit->insetlist.begin();
268                 InsetList::const_iterator iit_end = pit->insetlist.end();
269                 for (; iit != iit_end; ++iit) {
270                         Inset & inset = *iit->inset;
271                         inset.registerEmbeddedFiles(*buffer_, *this, pit);
272                 }
273         }
274         LYXERR(Debug::FILES) << "Manifest updated: " << endl
275                 << *this
276                 << "End Manifest" << endl;
277 }
278
279
280 bool EmbeddedFiles::write(DocFileName const & filename)
281 {
282         // file in the temporary path has the content
283         string const content = FileName(addName(buffer_->temppath(),
284                 onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
285
286         // get a file list and write a manifest file
287         vector<pair<string, string> > filenames;
288         string const manifest = FileName(
289                 addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
290
291         // write a manifest file
292         ofstream os(manifest.c_str());
293         os << *this;
294         os.close();
295         // prepare list of embedded file
296         EmbeddedFileList::iterator it = file_list_.begin();
297         EmbeddedFileList::iterator it_end = file_list_.end();
298         for (; it != it_end; ++it) {
299                 if (it->valid() && it->embedded()) {
300                         string file = it->availableFile(buffer_);
301                         if (file.empty())
302                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
303                         else
304                                 filenames.push_back(make_pair(file, it->inzipName()));
305                 }
306         }
307         // add filename (.lyx) and manifest to filenames
308         filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
309         filenames.push_back(make_pair(manifest, "manifest.txt"));
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)
367 {
368         // register a new one, using relative file path as inzip_name
369         string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
370                 from_utf8(buffer_->fileName())));
371         // if inzip_name is an absolute path, use filename only to avoid
372         // leaking of filesystem information in inzip_name
373         // The second case covers cases '../path/file' and '.'
374         if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
375                 inzip_name = onlyFilename(abs_filename);
376         // if this name has been used...
377         // use _1_name, _2_name etc
378         string tmp = inzip_name;
379         EmbeddedFileList::iterator it;
380         EmbeddedFileList::iterator it_end = file_list_.end();
381         bool unique_name = false;
382         size_t i = 0;
383         while (!unique_name) {
384                 unique_name = true;
385                 if (i > 0)
386                         inzip_name = convert<string>(i) + "_" + tmp;
387                 it = file_list_.begin();
388                 for (; it != it_end; ++it)
389                         if (it->inzipName() == inzip_name) {
390                                 unique_name = false;
391                                 ++i;
392                                 break;
393                         }
394         }
395         return inzip_name;
396 }
397
398
399 istream & operator>> (istream & is, EmbeddedFiles & files)
400 {
401         files.clear();
402         string tmp;
403         getline(is, tmp);
404         // get version
405         istringstream itmp(tmp);
406         int version;
407         itmp.ignore(string("# LyX manifest version ").size());
408         itmp >> version;
409
410         if (version != 1) {
411                 lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
412                 return is;
413         }
414
415         getline(is, tmp);
416         if (tmp != "<manifest>") {
417                 lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
418                 return is;
419         }
420         // manifest file may be messed up, be carefully
421         while (is.good()) {
422                 getline(is, tmp);
423                 if (tmp != "<file>")
424                         break;
425
426                 string fname;
427                 getline(is, fname);
428                 string inzip_name;
429                 getline(is, inzip_name);
430                 getline(is, tmp);
431                 istringstream itmp(tmp);
432                 int embed;
433                 itmp >> embed;
434
435                 getline(is, tmp);
436                 if (tmp != "</file>") {
437                         lyxerr << "Invalid manifest file, lacking </file>" << endl;
438                         break;
439                 }
440
441                 files.registerFile(fname, embed);
442         };
443         // the last line must be </manifest>
444         if (tmp != "</manifest>") {
445                 lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
446                 return is;
447         }
448         return is;
449 }
450
451
452 ostream & operator<< (ostream & os, EmbeddedFiles const & files)
453 {
454         // store a version so that operator >> can read later versions
455         // using version information.
456         os << "# lyx manifest version 1\n";
457         os << "<manifest>\n";
458         EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
459         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
460         for (; it != it_end; ++it) {
461                 if (!it->valid())
462                         continue;
463                 // use differnt lines to make reading easier.
464                 os << "<file>\n"
465                         // save the relative path
466                         << to_utf8(makeRelPath(from_utf8(it->absFilename()),
467                                 from_utf8(files.buffer_->filePath()))) << '\n'
468                         << it->inzipName() << '\n'
469                         << it->embedded() << '\n'
470                         << "</file>\n";
471         }
472         os << "</manifest>\n";
473         return os;
474 }
475
476 }