X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FEmbeddedFiles.cpp;h=4109dcd74662eba9fb255722449602ede3d5ae5b;hb=6c977615633e5e132494b9a7fa778588210f9c95;hp=e416ef907983c1412821320a364631c4809a9e90;hpb=b5153b3d2689abffebb2f4c97b3fb781dadf0c3c;p=lyx.git diff --git a/src/EmbeddedFiles.cpp b/src/EmbeddedFiles.cpp index e416ef9079..4109dcd746 100644 --- a/src/EmbeddedFiles.cpp +++ b/src/EmbeddedFiles.cpp @@ -34,6 +34,8 @@ #include "support/ExceptionMessage.h" #include "support/FileZipListDir.h" +#include + #include #include #include @@ -43,40 +45,10 @@ 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_("") + : DocFileName("", false), embedded_(false), inset_list_() { set(file, buffer_path); } @@ -88,19 +60,20 @@ void EmbeddedFile::set(std::string const & filename, std::string const & buffer_ 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_; + if (!buffer_path.empty()) + inzip_name_ = calcInzipName(buffer_path); +} - // 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_; +void EmbeddedFile::setInzipName(std::string const & name) +{ + if (name.empty() || name == inzip_name_) + return; + + // an enabled EmbeededFile should have this problem handled + BOOST_ASSERT(!enabled()); + // file will be synced when it is enabled + inzip_name_ = name; } @@ -139,19 +112,30 @@ void EmbeddedFile::setEmbed(bool embed) } -void EmbeddedFile::enable(bool flag, Buffer const * buf) +void EmbeddedFile::enable(bool flag, Buffer const * buf, bool updateFile) { - if (enabled() == flag) - return; - + // This function will be called when + // 1. through EmbeddedFiles::enable() when a file is read. Files + // should be in place so no updateFromExternalFile or extract() + // should be called. (updateFile should be false in this case). + // 2. through menu item enable/disable. updateFile should be true. + // 3. A single embedded file is added or modified. updateFile + // can be true or false. + LYXERR(Debug::FILES, (flag ? "Enable" : "Disable") + << " " << absFilename() + << (updateFile ? " (update file)." : " (no update).")); + if (flag) { temp_path_ = buf->temppath(); if (!suffixIs(temp_path_, '/')) temp_path_ += '/'; - if (embedded()) + if (embedded() && updateFile) updateFromExternalFile(); } else { - extract(); + // when a new embeddeed file is created, it is not enabled, and + // there is no need to extract. + if (enabled() && embedded() && updateFile) + extract(); temp_path_ = ""; } } @@ -170,11 +154,10 @@ bool EmbeddedFile::extract() const 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))); + 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 ... @@ -185,7 +168,7 @@ bool EmbeddedFile::extract() const // otherwise, ask if overwrite int ret = Alert::prompt( _("Overwrite external file?"), - bformat(_("External file %1$s already exists, do you want to overwrite it"), + 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 @@ -271,12 +254,70 @@ bool EmbeddedFile::updateFromExternalFile() const } -void EmbeddedFile::updateInsets(Buffer const * buf) const +EmbeddedFile EmbeddedFile::copyTo(Buffer const * buf) +{ + EmbeddedFile file = EmbeddedFile(absFilename(), buf->filePath()); + file.setEmbed(embedded()); + file.enable(buf->embedded(), buf, false); + + // use external file. + if (!embedded()) + return file; + + LYXERR(Debug::FILES, "Copy " << availableFile() + << " to " << file.availableFile()); + + FileName from_file = availableFile(); + FileName to_file = file.availableFile(); + + if (!from_file.exists()) { + // no from file + throw ExceptionMessage(ErrorException, + _("Failed to copy embedded file"), + bformat(_("Failed to embed file %1$s.\n" + "Please check whether the source file is available"), + from_utf8(absFilename()))); + file.setEmbed(false); + return file; + } + + // if destination file already exists ... + if (to_file.exists()) { + // no need to copy if the files are the same + if (checksum() == to_file.checksum()) + return file; + // 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(to_file.absFilename())), 1, 1, _("&Overwrite"), _("&Cancel")); + if (ret != 0) + // if the user does not want to overwrite, we still consider it + // a successful operation. + return file; + } + // copy file + // need to make directory? + FileName path = to_file.onlyPath(); + if (!path.isDirectory()) + path.createPath(); + if (from_file.copyTo(to_file)) + return file; + 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(from_file.absFilename()), from_utf8(to_file.absFilename()))); + return file; +} + + +void EmbeddedFile::updateInsets() 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); + const_cast(*it)->updateEmbeddedFile(*this); } @@ -291,6 +332,107 @@ unsigned long EmbeddedFile::checksum() const return availableFile().checksum(); } +/** +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 + +FIXME: +embDirName is set to . so that embedded layout and class files can be +used directly. However, putting all embedded files directly under +the temp directory may lead to file conflicts. For example, if a user +embeds a file blah.log in blah.lyx, it will be replaced when +'latex blah.tex' is called. +*/ +const std::string embDirName = "."; +const std::string upDirName = "LyX.Embed.Dir.Up"; +const std::string absDirName = "LyX.Embed.Dir.Abs"; +const std::string driveName = "LyX.Embed.Drive"; +const std::string spaceName = "LyX.Embed.Space"; + +std::string EmbeddedFile::calcInzipName(std::string const & buffer_path) +{ + string inzipName = to_utf8(makeRelPath(from_utf8(absFilename()), + from_utf8(buffer_path))); + + if (FileName(inzipName).isAbsolute()) + inzipName = absDirName + '/' + inzipName; + + // replace .. by upDirName + if (prefixIs(inzipName, ".")) + inzipName = subst(inzipName, "..", upDirName); + // replace special characters by their value + inzipName = subst(inzipName, ":", driveName); + inzipName = subst(inzipName, " ", spaceName); + + // to avoid name conflict between $docu_path/file and $temp_path/file + // embedded files are in a subdirectory of $temp_path. + inzipName = embDirName + '/' + inzipName; + return inzipName; +} + + +void EmbeddedFile::syncInzipFile(std::string const & buffer_path) +{ + BOOST_ASSERT(enabled()); + string old_emb_file = temp_path_ + '/' + inzip_name_; + FileName old_emb(old_emb_file); + + if (!old_emb.exists()) + throw ExceptionMessage(ErrorException, _("Failed to open file"), + bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"), + old_emb.displayName())); + + string new_inzip_name = calcInzipName(buffer_path); + if (new_inzip_name == inzip_name_) + return; + + LYXERR(Debug::FILES, " OLD ZIP " << old_emb_file << + " NEW ZIP " << calcInzipName(buffer_path)); + + string new_emb_file = temp_path_ + '/' + new_inzip_name; + FileName new_emb(new_emb_file); + + // need to make directory? + FileName path = new_emb.onlyPath(); + if (!path.createPath()) { + throw ExceptionMessage(ErrorException, _("Sync file failure"), + bformat(_("Cannot create file path '%1$s'.\n" + "Please check whether the path is writeable."), + from_utf8(path.absFilename()))); + return; + } + + if (old_emb.copyTo(new_emb)) { + LYXERR(Debug::FILES, "Sync inzip file from " << inzip_name_ + << " to " << new_inzip_name); + inzip_name_ = new_inzip_name; + return; + } + throw ExceptionMessage(ErrorException, _("Sync file failure"), + bformat(_("Cannot copy file %1$s to %2$s.\n" + "Please check whether the directory exists and is writeable."), + from_utf8(old_emb_file), from_utf8(new_emb_file))); +} + bool operator==(EmbeddedFile const & lhs, EmbeddedFile const & rhs) { @@ -306,34 +448,33 @@ bool operator!=(EmbeddedFile const & lhs, EmbeddedFile const & rhs) } -void EmbeddedFileList::enable(bool flag, Buffer & buffer) +void EmbeddedFileList::enable(bool flag, Buffer & buffer, bool updateFile) { - 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(); + iterator it = begin(); + iterator it_end = end(); // an exception may be thrown for (; it != it_end; ++it) { - it->enable(flag, &buffer); + it->enable(flag, &buffer, updateFile); if (it->embedded()) - count_embedded ++; + ++count_embedded; else - count_external ++; + ++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); - + it->updateInsets(); + + if (!updateFile || (count_external == 0 && count_embedded == 0)) + return; + // show result if (flag) { docstring const msg = bformat(_("%1$d external files are ignored.\n" @@ -350,7 +491,6 @@ void EmbeddedFileList::enable(bool flag, Buffer & buffer) void EmbeddedFileList::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 @@ -365,23 +505,75 @@ void EmbeddedFileList::registerFile(EmbeddedFile const & file, from_utf8(it->outputFilename()))); it->setEmbed(true); // update the inset with this embedding status. - const_cast(inset)->updateEmbeddedFile(buffer, *it); + const_cast(inset)->updateEmbeddedFile(*it); } it->addInset(inset); return; } // + file.clearInsets(); push_back(file); back().addInset(inset); } +void EmbeddedFileList::validate(Buffer const & buffer) +{ + clear(); + + for (InsetIterator it = inset_iterator_begin(buffer.inset()); it; ++it) + it->registerEmbeddedFiles(*this); + + iterator it = begin(); + iterator it_end = end(); + for (; it != it_end; ++it) { + if (buffer.embedded() && it->embedded()) + // An exception will be raised if inzip file does not exist + it->syncInzipFile(buffer.filePath()); + else + // inzipName may be OS dependent + it->setInzipName(it->calcInzipName(buffer.filePath())); + } + for (it = begin(); it != it_end; ++it) + it->updateInsets(); + + if (!buffer.embedded()) + return; + + // check if extra embedded files exist + vector extra = buffer.params().extraEmbeddedFiles(); + vector::iterator e_it = extra.begin(); + vector::iterator e_end = extra.end(); + for (; e_it != e_end; ++e_it) { + EmbeddedFile file = EmbeddedFile(*e_it, buffer.filePath()); + // do not update from external file + file.enable(true, &buffer, false); + // but we do need to check file existence. + if (!FileName(file.embeddedFile()).exists()) + throw ExceptionMessage(ErrorException, _("Failed to open file"), + bformat(_("Embedded file %1$s does not exist. Did you tamper lyx temporary directory?"), + file.displayName())); + } +} + + void EmbeddedFileList::update(Buffer const & buffer) { clear(); for (InsetIterator it = inset_iterator_begin(buffer.inset()); it; ++it) - it->registerEmbeddedFiles(buffer, *this); + it->registerEmbeddedFiles(*this); + + // add extra embedded files + vector extra = buffer.params().extraEmbeddedFiles(); + vector::iterator it = extra.begin(); + vector::iterator it_end = extra.end(); + for (; it != it_end; ++it) { + EmbeddedFile file = EmbeddedFile(*it, buffer.filePath()); + file.setEmbed(true); + file.enable(buffer.embedded(), &buffer, false); + insert(end(), file); + } } @@ -396,8 +588,9 @@ bool EmbeddedFileList::writeFile(DocFileName const & filename, Buffer const & bu 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(); + // + iterator it = begin(); + iterator it_end = end(); for (; it != it_end; ++it) { if (it->embedded()) { string file = it->embeddedFile(); @@ -420,12 +613,11 @@ bool EmbeddedFileList::writeFile(DocFileName const & filename, Buffer const & bu // copy file back if (!zipfile.copyTo(filename)) { Alert::error(_("Save failure"), - bformat(_("Cannot create file %1$s.\n" + 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; } -} +} // namespace lyx