]> git.lyx.org Git - lyx.git/blobdiff - src/EmbeddedFiles.cpp
* support/FileName:
[lyx.git] / src / EmbeddedFiles.cpp
index 76d852a6b244afdd258592d37c1b3f517e8e6d31..2d2aef8c37b842a1c93d4f2340528fc9191529d2 100644 (file)
 #include "Buffer.h"
 #include "BufferParams.h"
 #include "Paragraph.h"
-#include "ParIterator.h"
+#include "InsetIterator.h"
 #include "debug.h"
 #include "gettext.h"
 #include "Format.h"
+#include "Lexer.h"
+#include "ErrorList.h"
 
 #include "frontends/alert.h"
 
-#include <boost/filesystem/operations.hpp>
-
 #include "support/filetools.h"
-#include "support/fs_extras.h"
 #include "support/convert.h"
+#include "support/lyxlib.h"
+#include "support/lstrings.h"
+
+#include "LyX.h"
+#include "Session.h"
 
 #include <sstream>
 #include <fstream>
@@ -46,27 +50,30 @@ using std::istringstream;
 
 namespace lyx {
 
-namespace fs = boost::filesystem;
 namespace Alert = frontend::Alert;
 
 using support::FileName;
 using support::DocFileName;
 using support::makeAbsPath;
 using support::addName;
-using support::onlyPath;
 using support::absolutePath;
 using support::onlyFilename;
 using support::makeRelPath;
 using support::changeExtension;
 using support::bformat;
-using support::zipFiles;
+using support::prefixIs;
+using support::sum;
+using support::makedir;
 
 
 EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name,
-       STATUS status, ParConstIterator const & pit)
-       : DocFileName(file, true), inzip_name_(inzip_name), status_(status),
-               valid_(true), par_it_(pit)
-{}
+       bool embed, Inset const * inset)
+       : DocFileName(file, true), inzip_name_(inzip_name), embedded_(embed),
+               inset_list_()
+{
+       if (inset != NULL)
+               inset_list_.push_back(inset);
+}
 
 
 string EmbeddedFile::embeddedFile(Buffer const * buf) const
@@ -75,9 +82,139 @@ string EmbeddedFile::embeddedFile(Buffer const * buf) const
 }
 
 
-void EmbeddedFile::setParIter(ParConstIterator const & pit)
+void EmbeddedFile::addInset(Inset const * inset)
+{
+       inset_list_.push_back(inset);
+}
+
+
+Inset const * EmbeddedFile::inset(int idx) const
+{
+       BOOST_ASSERT(idx < refCount());
+       // some embedded file do not have a valid par iterator
+       return inset_list_[idx];
+}
+
+
+void EmbeddedFile::saveBookmark(Buffer const * buf, int idx) const
+{
+       Inset const * ptr = inset(idx);
+       // This might not be the most efficient method ... 
+       for (InsetIterator it = inset_iterator_begin(buf->inset()); it; ++it)
+               if (&(*it) == ptr) {
+                       // this is basically BufferView::saveBookmark(0)
+                       LyX::ref().session().bookmarks().save(
+                               FileName(buf->absFileName()),
+                               it.bottom().pit(),
+                               it.bottom().pos(),
+                               it.paragraph().id(),
+                               it.pos(),
+                               0
+                       );
+               }
+       // this inset can not be located. There is something wrong that needs
+       // to be fixed.
+       BOOST_ASSERT(true);
+}
+
+
+string EmbeddedFile::availableFile(Buffer const * buf) const
+{
+       return embedded() ? embeddedFile(buf) : absFilename();
+}
+
+
+bool EmbeddedFile::extract(Buffer const * buf) const
+{
+       string ext_file = absFilename();
+       string emb_file = embeddedFile(buf);
+
+       FileName emb(emb_file);
+       FileName ext(ext_file);
+
+       if (!emb.exists())
+               return false;
+
+       // if external file already exists ...
+       if (ext.exists()) {
+               // no need to copy if the files are the same
+               if (sum(*this) == sum(FileName(emb_file)))
+                       return true;
+               // otherwise, ask if overwrite
+               int ret = Alert::prompt(
+                       _("Overwrite external file?"),
+                       bformat(_("External file %1$s already exists, do you want to overwrite it"),
+                               from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
+               if (ret != 0)
+                       // if the user does not want to overwrite, we still consider it
+                       // a successful operation.
+                       return true;
+       }
+       // copy file
+
+       // need to make directory?
+       FileName path = ext.onlyPath();
+       if (!path.isDirectory())
+               makedir(const_cast<char*>(path.absFilename().c_str()), 0755);
+       if (emb.copyTo(ext))
+               return true;
+       Alert::error(_("Copy file failure"),
+                bformat(_("Cannot copy file %1$s to %2$s.\n"
+                                "Please check whether the directory exists and is writeable."),
+                               from_utf8(emb_file), from_utf8(ext_file)));
+       //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
+       return false;
+}
+
+
+bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
+{
+       string ext_file = absFilename();
+       string emb_file = embeddedFile(buf);
+
+       FileName emb(emb_file);
+       FileName ext(ext_file);
+
+       if (!ext.exists())
+               return false;
+       
+       // if embedded file already exists ...
+       if (emb.exists()) {
+               // no need to copy if the files are the same
+               if (sum(*this) == sum(FileName(emb_file)))
+                       return true;
+               // other wise, ask if overwrite
+               int const ret = Alert::prompt(
+                       _("Update embedded file?"),
+                       bformat(_("Embedded file %1$s already exists, do you want to overwrite it"),
+                               from_utf8(ext_file)), 1, 1, _("&Overwrite"), _("&Cancel"));
+               if (ret != 0)
+                       // if the user does not want to overwrite, we still consider it
+                       // a successful operation.
+                       return true;
+       }
+       // copy file
+       // need to make directory?
+       FileName path = emb.onlyPath();
+       if (!path.isDirectory())
+               makedir(const_cast<char*>(path.absFilename().c_str()), 0755);
+       if (ext.copyTo(emb))
+               return true;
+       Alert::error(_("Copy file failure"),
+                bformat(_("Cannot copy file %1$s to %2$s.\n"
+                          "Please check whether the directory exists and is writeable."),
+                               from_utf8(ext_file), from_utf8(emb_file)));
+       //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
+       return false;
+}
+
+
+void EmbeddedFile::updateInsets(Buffer const * buf) const
 {
-       par_it_ = pit;
+       vector<Inset const *>::const_iterator it = inset_list_.begin();
+       vector<Inset const *>::const_iterator it_end = inset_list_.end();
+       for (; it != it_end; ++it)
+               const_cast<Inset *>(*it)->updateEmbeddedFile(*buf, *this);
 }
 
 
@@ -89,246 +226,171 @@ bool EmbeddedFiles::enabled() const
 
 void EmbeddedFiles::enable(bool flag)
 {
-       // FIXME: there are much more to do here. 
-       // If we enable embedding, it is maybe a good idea to copy embedded files
-       // to temppath()
-       // if we disable embedding, embedded files need to be copied to their
-       // original positions.
        if (enabled() != flag) {
-               // file will be changed
+               // if enable, copy all files to temppath()
+               // if disable, extract all files
+               if ((flag && !updateFromExternalFile()) || (!flag && !extract()))
+                       return;
+               // if operation is successful
                buffer_->markDirty();
                buffer_->params().embedded = flag;
+               if (flag)
+                       updateInsets();
        }
 }
 
 
-void EmbeddedFiles::registerFile(string const & filename,
-       EmbeddedFile::STATUS status, ParConstIterator const & pit)
+EmbeddedFile & EmbeddedFiles::registerFile(string const & filename,
+       bool embed, Inset const * inset, string const & inzipName)
 {
+       // filename can be relative or absolute, translate to absolute filename
        string abs_filename = makeAbsPath(filename, buffer_->filePath()).absFilename();
        // try to find this file from the list
        EmbeddedFileList::iterator it = file_list_.begin();
        EmbeddedFileList::iterator it_end = file_list_.end();
        for (; it != it_end; ++it)
-               if (it->absFilename() == abs_filename)
+               if (it->absFilename() == abs_filename || it->embeddedFile(buffer_) == abs_filename)
                        break;
-       // find this filename
+       // find this filename, keep the original embedding status
        if (it != file_list_.end()) {
-               it->setParIter(pit);
-               it->setStatus(status);
-               it->validate();
-               return;
-       }
-       // register a new one, using relative file path as inzip_name
-       string inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
-               from_utf8(buffer_->fileName())));
-       // if inzip_name is an absolute path, use filename only to avoid
-       // leaking of filesystem information in inzip_name
-       if (absolutePath(inzip_name))
-               inzip_name = onlyFilename(inzip_name);
-       // if this name has been used...
-       // use _1_name, _2_name etc
-       if (!validInzipName(inzip_name)) {
-               size_t i = 1;
-               string tmp = inzip_name;
-               do {
-                       inzip_name = convert<string>(i) + "_" + tmp;
-               } while (!validInzipName(inzip_name));
+               it->addInset(inset);
+               return *it;
        }
-       file_list_.push_back(EmbeddedFile(abs_filename, inzip_name, status, pit));
+       //
+       file_list_.push_back(EmbeddedFile(abs_filename, 
+               getInzipName(abs_filename, inzipName), embed, inset));
+       return file_list_.back();
 }
 
 
 void EmbeddedFiles::update()
 {
-       // invalidate all files, obsolete files will then not be validated by the
-       // following document scan. These files will still be kept though, because
-       // they may be added later and their embedding status will be meaningful
-       // again (thinking of cut/paste of an InsetInclude).
-       EmbeddedFileList::iterator it = file_list_.begin();
-       EmbeddedFileList::iterator it_end = file_list_.end();
-       for (; it != it_end; ++it)
-               it->invalidate();
-
-       ParIterator pit = buffer_->par_iterator_begin();
-       ParIterator pit_end = buffer_->par_iterator_end();
-       for (; pit != pit_end; ++pit) {
-               // For each paragraph, traverse its insets and register embedded files
-               InsetList::const_iterator iit = pit->insetlist.begin();
-               InsetList::const_iterator iit_end = pit->insetlist.end();
-               for (; iit != iit_end; ++iit) {
-                       Inset & inset = *iit->inset;
-                       inset.registerEmbeddedFiles(*buffer_, *this, pit);
-               }
-       }
-       LYXERR(Debug::FILES) << "Manifest updated: " << endl
-               << *this
-               << "End Manifest" << endl;
+       file_list_.clear();
+
+       for (InsetIterator it = inset_iterator_begin(buffer_->inset()); it; ++it)
+               it->registerEmbeddedFiles(*buffer_, *this);
 }
 
 
-bool EmbeddedFiles::write(DocFileName const & filename)
+bool EmbeddedFiles::writeFile(DocFileName const & filename)
 {
        // file in the temporary path has the content
        string const content = FileName(addName(buffer_->temppath(),
-               onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding();
+               "content.lyx")).toFilesystemEncoding();
 
-       // get a file list and write a manifest file
        vector<pair<string, string> > filenames;
-       string const manifest = FileName(
-               addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding();
-
-       // write a manifest file
-       ofstream os(manifest.c_str());
-       os << *this;
-       os.close();
+       // add content.lyx to filenames
+       filenames.push_back(make_pair(content, "content.lyx"));
        // prepare list of embedded file
        EmbeddedFileList::iterator it = file_list_.begin();
        EmbeddedFileList::iterator it_end = file_list_.end();
        for (; it != it_end; ++it) {
-               if (it->valid() && it->embedded()) {
-                       // use external file if possible
-                       if (it->status() != EmbeddedFile::EMBEDDED && fs::exists(it->absFilename()))
-                               filenames.push_back(make_pair(it->absFilename(), it->inzipName()));
-                       // use embedded file (AUTO or EMBEDDED mode)
-                       else if(fs::exists(it->embeddedFile(buffer_)))
-                               filenames.push_back(make_pair(it->embeddedFile(buffer_), it->inzipName()));
-                       else
+               if (it->embedded()) {
+                       string file = it->availableFile(buffer_);
+                       if (file.empty())
                                lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl;
+                       else
+                               filenames.push_back(make_pair(file, it->inzipName()));
                }
        }
-       // add filename (.lyx) and manifest to filenames
-       filenames.push_back(make_pair(content, onlyFilename(filename.toFilesystemEncoding())));
-       filenames.push_back(make_pair(manifest, "manifest.txt"));
        // write a zip file with all these files. Write to a temp file first, to
        // avoid messing up the original file in case something goes terribly wrong.
        DocFileName zipfile(addName(buffer_->temppath(),
                onlyFilename(changeExtension(
                        filename.toFilesystemEncoding(), ".zip"))));
 
-       zipFiles(zipfile, filenames);
+       ::zipFiles(zipfile.toFilesystemEncoding(), filenames);
        // copy file back
-       try {
-               fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false);
-       } catch (fs::filesystem_error const & fe) {
+       if (!zipfile.copyTo(filename)) {
                Alert::error(_("Save failure"),
                                 bformat(_("Cannot create file %1$s.\n"
                                           "Please check whether the directory exists and is writeable."),
                                         from_utf8(filename.absFilename())));
-               LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
+               //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
        }
        return true;
 }
 
 
-string EmbeddedFiles::filename(size_t idx) const
-{
-       return (file_list_.begin() + idx)->absFilename();
-}
-
-
-EmbeddedFile::STATUS EmbeddedFiles::status(size_t idx) const
+EmbeddedFiles::EmbeddedFileList::const_iterator
+EmbeddedFiles::find(std::string filename) const
 {
-       return (file_list_.begin() + idx)->status();
+       EmbeddedFileList::const_iterator it = file_list_.begin();
+       EmbeddedFileList::const_iterator it_end = file_list_.end();
+       for (; it != it_end; ++it)
+               if (it->absFilename() == filename || it->embeddedFile(buffer_) == filename)     
+                       return it;
+       return file_list_.end();
 }
 
 
-void EmbeddedFiles::setStatus(size_t idx, EmbeddedFile::STATUS status)
+bool EmbeddedFiles::extract() const
 {
-       if ((file_list_.begin() + idx)->status() != status) {
-               // file will be changed
-               buffer_->markDirty();
-               (file_list_.begin() + idx)->setStatus(status);
-       }
+       EmbeddedFileList::const_iterator it = file_list_.begin();
+       EmbeddedFileList::const_iterator it_end = file_list_.end();
+       for (; it != it_end; ++it)
+               if (it->embedded())
+                       if(!it->extract(buffer_))
+                               return false;
+       return true;
 }
 
 
-bool EmbeddedFiles::validInzipName(string const & name)
+bool EmbeddedFiles::updateFromExternalFile() const
 {
-       EmbeddedFileList::iterator it = file_list_.begin();
-       EmbeddedFileList::iterator it_end = file_list_.end();
+       EmbeddedFileList::const_iterator it = file_list_.begin();
+       EmbeddedFileList::const_iterator it_end = file_list_.end();
        for (; it != it_end; ++it)
-                       if (it->inzipName() == name)
+               if (it->embedded())
+                       if (!it->updateFromExternalFile(buffer_))
                                return false;
        return true;
 }
 
 
-istream & operator>> (istream & is, EmbeddedFiles & files)
+string const EmbeddedFiles::getInzipName(string const & abs_filename, string const & name)
 {
-       files.clear();
-       string tmp;
-       getline(is, tmp);
-       // get version
-       istringstream itmp(tmp);
-       int version;
-       itmp.ignore(string("# LyX manifest version ").size());
-       itmp >> version;
-
-       if (version != 1) {
-               lyxerr << "This version of LyX can only read LyX manifest version 1" << endl;
-               return is;
-       }
-
-       getline(is, tmp);
-       if (tmp != "<manifest>") {
-               lyxerr << "Invalid manifest file, lacking <manifest>" << endl;
-               return is;
-       }
-       // manifest file may be messed up, be carefully
-       while (is.good()) {
-               getline(is, tmp);
-               if (tmp != "<file>")
-                       break;
-
-               string fname;
-               getline(is, fname);
-               string inzip_name;
-               getline(is, inzip_name);
-               getline(is, tmp);
-               istringstream itmp(tmp);
-               int status;
-               itmp >> status;
-
-               getline(is, tmp);
-               if (tmp != "</file>") {
-                       lyxerr << "Invalid manifest file, lacking </file>" << endl;
-                       break;
-               }
-
-               files.registerFile(fname, static_cast<EmbeddedFile::STATUS>(status));
-       };
-       // the last line must be </manifest>
-       if (tmp != "</manifest>") {
-               lyxerr << "Invalid manifest file, lacking </manifest>" << endl;
-               return is;
+       // register a new one, using relative file path as inzip_name
+       string inzip_name = name;
+       if (name.empty())
+               inzip_name = to_utf8(makeRelPath(from_utf8(abs_filename),
+                       from_utf8(buffer_->filePath())));
+       // if inzip_name is an absolute path, use filename only to avoid
+       // leaking of filesystem information in inzip_name
+       // The second case covers cases '../path/file' and '.'
+       if (absolutePath(inzip_name) || prefixIs(inzip_name, "."))
+               inzip_name = onlyFilename(abs_filename);
+       // if this name has been used...
+       // use _1_name, _2_name etc
+       string tmp = inzip_name;
+       EmbeddedFileList::iterator it;
+       EmbeddedFileList::iterator it_end = file_list_.end();
+       bool unique_name = false;
+       size_t i = 0;
+       while (!unique_name) {
+               unique_name = true;
+               if (i > 0)
+                       inzip_name = convert<string>(i) + "_" + tmp;
+               it = file_list_.begin();
+               for (; it != it_end; ++it)
+                       if (it->inzipName() == inzip_name) {
+                               unique_name = false;
+                               ++i;
+                               break;
+                       }
        }
-       return is;
+       return inzip_name;
 }
 
 
-ostream & operator<< (ostream & os, EmbeddedFiles const & files)
+void EmbeddedFiles::updateInsets() const
 {
-       // store a version so that operator >> can read later versions
-       // using version information.
-       os << "# lyx manifest version 1\n";
-       os << "<manifest>\n";
-       EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin();
-       EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end();
-       for (; it != it_end; ++it) {
-               if (!it->valid())
-                       continue;
-               // use differnt lines to make reading easier.
-               os << "<file>\n"
-                       // save the relative path
-                       << to_utf8(makeRelPath(from_utf8(it->absFilename()),
-                               from_utf8(files.buffer_->filePath()))) << '\n'
-                       << it->inzipName() << '\n'
-                       << it->status() << '\n'
-                       << "</file>\n";
-       }
-       os << "</manifest>\n";
-       return os;
+       EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
+       EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
+       for (; it != it_end; ++it)
+               if (it->refCount() > 0)
+                       it->updateInsets(buffer_);
 }
 
+
 }