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