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