]> git.lyx.org Git - lyx.git/blob - src/EmbeddedFiles.cpp
Further cleanup of InsetFlex, InsetCollapsable and InsetLayout:
[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                 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->absFileName()),
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         return embedded() ? embeddedFile(buf) : absFilename();
129 }
130
131
132 bool EmbeddedFile::extract(Buffer const * buf) const
133 {
134         string ext_file = absFilename();
135         string emb_file = embeddedFile(buf);
136
137         if (!fs::exists(emb_file))
138                 return false;
139
140         // if external file already exists ...
141         if (fs::exists(ext_file)) {
142                 // no need to copy if the files are the same
143                 if (sum(*this) == sum(FileName(emb_file)))
144                         return true;
145                 // otherwise, ask if overwrite
146                 int ret = Alert::prompt(
147                         _("Overwrite external file?"),
148                         bformat(_("External file %1$s already exists, do you want to overwrite it"),
149                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
150                 if (ret != 0)
151                         // if the user does not want to overwrite, we still consider it
152                         // a successful operation.
153                         return true;
154         }
155         // copy file
156         try {
157                 // need to make directory?
158                 string path = onlyPath(ext_file);
159                 if (!fs::is_directory(path))
160                         makedir(const_cast<char*>(path.c_str()), 0755);
161                 fs::copy_file(emb_file, ext_file, false);
162                 return true;
163         } catch (fs::filesystem_error const & fe) {
164                 Alert::error(_("Copy file failure"),
165                          bformat(_("Cannot copy file %1$s to %2$s.\n"
166                                    "Please check whether the directory exists and is writeable."),
167                                         from_utf8(emb_file), from_utf8(ext_file)));
168                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
169         }
170         return false;
171 }
172
173
174 bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
175 {
176         string ext_file = absFilename();
177         string emb_file = embeddedFile(buf);
178
179         if (!fs::exists(ext_file))
180                 return false;
181         
182         // if embedded file already exists ...
183         if (fs::exists(emb_file)) {
184                 // no need to copy if the files are the same
185                 if (sum(*this) == sum(FileName(emb_file)))
186                         return true;
187                 // other wise, ask if overwrite
188                 int const ret = Alert::prompt(
189                         _("Update embedded file?"),
190                         bformat(_("Embedded file %1$s already exists, do you want to overwrite it"),
191                                 from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
192                 if (ret != 0)
193                         // if the user does not want to overwrite, we still consider it
194                         // a successful operation.
195                         return true;
196         }
197         // copy file
198         try {
199                 // need to make directory?
200                 string path = onlyPath(emb_file);
201                 if (!fs::is_directory(path))
202                         makedir(const_cast<char*>(path.c_str()), 0755);
203                 fs::copy_file(ext_file, emb_file, false);
204                 return true;
205         } catch (fs::filesystem_error const & fe) {
206                 Alert::error(_("Copy file failure"),
207                          bformat(_("Cannot copy file %1$s to %2$s.\n"
208                                    "Please check whether the directory exists and is writeable."),
209                                         from_utf8(ext_file), from_utf8(emb_file)));
210                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
211         }
212         return false;
213 }
214
215
216 void EmbeddedFile::updateInsets(Buffer const * buf) const
217 {
218         vector<Inset const *>::const_iterator it = inset_list_.begin();
219         vector<Inset const *>::const_iterator it_end = inset_list_.end();
220         for (; it != it_end; ++it)
221                 const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
222 }
223
224
225 bool EmbeddedFiles::enabled() const
226 {
227         return buffer_->params().embedded;
228 }
229
230
231 void EmbeddedFiles::enable(bool flag)
232 {
233         if (enabled() != flag) {
234                 // if enable, copy all files to temppath()
235                 // if disable, extract all files
236                 if ((flag && !updateFromExternalFile()) || (!flag && !extract()))
237                         return;
238                 // if operation is successful
239                 buffer_->markDirty();
240                 buffer_->params().embedded = flag;
241                 if (flag)
242                         updateInsets();
243         }
244 }
245
246
247 EmbeddedFile & EmbeddedFiles::registerFile(string const & filename,
248         bool embed, Inset const * inset, string const & inzipName)
249 {
250         // filename can be relative or absolute, translate to absolute filename
251         string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
252         // try to find this file from the list
253         EmbeddedFileList::iterator it = file_list_.begin();
254         EmbeddedFileList::iterator it_end = file_list_.end();
255         for (; it != it_end; ++it)
256                 if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
257                         break;
258         // find this filename, keep the original embedding status
259         if (it != file_list_.end()) {
260                 it->addInset(inset);
261                 return *it;
262         }
263         //
264         file_list_.push_back(EmbeddedFile(abs_filename, 
265                 getInzipName(abs_filename, inzipName), embed, inset));
266         return file_list_.back();
267 }
268
269
270 void EmbeddedFiles::update()
271 {
272         file_list_.clear();
273
274         for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
275                 it->registerEmbeddedFiles(*buffer_, *this);
276 }
277
278
279 bool EmbeddedFiles::writeFile(DocFileName const & filename)
280 {
281         // file in the temporary path has the content
282         string const content = FileName(addName(buffer_->temppath(),
283                 "content.lyx")).toFilesystemEncoding();
284
285         vector<pair<string, string> > filenames;
286         // add content.lyx to filenames
287         filenames.push_back(make_pair(content, "content.lyx"));
288         // prepare list of embedded file
289         EmbeddedFileList::iterator it = file_list_.begin();
290         EmbeddedFileList::iterator it_end = file_list_.end();
291         for (; it != it_end; ++it) {
292                 if (it->embedded()) {
293                         string file = it->availableFile(buffer_);
294                         if (file.empty())
295                                 lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
296                         else
297                                 filenames.push_back(make_pair(file, it->inzipName()));
298                 }
299         }
300         // write a zip file with all these files. Write to a temp file first, to
301         // avoid messing up the original file in case something goes terribly wrong.
302         DocFileName zipfile(addName(buffer_->temppath(),
303                 onlyFilename(changeExtension(
304                         filename.toFilesystemEncoding(), ".zip"))));
305
306         ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
307         // copy file back
308         try {
309                 fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
310         } catch (fs::filesystem_error const & fe) {
311                 Alert::error(_("Save failure"),
312                                  bformat(_("Cannot create file %1$s.\n"
313                                            "Please check whether the directory exists and is writeable."),
314                                          from_utf8(filename.absFilename())));
315                 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
316         }
317         return true;
318 }
319
320
321 EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
322 {
323         EmbeddedFileList::const_iterator it = file_list_.begin();
324         EmbeddedFileList::const_iterator it_end = file_list_.end();
325         for (; it != it_end; ++it)
326                 if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)     
327                         return it;
328         return file_list_.end();
329 }
330
331
332 bool EmbeddedFiles::extract() const
333 {
334         EmbeddedFileList::const_iterator it = file_list_.begin();
335         EmbeddedFileList::const_iterator it_end = file_list_.end();
336         for (; it != it_end; ++it)
337                 if (it->embedded())
338                         if(!it->extract(buffer_))
339                                 return false;
340         return true;
341 }
342
343
344 bool EmbeddedFiles::updateFromExternalFile() const
345 {
346         EmbeddedFileList::const_iterator it = file_list_.begin();
347         EmbeddedFileList::const_iterator it_end = file_list_.end();
348         for (; it != it_end; ++it)
349                 if (it->embedded())
350                         if (!it->updateFromExternalFile(buffer_))
351                                 return false;
352         return true;
353 }
354
355
356 string const EmbeddedFiles::getInzipName(string const & abs_filename, string const & name)
357 {
358         // register a new one, using relative file path as inzip_name
359         string inzip_name = name;
360         if (name.empty())
361                 inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
362                         from_utf8(buffer_->filePath())));
363         // if inzip_name is an absolute path, use filename only to avoid
364         // leaking of filesystem information in inzip_name
365         // The second case covers cases '../path/file' and '.'
366         if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
367                 inzip_name = onlyFilename(abs_filename);
368         // if this name has been used...
369         // use _1_name, _2_name etc
370         string tmp = inzip_name;
371         EmbeddedFileList::iterator it;
372         EmbeddedFileList::iterator it_end = file_list_.end();
373         bool unique_name = false;
374         size_t i = 0;
375         while (!unique_name) {
376                 unique_name = true;
377                 if (i > 0)
378                         inzip_name = convert<string>(i) + "_" + tmp;
379                 it = file_list_.begin();
380                 for (; it != it_end; ++it)
381                         if (it->inzipName() == inzip_name) {
382                                 unique_name = false;
383                                 ++i;
384                                 break;
385                         }
386         }
387         return inzip_name;
388 }
389
390
391 void EmbeddedFiles::updateInsets() const
392 {
393         EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
394         EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
395         for (; it != it_end; ++it)
396                 if (it->refCount() > 0)
397                         it->updateInsets(buffer_);
398 }
399
400
401 }