// -*- C++ -*- /** * \file EmbeddedFiles.cpp * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Bo Peng * * Full author contact details are available in file CREDITS. * */ #include #include "EmbeddedFiles.h" #include "Buffer.h" #include "BufferParams.h" #include "ErrorList.h" #include "Format.h" #include "InsetIterator.h" #include "Lexer.h" #include "LyX.h" #include "Paragraph.h" #include "Session.h" #include "frontends/alert.h" #include "support/debug.h" #include "support/filetools.h" #include "support/gettext.h" #include "support/convert.h" #include "support/lstrings.h" #include "support/ExceptionMessage.h" #include "support/FileZipListDir.h" #include #include #include using namespace std; using namespace lyx::support; namespace lyx { /** Dir name used for ".." in the bundled file. Under the lyx temp directory, content.lyx and its embedded files are usually saved as $temp/$embDirName/file.lyx $temp/$embDirName/figure1.png for ./figure1.png) $temp/$embDirName/sub/figure2.png for ./sub/figure2.png) This works fine for embedded files that are in the current or deeper directory of the document directory, but not for files such as ../figures/figure.png. A unique name $upDirName is chosen to represent .. in such filenames so that 'up' directories can be stored 'down' the directory tree: $temp/$embDirName/$upDirName/figures/figure.png for ../figures/figure.png $temp/$embDirName/$upDirName/$upDirName/figure.png for ../../figure.png This name has to be fixed because it is used in lyx bundled .zip file. Using a similar trick, we use $absDirName for absolute path so that an absolute filename can be saved as $temp/$embDirName/$absDirName/a/absolute/path for /a/absolute/path */ const std::string embDirName = "LyX.Embedded.Files"; const std::string upDirName = "LyX.Embed.Dir.Up"; const std::string absDirName = "LyX.Embed.Dir.Abs"; namespace Alert = frontend::Alert; EmbeddedFile::EmbeddedFile(string const & file, std::string const & buffer_path) : DocFileName("", false), inzip_name_(""), embedded_(false), inset_list_(), temp_path_("") { set(file, buffer_path); } void EmbeddedFile::set(std::string const & filename, std::string const & buffer_path) { DocFileName::set(filename, buffer_path); if (filename.empty()) return; inzip_name_ = to_utf8(makeRelPath(from_utf8(absFilename()), from_utf8(buffer_path))); if (FileName(inzip_name_).isAbsolute()) inzip_name_ = absDirName + '/' + inzip_name_; // replace .. by upDirName if (prefixIs(inzip_name_, ".")) inzip_name_ = subst(inzip_name_, "..", upDirName); // to avoid name conflict between $docu_path/file and $temp_path/file // embedded files are in a subdirectory of $temp_path. inzip_name_ = embDirName + '/' + inzip_name_; } string EmbeddedFile::embeddedFile() const { BOOST_ASSERT(enabled()); return temp_path_ + inzip_name_; } FileName EmbeddedFile::availableFile() const { if (enabled() && embedded()) return FileName(embeddedFile()); else return *this; } string EmbeddedFile::latexFilename(std::string const & buffer_path) const { return (enabled() && embedded()) ? inzip_name_ : relFilename(buffer_path); } void EmbeddedFile::addInset(Inset const * inset) { if (inset != NULL) inset_list_.push_back(inset); } void EmbeddedFile::setEmbed(bool embed) { embedded_ = embed; } void EmbeddedFile::enable(bool flag, Buffer const * buf) { if (enabled() == flag) return; if (flag) { temp_path_ = buf->temppath(); if (!suffixIs(temp_path_, '/')) temp_path_ += '/'; if (embedded()) updateFromExternalFile(); } else { extract(); temp_path_ = ""; } } bool EmbeddedFile::extract() const { BOOST_ASSERT(enabled()); string ext_file = absFilename(); string emb_file = embeddedFile(); FileName emb(emb_file); FileName ext(ext_file); if (!emb.exists()) { if (ext.exists()) return true; else throw ExceptionMessage(ErrorException, _("Failed to extract file"), bformat(_("Cannot extract file '%1$s'.\n" "Source file %2$s does not exist"), from_utf8(outputFilename()), from_utf8(emb_file))); } // if external file already exists ... if (ext.exists()) { // no need to copy if the files are the same if (checksum() == FileName(emb_file).checksum()) 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.createPath()) { throw ExceptionMessage(ErrorException, _("Copy file failure"), bformat(_("Cannot create file path '%1$s'.\n" "Please check whether the path is writeable."), from_utf8(path.absFilename()))); return false; } if (emb.copyTo(ext)) { LYXERR(Debug::FILES, "Extract file " << emb_file << " to " << ext_file << endl); return true; } throw ExceptionMessage(ErrorException, _("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))); return false; } bool EmbeddedFile::updateFromExternalFile() const { BOOST_ASSERT(enabled()); string ext_file = absFilename(); string emb_file = embeddedFile(); FileName emb(emb_file); FileName ext(ext_file); if (!ext.exists()) { // no need to update if (emb.exists()) return true; // no external and internal file throw ExceptionMessage(ErrorException, _("Failed to embed file"), bformat(_("Failed to embed file %1$s.\n" "Please check whether this file exists and is readable."), from_utf8(ext_file))); } // if embedded file already exists ... if (emb.exists()) { // no need to copy if the files are the same if (checksum() == FileName(emb_file).checksum()) 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()) path.createPath(); if (ext.copyTo(emb)) return true; throw ExceptionMessage(ErrorException, _("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 { vector::const_iterator it = inset_list_.begin(); vector::const_iterator it_end = inset_list_.end(); for (; it != it_end; ++it) const_cast(*it)->updateEmbeddedFile(*buf, *this); } bool EmbeddedFile::isReadableFile() const { return availableFile().isReadableFile(); } unsigned long EmbeddedFile::checksum() const { return availableFile().checksum(); } bool operator==(EmbeddedFile const & lhs, EmbeddedFile const & rhs) { return lhs.absFilename() == rhs.absFilename() && lhs.saveAbsPath() == rhs.saveAbsPath() && lhs.embedded() == rhs.embedded(); } bool operator!=(EmbeddedFile const & lhs, EmbeddedFile const & rhs) { return !(lhs == rhs); } void EmbeddedFiles::enable(bool flag, Buffer & buffer) { if (buffer.embedded() == flag) return; // update embedded file list update(buffer); int count_embedded = 0; int count_external = 0; std::vector::iterator it = begin(); std::vector::iterator it_end = end(); // an exception may be thrown for (; it != it_end; ++it) { it->enable(flag, &buffer); if (it->embedded()) count_embedded ++; else count_external ++; } // if operation is successful (no exception is thrown) buffer.markDirty(); buffer.params().embedded = flag; // if the operation is successful, update insets for (it = begin(); it != it_end; ++it) it->updateInsets(&buffer); // show result if (flag) { docstring const msg = bformat(_("%1$d external files are ignored.\n" "%2$d embeddable files are embedded.\n"), count_external, count_embedded); Alert::information(_("Packing all files"), msg); } else { docstring const msg = bformat(_("%1$d external files are ignored.\n" "%2$d embedded files are extracted.\n"), count_external, count_embedded); Alert::information(_("Unpacking all files"), msg); } } void EmbeddedFiles::registerFile(EmbeddedFile const & file, Inset const * inset, Buffer const & buffer) { BOOST_ASSERT(!buffer.embedded() || file.availableFile().exists()); BOOST_ASSERT(!buffer.embedded() || file.enabled()); // try to find this file from the list std::vector::iterator it = begin(); std::vector::iterator it_end = end(); for (; it != it_end; ++it) if (it->absFilename() == file.absFilename()) { if (it->embedded() != file.embedded()) { Alert::error(_("Wrong embedding status."), bformat(_("File %1$s is included in more than one insets, " "but with different embedding status. Assuming embedding status."), from_utf8(it->outputFilename()))); it->setEmbed(true); // update the inset with this embedding status. const_cast(inset)->updateEmbeddedFile(buffer, *it); } it->addInset(inset); return; } // push_back(file); back().addInset(inset); } void EmbeddedFiles::update(Buffer const & buffer) { clear(); for (InsetIterator it = inset_iterator_begin(buffer.inset()); it; ++it) it->registerEmbeddedFiles(buffer, *this); } bool EmbeddedFiles::writeFile(DocFileName const & filename, Buffer const & buffer) { // file in the temporary path has the content string const content = FileName(addName(buffer.temppath(), "content.lyx")).toFilesystemEncoding(); vector > filenames; // add content.lyx to filenames filenames.push_back(make_pair(content, "content.lyx")); // prepare list of embedded file update(buffer); std::vector::iterator it = begin(); std::vector::iterator it_end = end(); for (; it != it_end; ++it) { if (it->embedded()) { string file = it->embeddedFile(); if (!FileName(file).exists()) throw ExceptionMessage(ErrorException, _("Failed to write file"), bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"), it->displayName())); filenames.push_back(make_pair(file, it->inzipName())); LYXERR(Debug::FILES, "Writing file " << it->outputFilename() << " as " << it->inzipName() << endl); } } // 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.toFilesystemEncoding(), filenames); // copy file back 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()); } return true; } }