]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
this we don't need anymore
[lyx.git] / src / EmbeddedFiles.cpp
1 // -*- C++ -*-
2 /**
3  * \file EmbeddedFileList.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
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "ErrorList.h"
20 #include "Format.h"
21 #include "InsetIterator.h"
22 #include "Lexer.h"
23 #include "LyX.h"
24 #include "Paragraph.h"
25 #include "Session.h"
26
27 #include "frontends/alert.h"
28
29 #include "support/debug.h"
30 #include "support/filetools.h"
31 #include "support/gettext.h"
32 #include "support/convert.h"
33 #include "support/lstrings.h"
34 #include "support/ExceptionMessage.h"
35 #include "support/FileZipListDir.h"
36
37 #include "support/assert.h"
38
39 #include <sstream>
40 #include <fstream>
41 #include <utility>
42
43 using namespace std;
44 using namespace lyx::support;
45
46 namespace lyx {
47
48 namespace Alert = frontend::Alert;
49
50 EmbeddedFile::EmbeddedFile(string const & file, std::string const & buffer_path)
51         : DocFileName("", false), embedded_(false), inset_list_()
52 {
53         set(file, buffer_path);
54 }
55
56
57 void EmbeddedFile::set(std::string const & filename, std::string const & buffer_path)
58 {
59         DocFileName::set(filename, buffer_path);
60         if (filename.empty())
61                 return;
62
63         if (!buffer_path.empty())
64                 inzip_name_ = calcInzipName(buffer_path);
65 }
66
67
68 void EmbeddedFile::setInzipName(std::string const & name)
69 {
70         if (name.empty() || name == inzip_name_)
71                 return;
72
73         // an enabled EmbeededFile should have this problem handled
74         LASSERT(!isEnabled(), /**/);
75         // file will be synced when it is enabled
76         inzip_name_ = name;
77 }
78
79
80 string EmbeddedFile::embeddedFile() const
81 {
82         LASSERT(isEnabled(), /**/);
83         return temp_path_ + inzip_name_;
84 }
85
86
87 FileName EmbeddedFile::availableFile() const
88 {
89         if (isEnabled() && embedded())
90                 return FileName(embeddedFile());
91         return *this;
92 }
93
94
95 string EmbeddedFile::latexFilename(std::string const & buffer_path) const
96 {
97         return (isEnabled() && embedded()) ? inzip_name_ : relFilename(buffer_path);
98 }
99
100
101 void EmbeddedFile::addInset(Inset const * inset)
102 {
103         if (inset)
104                 inset_list_.push_back(inset);
105 }
106
107
108 void EmbeddedFile::setEmbed(bool embed)
109 {
110         embedded_ = embed;
111 }
112
113
114 void EmbeddedFile::enable(bool enabled, Buffer const & buf, bool updateFile)
115 {
116         // This function will be called when
117         // 1. through EmbeddedFiles::enable() when a file is read. Files
118         //    should be in place so no updateFromExternalFile or extract()
119         //    should be called. (updateFile should be false in this case).
120         // 2. through menu item enable/disable. updateFile should be true.
121         // 3. A single embedded file is added or modified. updateFile
122         //    can be true or false.
123         LYXERR(Debug::FILES, (enabled ? "Enable" : "Disable") 
124                 << " " << absFilename() 
125                 << (updateFile ? " (update file)." : " (no update)."));
126
127         if (enabled) {
128                 temp_path_ = buf.temppath();
129                 if (!suffixIs(temp_path_, '/'))
130                         temp_path_ += '/';
131                 if (embedded() && updateFile)
132                         updateFromExternalFile();
133         } else {
134                 // when a new embeddeed file is created, it is not enabled, and 
135                 // there is no need to extract.
136                 if (isEnabled() && embedded() && updateFile)
137                         extract();
138                 temp_path_ = "";
139         }
140 }
141
142
143 bool EmbeddedFile::extract() const
144 {
145         LASSERT(isEnabled(), /**/);
146
147         string ext_file = absFilename();
148         string emb_file = embeddedFile();
149
150         FileName emb(emb_file);
151         FileName ext(ext_file);
152
153         if (!emb.exists()) {
154                 if (ext.exists())
155                         return true;
156                 throw ExceptionMessage(ErrorException, _("Failed to extract file"),
157                         bformat(_("Cannot extract file '%1$s'.\n"
158                         "Source file %2$s does not exist"),
159                         from_utf8(outputFilename()), from_utf8(emb_file)));
160         }
161
162         // if external file already exists ...
163         if (ext.exists()) {
164                 // no need to copy if the files are the same
165                 if (checksum() == FileName(emb_file).checksum())
166                         return true;
167                 // otherwise, ask if overwrite
168                 int ret = Alert::prompt(
169                         _("Overwrite external file?"),
170                         bformat(_("External file %1$s already exists, do you want to overwrite it?"),
171                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
172                 if (ret != 0)
173                         // if the user does not want to overwrite, we still consider it
174                         // a successful operation.
175                         return true;
176         }
177         // copy file
178
179         // need to make directory?
180         FileName path = ext.onlyPath();
181         if (!path.createPath()) {
182                 throw ExceptionMessage(ErrorException, _("Copy file failure"),
183                         bformat(_("Cannot create file path '%1$s'.\n"
184                         "Please check whether the path is writeable."),
185                         from_utf8(path.absFilename())));
186                 return false;
187         }
188
189         if (emb.copyTo(ext)) {
190                 LYXERR(Debug::FILES, "Extract file " << emb_file << " to " << ext_file << endl);
191                 return true;
192         }
193
194         throw ExceptionMessage(ErrorException, _("Copy file failure"),
195                  bformat(_("Cannot copy file %1$s to %2$s.\n"
196                                  "Please check whether the directory exists and is writeable."),
197                                 from_utf8(emb_file), from_utf8(ext_file)));
198         return false;
199 }
200
201
202 bool EmbeddedFile::updateFromExternalFile() const
203 {
204         LASSERT(isEnabled(), /**/);
205
206         string ext_file = absFilename();
207         string emb_file = embeddedFile();
208
209         FileName emb(emb_file);
210         FileName ext(ext_file);
211
212         if (!ext.exists()) {
213                 // no need to update
214                 if (emb.exists())
215                         return true;
216                 // no external and internal file
217                 throw ExceptionMessage(ErrorException,
218                         _("Failed to embed file"),
219                         bformat(_("Failed to embed file %1$s.\n"
220                            "Please check whether this file exists and is readable."),
221                                 from_utf8(ext_file)));
222         }
223
224         // if embedded file already exists ...
225         if (emb.exists()) {
226                 // no need to copy if the files are the same
227                 if (checksum() == FileName(emb_file).checksum())
228                         return true;
229                 // other wise, ask if overwrite
230                 int const ret = Alert::prompt(
231                         _("Update embedded file?"),
232                         bformat(_("Embedded file %1$s already exists, do you want to overwrite it"),
233                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
234                 if (ret != 0)
235                         // if the user does not want to overwrite, we still consider it
236                         // a successful operation.
237                         return true;
238         }
239         // copy file
240         // need to make directory?
241         FileName path = emb.onlyPath();
242         if (!path.isDirectory())
243                 path.createPath();
244         if (ext.copyTo(emb))
245                 return true;
246         throw ExceptionMessage(ErrorException,
247                 _("Copy file failure"),
248                 bformat(_("Cannot copy file %1$s to %2$s.\n"
249                            "Please check whether the directory exists and is writeable."),
250                                 from_utf8(ext_file), from_utf8(emb_file)));
251         //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
252         return false;
253 }
254
255
256 EmbeddedFile EmbeddedFile::copyTo(Buffer const & buf)
257 {
258         EmbeddedFile file = EmbeddedFile(absFilename(), buf.filePath());
259         file.setEmbed(embedded());
260         file.enable(buf.embedded(), buf, false);
261         
262         // use external file.
263         if (!embedded())
264                 return file;
265
266         LYXERR(Debug::FILES, "Copy " << availableFile()
267                 << " to " << file.availableFile());
268
269         FileName from_file = availableFile();
270         FileName to_file = file.availableFile();
271
272         if (!from_file.exists()) {
273                 // no from file
274                 throw ExceptionMessage(ErrorException,
275                         _("Failed to copy embedded file"),
276                         bformat(_("Failed to embed file %1$s.\n"
277                            "Please check whether the source file is available"),
278                                 from_utf8(absFilename())));
279                 file.setEmbed(false);
280                 return file;
281         }
282
283         // if destination file already exists ...
284         if (to_file.exists()) {
285                 // no need to copy if the files are the same
286                 if (checksum() == to_file.checksum())
287                         return file;
288                 // other wise, ask if overwrite
289                 int const ret = Alert::prompt(
290                         _("Update embedded file?"),
291                         bformat(_("Embedded file %1$s already exists, do you want to overwrite it"),
292                                 from_utf8(to_file.absFilename())), 1, 1, _("&Overwrite"), _("&Cancel"));
293                 if (ret != 0)
294                         // if the user does not want to overwrite, we still consider it
295                         // a successful operation.
296                         return file;
297         }
298         // copy file
299         // need to make directory?
300         FileName path = to_file.onlyPath();
301         if (!path.isDirectory())
302                 path.createPath();
303         if (from_file.copyTo(to_file))
304                 return file;
305         throw ExceptionMessage(ErrorException,
306                 _("Copy file failure"),
307                 bformat(_("Cannot copy file %1$s to %2$s.\n"
308                            "Please check whether the directory exists and is writeable."),
309                                 from_utf8(from_file.absFilename()), from_utf8(to_file.absFilename())));
310         return file;
311 }
312
313
314 void EmbeddedFile::updateInsets() const
315 {
316         vector<Inset const *>::const_iterator it = inset_list_.begin();
317         vector<Inset const *>::const_iterator it_end = inset_list_.end();
318         for (; it != it_end; ++it)
319                 const_cast<Inset *>(*it)->updateEmbeddedFile(*this);
320 }
321
322
323 bool EmbeddedFile::isReadableFile() const
324 {
325         return availableFile().isReadableFile();
326 }
327
328
329 unsigned long EmbeddedFile::checksum() const
330 {
331         return availableFile().checksum();
332 }
333
334 /**
335 Under the lyx temp directory, content.lyx and its embedded files are usually
336 saved as
337
338 $temp/$embDirName/file.lyx
339 $temp/$embDirName/figure1.png     for ./figure1.png)
340 $temp/$embDirName/sub/figure2.png for ./sub/figure2.png)
341
342 This works fine for embedded files that are in the current or deeper directory
343 of the document directory, but not for files such as ../figures/figure.png.
344 A unique name $upDirName is chosen to represent .. in such filenames so that
345 'up' directories can be stored 'down' the directory tree:
346
347 $temp/$embDirName/$upDirName/figures/figure.png     for ../figures/figure.png
348 $temp/$embDirName/$upDirName/$upDirName/figure.png  for ../../figure.png
349
350 This name has to be fixed because it is used in lyx bundled .zip file.
351
352 Using a similar trick, we use $absDirName for absolute path so that
353 an absolute filename can be saved as
354
355 $temp/$embDirName/$absDirName/a/absolute/path for /a/absolute/path
356
357 FIXME:
358 embDirName is set to . so that embedded layout and class files can be
359 used directly. However, putting all embedded files directly under
360 the temp directory may lead to file conflicts. For example, if a user
361 embeds a file blah.log in blah.lyx, it will be replaced when
362 'latex blah.tex' is called.
363 */
364 const std::string embDirName = ".";
365 const std::string upDirName = "LyX.Embed.Dir.Up";
366 const std::string absDirName = "LyX.Embed.Dir.Abs";
367 const std::string driveName = "LyX.Embed.Drive";
368 const std::string spaceName = "LyX.Embed.Space";
369
370 std::string EmbeddedFile::calcInzipName(std::string const & buffer_path)
371 {
372         string inzipName = to_utf8(makeRelPath(from_utf8(absFilename()),
373                         from_utf8(buffer_path)));
374         
375         if (FileName(inzipName).isAbsolute())
376                 inzipName = absDirName + '/' + inzipName;
377
378         // replace .. by upDirName
379         if (prefixIs(inzipName, "."))
380                 inzipName = subst(inzipName, "..", upDirName);
381         // replace special characters by their value
382         inzipName = subst(inzipName, ":", driveName);
383         inzipName = subst(inzipName, " ", spaceName);
384
385         // to avoid name conflict between $docu_path/file and $temp_path/file
386         // embedded files are in a subdirectory of $temp_path.
387         inzipName = embDirName + '/' + inzipName;
388         return inzipName;
389 }
390
391
392 void EmbeddedFile::syncInzipFile(std::string const & buffer_path)
393 {
394         LASSERT(isEnabled(), /**/);
395         string old_emb_file = temp_path_ + '/' + inzip_name_;
396         FileName old_emb(old_emb_file);
397
398         if (!old_emb.exists())
399                 throw ExceptionMessage(ErrorException, _("Failed to open file"),
400                         bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"),
401                                 old_emb.displayName()));
402
403         string new_inzip_name = calcInzipName(buffer_path);
404         if (new_inzip_name == inzip_name_)
405                 return;
406
407         LYXERR(Debug::FILES, " OLD ZIP " << old_emb_file <<
408                 " NEW ZIP " << calcInzipName(buffer_path));
409
410         string new_emb_file = temp_path_ + '/' + new_inzip_name;
411         FileName new_emb(new_emb_file);
412         
413         // need to make directory?
414         FileName path = new_emb.onlyPath();
415         if (!path.createPath()) {
416                 throw ExceptionMessage(ErrorException, _("Sync file failure"),
417                         bformat(_("Cannot create file path '%1$s'.\n"
418                         "Please check whether the path is writeable."),
419                         from_utf8(path.absFilename())));
420                 return;
421         }
422
423         if (old_emb.copyTo(new_emb)) {
424                 LYXERR(Debug::FILES, "Sync inzip file from " << inzip_name_ 
425                         << " to " << new_inzip_name);
426                 inzip_name_ = new_inzip_name;
427                 return;
428         }
429         throw ExceptionMessage(ErrorException, _("Sync file failure"),
430                  bformat(_("Cannot copy file %1$s to %2$s.\n"
431                                  "Please check whether the directory exists and is writeable."),
432                                 from_utf8(old_emb_file), from_utf8(new_emb_file)));
433 }
434
435
436 bool operator==(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
437 {
438         return lhs.absFilename() == rhs.absFilename()
439                 && lhs.saveAbsPath() == rhs.saveAbsPath()
440                 && lhs.embedded() == rhs.embedded();
441 }
442
443
444 bool operator!=(EmbeddedFile const & lhs, EmbeddedFile const & rhs)
445 {
446         return !(lhs == rhs);
447 }
448
449
450 void EmbeddedFileList::enable(bool enabled, Buffer & buffer, bool updateFile)
451 {
452         // update embedded file list
453         update(buffer);
454         
455         int count_embedded = 0;
456         int count_external = 0;
457         iterator it = begin();
458         iterator it_end = end();
459         // an exception may be thrown
460         for (; it != it_end; ++it) {
461                 it->enable(enabled, buffer, updateFile);
462                 if (it->embedded())
463                         ++count_embedded;
464                 else
465                         ++count_external;
466         }
467         // if operation is successful (no exception is thrown)
468         buffer.params().embedded = enabled;
469
470         // if the operation is successful, update insets
471         for (it = begin(); it != it_end; ++it)
472                 it->updateInsets();
473
474         if (!updateFile || (count_external == 0 && count_embedded == 0))
475                 return;
476
477         // show result
478         if (enabled) {
479                 docstring const msg = bformat(_("%1$d external files are ignored.\n"
480                         "%2$d embeddable files are embedded.\n"), count_external, count_embedded);
481                 Alert::information(_("Packing all files"), msg);
482         } else {
483                 docstring const msg = bformat(_("%1$d external files are ignored.\n"
484                         "%2$d embedded files are extracted.\n"), count_external, count_embedded);
485                 Alert::information(_("Unpacking all files"), msg);
486         }
487 }
488
489
490 void EmbeddedFileList::registerFile(EmbeddedFile const & file,
491         Inset const * inset, Buffer const & buffer)
492 {
493         LASSERT(!buffer.embedded() || file.isEnabled(), /**/);
494
495         string newfile = file.absFilename();
496         iterator efp = findFile(newfile);
497         if (efp != end()) {
498                 if (efp->embedded() != file.embedded()) {
499                         Alert::error(_("Wrong embedding status."),
500                                 bformat(_("File %1$s is included in more than one insets, "
501                                         "but with different embedding status. Assuming embedding status."),
502                                         from_utf8(efp->outputFilename())));
503                         efp->setEmbed(true);
504                         // update the inset with this embedding status.
505                         const_cast<Inset*>(inset)->updateEmbeddedFile(*efp);
506                 }
507                 efp->addInset(inset);
508                 return;
509         }
510         file.clearInsets();
511         push_back(file);
512         back().addInset(inset);
513 }
514
515
516 EmbeddedFileList::const_iterator 
517         EmbeddedFileList::findFile(std::string const & filename) const
518 {
519         // try to find this file from the list
520         std::vector<EmbeddedFile>::const_iterator it = begin();
521         std::vector<EmbeddedFile>::const_iterator it_end = end();
522         for (; it != it_end; ++it)
523                 if (it->absFilename() == filename)
524                         return it;
525         return end();
526 }
527
528
529 EmbeddedFileList::iterator 
530         EmbeddedFileList::findFile(std::string const & filename)
531 {
532         // try to find this file from the list
533         std::vector<EmbeddedFile>::iterator it = begin();
534         std::vector<EmbeddedFile>::iterator it_end = end();
535         for (; it != it_end; ++it)
536                 if (it->absFilename() == filename)
537                         return it;
538         return end();
539 }
540
541
542 void EmbeddedFileList::validate(Buffer const & buffer)
543 {
544         clear();
545         
546         for (InsetIterator it = inset_iterator_begin(buffer.inset()); it; ++it)
547                 it->registerEmbeddedFiles(*this);
548
549         iterator it = begin();
550         iterator it_end = end();
551         for (; it != it_end; ++it) {
552                 if (buffer.embedded() && it->embedded())
553                         // An exception will be raised if inzip file does not exist
554                         it->syncInzipFile(buffer.filePath());
555                 else
556                         // inzipName may be OS dependent
557                         it->setInzipName(it->calcInzipName(buffer.filePath()));
558         }
559         for (it = begin(); it != it_end; ++it)
560                 it->updateInsets();
561         
562         if (!buffer.embedded())
563                 return;
564
565         // check if extra embedded files exist
566         vector<string> extra = buffer.params().extraEmbeddedFiles();
567         vector<string>::iterator e_it = extra.begin();
568         vector<string>::iterator e_end = extra.end();
569         for (; e_it != e_end; ++e_it) {
570                 EmbeddedFile file = EmbeddedFile(*e_it, buffer.filePath());
571                 // do not update from external file
572                 file.enable(true, buffer, false);
573                 // but we do need to check file existence.
574                 if (!FileName(file.embeddedFile()).exists())
575                         throw ExceptionMessage(ErrorException, _("Failed to open file"),
576                                 bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"),
577                                         file.displayName()));
578         }
579 }
580
581
582 void EmbeddedFileList::update(Buffer const & buffer)
583 {
584         clear();
585
586         for (InsetIterator it = inset_iterator_begin(buffer.inset()); it; ++it)
587                 it->registerEmbeddedFiles(*this);
588
589         // add extra embedded files
590         vector<string> extra = buffer.params().extraEmbeddedFiles();
591         vector<string>::iterator it = extra.begin();
592         vector<string>::iterator it_end = extra.end();
593         for (; it != it_end; ++it) {
594                 EmbeddedFile file = EmbeddedFile(*it, buffer.filePath());
595                 file.setEmbed(true);
596                 file.enable(buffer.embedded(), buffer, false);
597                 insert(end(), file);
598         }
599 }
600
601
602 bool EmbeddedFileList::writeFile(DocFileName const & filename, Buffer const & buffer)
603 {
604         // file in the temporary path has the content
605         string const content = FileName(addName(buffer.temppath(),
606                 "content.lyx")).toFilesystemEncoding();
607
608         vector<pair<string, string> > filenames;
609         // add content.lyx to filenames
610         filenames.push_back(make_pair(content, "content.lyx"));
611         // prepare list of embedded file
612         update(buffer);
613         //
614         iterator it = begin();
615         iterator it_end = end();
616         for (; it != it_end; ++it) {
617                 if (it->embedded()) {
618                         string file = it->embeddedFile();
619                         if (!FileName(file).exists())
620                                 throw ExceptionMessage(ErrorException, _("Failed to write file"),
621                                         bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"),
622                                                 it->displayName()));
623                         filenames.push_back(make_pair(file, it->inzipName()));
624                         LYXERR(Debug::FILES, "Writing file " << it->outputFilename()
625                                 << " as " << it->inzipName() << endl);
626                 }
627         }
628         // write a zip file with all these files. Write to a temp file first, to
629         // avoid messing up the original file in case something goes terribly wrong.
630         DocFileName zipfile(addName(buffer.temppath(),
631                 onlyFilename(changeExtension(
632                         filename.toFilesystemEncoding(), ".zip"))));
633
634         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
635         // copy file back
636         if (!zipfile.copyTo(filename)) {
637                 Alert::error(_("Save failure"),
638                          bformat(_("Cannot create file %1$s.\n"
639                                            "Please check whether the directory exists and is writeable."),
640                                          from_utf8(filename.absFilename())));
641         }
642         return true;
643 }
644
645 } // namespace lyx