]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Showing a message box after embedding status 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
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 <sstream>
38 #include <fstream>
39 #include <utility>
40
41 using namespace std;
42 using namespace lyx::support;
43
44 namespace lyx {
45
46 namespace Alert = frontend::Alert;
47
48
49 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
50         bool embed, Inset const * inset)
51         : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
52                 inset_list_()
53 {
54         if (inset != NULL)
55                 inset_list_.push_back(inset);
56 }
57
58
59 string EmbeddedFile::embeddedFile(Buffer const * buf) const
60 {
61         return addName(buf->temppath(), inzip_name_);
62 }
63
64
65 void EmbeddedFile::addInset(Inset const * inset)
66 {
67         inset_list_.push_back(inset);
68 }
69
70
71 Inset const * EmbeddedFile::inset(int idx) const
72 {
73         BOOST_ASSERT(idx < refCount());
74         // some embedded file do not have a valid par iterator
75         return inset_list_[idx];
76 }
77
78
79 void EmbeddedFile::saveBookmark(Buffer const * buf, int idx) const
80 {
81         Inset const * ptr = inset(idx);
82         // This might not be the most efficient method ... 
83         for (InsetIterator it = inset_iterator_begin(buf->inset()); it; ++it)
84                 if (&(*it) == ptr) {
85                         // this is basically BufferView::saveBookmark(0)
86                         LyX::ref().session().bookmarks().save(
87                                 FileName(buf->absFileName()),
88                                 it.bottom().pit(),
89                                 it.bottom().pos(),
90                                 it.paragraph().id(),
91                                 it.pos(),
92                                 0
93                         );
94                 }
95         // this inset can not be located. There is something wrong that needs
96         // to be fixed.
97         BOOST_ASSERT(true);
98 }
99
100
101 string EmbeddedFile::availableFile(Buffer const * buf) const
102 {
103         return embedded() ? embeddedFile(buf) : absFilename();
104 }
105
106
107 bool EmbeddedFile::extract(Buffer const * buf) const
108 {
109         string ext_file = absFilename();
110         string emb_file = embeddedFile(buf);
111
112         FileName emb(emb_file);
113         FileName ext(ext_file);
114
115         if (!emb.exists())
116                 return false;
117
118         // if external file already exists ...
119         if (ext.exists()) {
120                 // no need to copy if the files are the same
121                 if (checksum() == FileName(emb_file).checksum())
122                         return true;
123                 // otherwise, ask if overwrite
124                 int ret = Alert::prompt(
125                         _("Overwrite external file?"),
126                         bformat(_("External file %1$s already exists, do you want to overwrite it"),
127                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
128                 if (ret != 0)
129                         // if the user does not want to overwrite, we still consider it
130                         // a successful operation.
131                         return true;
132         }
133         // copy file
134
135         // need to make directory?
136         FileName path = ext.onlyPath();
137         if (!path.createPath()) {
138                 throw ExceptionMessage(ErrorException, _("Copy file failure"),
139                         bformat(_("Cannot create file path '%1$s'.\n"
140                         "Please check whether the path is writeable."),
141                         from_utf8(path.absFilename())));
142                 return false;
143         }
144
145         if (emb.copyTo(ext))
146                 return true;
147
148         throw ExceptionMessage(ErrorException, _("Copy file failure"),
149                  bformat(_("Cannot copy file %1$s to %2$s.\n"
150                                  "Please check whether the directory exists and is writeable."),
151                                 from_utf8(emb_file), from_utf8(ext_file)));
152         return false;
153 }
154
155
156 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
157 {
158         string ext_file = absFilename();
159         string emb_file = embeddedFile(buf);
160
161         FileName emb(emb_file);
162         FileName ext(ext_file);
163
164         if (!ext.exists())
165                 return false;
166         
167         // if embedded file already exists ...
168         if (emb.exists()) {
169                 // no need to copy if the files are the same
170                 if (checksum() == FileName(emb_file).checksum())
171                         return true;
172                 // other wise, ask if overwrite
173                 int const ret = Alert::prompt(
174                         _("Update embedded file?"),
175                         bformat(_("Embedded 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         // need to make directory?
184         FileName path = emb.onlyPath();
185         if (!path.isDirectory())
186                 path.createPath();
187         if (ext.copyTo(emb))
188                 return true;
189         throw ExceptionMessage(ErrorException, 
190                 _("Copy file failure"),
191                 bformat(_("Cannot copy file %1$s to %2$s.\n"
192                            "Please check whether the directory exists and is writeable."),
193                                 from_utf8(ext_file), from_utf8(emb_file)));
194         //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
195         return false;
196 }
197
198
199 void EmbeddedFile::updateInsets(Buffer const * buf) const
200 {
201         vector<Inset const *>::const_iterator it = inset_list_.begin();
202         vector<Inset const *>::const_iterator it_end = inset_list_.end();
203         for (; it != it_end; ++it)
204                 const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
205 }
206
207
208 bool EmbeddedFiles::enabled() const
209 {
210         return buffer_->params().embedded;
211 }
212
213
214 void EmbeddedFiles::enable(bool flag)
215 {
216         if (enabled() != flag) {
217                 // update embedded file list
218                 update();
219                 // An exception may be thrown.
220                 if (flag)
221                         // if enable, copy all files to temppath()
222                         updateFromExternalFile();
223                 else
224                         // if disable, extract all files
225                         extractAll();
226                 // if operation is successful (no exception is thrown)
227                 buffer_->markDirty();
228                 buffer_->params().embedded = flag;
229                 if (flag)
230                         updateInsets();
231         }
232 }
233
234
235 EmbeddedFile & EmbeddedFiles::registerFile(string const & filename,
236         bool embed, Inset const * inset, string const & inzipName)
237 {
238         // filename can be relative or absolute, translate to absolute filename
239         string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
240         // try to find this file from the list
241         EmbeddedFileList::iterator it = file_list_.begin();
242         EmbeddedFileList::iterator it_end = file_list_.end();
243         for (; it != it_end; ++it)
244                 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
245                         break;
246         // find this filename, keep the original embedding status
247         if (it != file_list_.end()) {
248                 it->addInset(inset);
249                 return *it;
250         }
251         //
252         file_list_.push_back(EmbeddedFile(abs_filename, 
253                 getInzipName(abs_filename, inzipName), embed, inset));
254         return file_list_.back();
255 }
256
257
258 void EmbeddedFiles::update()
259 {
260         file_list_.clear();
261
262         for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
263                 it->registerEmbeddedFiles(*buffer_, *this);
264 }
265
266
267 bool EmbeddedFiles::writeFile(DocFileName const & filename)
268 {
269         // file in the temporary path has the content
270         string const content = FileName(addName(buffer_->temppath(),
271                 "content.lyx")).toFilesystemEncoding();
272
273         vector<pair<string, string> > filenames;
274         // add content.lyx to filenames
275         filenames.push_back(make_pair(content, "content.lyx"));
276         // prepare list of embedded file
277         EmbeddedFileList::iterator it = file_list_.begin();
278         EmbeddedFileList::iterator it_end = file_list_.end();
279         for (; it != it_end; ++it) {
280                 if (it->embedded()) {
281                         string file = it->availableFile(buffer_);
282                         if (file.empty())
283                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
284                         else
285                                 filenames.push_back(make_pair(file, it->inzipName()));
286                 }
287         }
288         // write a zip file with all these files. Write to a temp file first, to
289         // avoid messing up the original file in case something goes terribly wrong.
290         DocFileName zipfile(addName(buffer_->temppath(),
291                 onlyFilename(changeExtension(
292                         filename.toFilesystemEncoding(), ".zip"))));
293
294         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
295         // copy file back
296         if (!zipfile.copyTo(filename)) {
297                 Alert::error(_("Save failure"),
298                                  bformat(_("Cannot create file %1$s.\n"
299                                            "Please check whether the directory exists and is writeable."),
300                                          from_utf8(filename.absFilename())));
301                 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
302         }
303         return true;
304 }
305
306
307 EmbeddedFiles::EmbeddedFileList::const_iterator
308 EmbeddedFiles::find(string filename) const
309 {
310         EmbeddedFileList::const_iterator it = file_list_.begin();
311         EmbeddedFileList::const_iterator it_end = file_list_.end();
312         for (; it != it_end; ++it)
313                 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)     
314                         return it;
315         return file_list_.end();
316 }
317
318
319 bool EmbeddedFiles::extractAll() const
320 {
321         EmbeddedFileList::const_iterator it = file_list_.begin();
322         EmbeddedFileList::const_iterator it_end = file_list_.end();
323         int count_extracted = 0;
324         int count_external = 0;
325         for (; it != it_end; ++it)
326                 if (it->embedded()) {
327                         if(!it->extract(buffer_)) {
328                                 throw ExceptionMessage(ErrorException,
329                                         _("Failed to extract file"),
330                                         bformat(_("Error: can not extract file %1$s.\n"), it->displayName()));
331                         } else
332                                 count_extracted += 1;
333                 } else
334                         count_external += 1;
335         docstring const msg = bformat(_("%1$d external files are ignored.\n"
336                 "%2$d embedded files are extracted.\n"), count_external, count_extracted);
337         Alert::information(_("Unpacking all files"), msg);
338         return true;
339 }
340
341
342 bool EmbeddedFiles::updateFromExternalFile() const
343 {
344         EmbeddedFileList::const_iterator it = file_list_.begin();
345         EmbeddedFileList::const_iterator it_end = file_list_.end();
346         int count_embedded = 0;
347         int count_external = 0;
348         for (; it != it_end; ++it)
349                 if (it->embedded()) {
350                         if (!it->updateFromExternalFile(buffer_)) {
351                                 throw ExceptionMessage(ErrorException,
352                                         _("Failed to embed file"),
353                                         bformat(_("Error: can not embed file %1$s.\n"), it->displayName()));
354                                 return false;
355                         } else
356                                 count_external += 1;
357                 } else
358                         count_external += 1;
359         docstring const msg = bformat(_("%1$d external files are ignored.\n"
360                 "%2$d embeddable files are embedded.\n"), count_external, count_embedded);
361         Alert::information(_("Packing all files"), msg);
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 (FileName(inzip_name).isAbsolute() || 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 void EmbeddedFiles::updateInsets() const
402 {
403         EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
404         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
405         for (; it != it_end; ++it)
406                 if (it->refCount() > 0)
407                         it->updateInsets(buffer_);
408 }
409
410
411 }