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