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