X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FEmbeddedFiles.cpp;h=59e93710155d87351e7ae9a1c4dc1aefad3bde63;hb=0362c6aae73c293d1c20277c12d362acfe0b2ef6;hp=180b88ed921db8766f6aa646ca0d7b6ff8a26272;hpb=22ce34e5d5d39a0fd64b9a821ef2c3f0be09a4a8;p=lyx.git diff --git a/src/EmbeddedFiles.cpp b/src/EmbeddedFiles.cpp index 180b88ed92..59e9371015 100644 --- a/src/EmbeddedFiles.cpp +++ b/src/EmbeddedFiles.cpp @@ -1,6 +1,6 @@ // -*- C++ -*- /** - * \file EmbeddedFiles.cpp + * \file EmbeddedFileList.cpp * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * @@ -13,390 +13,483 @@ #include #include "EmbeddedFiles.h" + #include "Buffer.h" #include "BufferParams.h" -#include "Paragraph.h" -#include "ParIterator.h" -#include "debug.h" -#include "gettext.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 - +#include "support/debug.h" #include "support/filetools.h" -#include "support/fs_extras.h" +#include "support/gettext.h" #include "support/convert.h" -#include "support/lyxlib.h" #include "support/lstrings.h" +#include "support/ExceptionMessage.h" +#include "support/FileZipListDir.h" #include #include #include -using std::ofstream; -using std::endl; -using std::vector; -using std::string; -using std::pair; -using std::make_pair; -using std::istream; -using std::ostream; -using std::getline; -using std::istringstream; +using namespace std; +using namespace lyx::support; 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::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) -{} - - -string EmbeddedFile::embeddedFile(Buffer const * buf) const +EmbeddedFile::EmbeddedFile(string const & file, std::string const & buffer_path) + : DocFileName("", false), inzip_name_(""), embedded_(false), inset_list_(), + temp_path_("") { - return addName(buf->temppath(), inzip_name_); + set(file, buffer_path); } -int const EmbeddedFile::parID() const +void EmbeddedFile::set(std::string const & filename, std::string const & buffer_path) { - // some embedded file do not have a valid par iterator - return par_it_ == ParConstIterator() ? 0 : par_it_->id(); + DocFileName::set(filename, buffer_path); + if (filename.empty()) + return; + + inzip_name_ = calcInzipName(buffer_path); } -void EmbeddedFile::setParIter(ParConstIterator const & pit) +void EmbeddedFile::setInzipName(std::string const & name) { - par_it_ = pit; + 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; } -string EmbeddedFile::availableFile(Buffer const * buf) const +string EmbeddedFile::embeddedFile() const { - string ext_file = absFilename(); - string emb_file = embeddedFile(buf); - if (status_ == AUTO) { - // use external file first - if (fs::exists(ext_file)) - return ext_file; - else if (fs::exists(emb_file)) - return emb_file; - else - return string(); - } else if (status_ == EMBEDDED) { - // use embedded file first - if (fs::exists(emb_file)) - return emb_file; - else if (fs::exists(ext_file)) - return ext_file; - else - return string(); - } else - return string(); + 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; } -bool EmbeddedFile::extract(Buffer const * buf) const +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()) { + if (inzip_name_ != calcInzipName(buf->filePath())) + syncInzipFile(buf->filePath()); + updateFromExternalFile(); + } else + extract(); + } else { + extract(); + temp_path_ = ""; + } +} + + +bool EmbeddedFile::extract() const +{ + BOOST_ASSERT(enabled()); + string ext_file = absFilename(); - string emb_file = embeddedFile(buf); - bool copyFile = false; - // both files exist, are different, and in EMBEDDED status - if (fs::exists(ext_file) && fs::exists(emb_file) && status_ == EMBEDDED - && sum(*this) != sum(FileName(emb_file))) { - int const ret = Alert::prompt( + 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")); - copyFile = ret == 0; - } - // copy file in the previous case, and a new case - if (copyFile || (!fs::exists(ext_file) && fs::exists(emb_file))) { - try { - // need to make directory? - string path = onlyPath(ext_file); - if (!fs::is_directory(path)) - makedir(const_cast(path.c_str()), 0755); - fs::copy_file(emb_file, ext_file, false); + if (ret != 0) + // if the user does not want to overwrite, we still consider it + // a successful operation. return true; - } catch (fs::filesystem_error const & fe) { - 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() << endl; - } } + // 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 EmbeddedFiles::enabled() const + +bool EmbeddedFile::updateFromExternalFile() const { - return buffer_->params().embedded; -} + BOOST_ASSERT(enabled()); + string ext_file = absFilename(); + string emb_file = embeddedFile(); -void EmbeddedFiles::enable(bool flag) -{ - if (enabled() != flag) { - // if disable embedding, first extract all embedded files - if (flag || (!flag && extractAll())) { - // file will be changed - buffer_->markDirty(); - buffer_->params().embedded = flag; - } + 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 EmbeddedFiles::registerFile(string const & filename, - EmbeddedFile::STATUS status, ParConstIterator const & pit) +void EmbeddedFile::updateInsets(Buffer const * buf) const { - 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(); + vector::const_iterator it = inset_list_.begin(); + vector::const_iterator it_end = inset_list_.end(); for (; it != it_end; ++it) - if (it->absFilename() == abs_filename) - break; - // find this filename - if (it != file_list_.end()) { - it->setParIter(pit); - it->setStatus(status); - it->validate(); - return; - } - file_list_.push_back(EmbeddedFile(abs_filename, - getInzipName(abs_filename), status, pit)); + const_cast(*it)->updateEmbeddedFile(*buf, *this); } -void EmbeddedFiles::update() +bool EmbeddedFile::isReadableFile() const { - // 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) - // Only AUTO files will be updated. If the status of a file is EMBEDDED, - // it will be embedded even if it is not referred by a document. - if (it->status() == EmbeddedFile::AUTO) - 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; + return availableFile().isReadableFile(); } -bool EmbeddedFiles::write(DocFileName const & filename) +unsigned long EmbeddedFile::checksum() const { - // file in the temporary path has the content - string const content = FileName(addName(buffer_->temppath(), - onlyFilename(filename.toFilesystemEncoding()))).toFilesystemEncoding(); + return availableFile().checksum(); +} - // get a file list and write a manifest file - vector > filenames; - string const manifest = FileName( - addName(buffer_->temppath(), "manifest.txt")).toFilesystemEncoding(); +/** +Under the lyx temp directory, content.lyx and its embedded files are usually +saved as - // write a manifest file - ofstream os(manifest.c_str()); - os << *this; - os.close(); - // 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()) { - 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())); - } +$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"; +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); + + LYXERR(Debug::FILES, " OLD ZIP " << old_emb_file << + " NEW ZIP " << calcInzipName(buffer_path)); + + //BOOST_ASSERT(old_emb.exists()); + + string new_inzip_name = 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; } - // 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.toFilesystemEncoding(), filenames); - // copy file back - try { - fs::copy_file(zipfile.toFilesystemEncoding(), filename.toFilesystemEncoding(), false); - } catch (fs::filesystem_error const & fe) { - 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; + 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; } - return true; + 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 EmbeddedFiles::extractAll() const +bool operator==(EmbeddedFile const & lhs, EmbeddedFile const & rhs) { - EmbeddedFileList::const_iterator it = file_list_.begin(); - EmbeddedFileList::const_iterator it_end = file_list_.end(); - // FIXME: the logic here is hard to decide, we should allow cancel for - // 'do not overwrite' this file, and cancel for 'cancel extract all files'. - // I am not sure how to do this now. - for (; it != it_end; ++it) - if (it->valid() && it->status() != EmbeddedFile::EXTERNAL) - it->extract(buffer_); - return true; + return lhs.absFilename() == rhs.absFilename() + && lhs.saveAbsPath() == rhs.saveAbsPath() + && lhs.embedded() == rhs.embedded(); } -string const EmbeddedFiles::getInzipName(string const & abs_filename) +bool operator!=(EmbeddedFile const & lhs, EmbeddedFile const & rhs) { - // 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 - // 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; - while (!unique_name) { - unique_name = true; - size_t i = 0; - if (i > 0) - inzip_name = convert(i) + "_" + tmp; - it = file_list_.begin(); - for (; it != it_end; ++it) - if (it->inzipName() == inzip_name) { - unique_name = false; - ++i; - break; - } - } - return inzip_name; + return !(lhs == rhs); } -istream & operator>> (istream & is, EmbeddedFiles & files) +void EmbeddedFileList::enable(bool flag, Buffer & buffer) { - 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; + 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 ++; } - - getline(is, tmp); - if (tmp != "") { - lyxerr << "Invalid manifest file, lacking " << endl; - return is; + // 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); } - // manifest file may be messed up, be carefully - while (is.good()) { - getline(is, tmp); - if (tmp != "") - 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 != "") { - lyxerr << "Invalid manifest file, lacking " << endl; - break; +} + + +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 + 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); +} - files.registerFile(fname, static_cast(status)); - }; - // the last line must be - if (tmp != "") { - lyxerr << "Invalid manifest file, lacking " << endl; - return is; - } - return is; + +void EmbeddedFileList::update(Buffer const & buffer) +{ + clear(); + + for (InsetIterator it = inset_iterator_begin(buffer.inset()); it; ++it) + it->registerEmbeddedFiles(buffer, *this); } -ostream & operator<< (ostream & os, EmbeddedFiles const & files) +bool EmbeddedFileList::writeFile(DocFileName const & filename, Buffer const & buffer) { - // store a version so that operator >> can read later versions - // using version information. - os << "# lyx manifest version 1\n"; - os << "\n"; - EmbeddedFiles::EmbeddedFileList::const_iterator it = files.begin(); - EmbeddedFiles::EmbeddedFileList::const_iterator it_end = files.end(); + // 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->valid()) - continue; - // use differnt lines to make reading easier. - os << "\n" - // save the relative path - << to_utf8(makeRelPath(from_utf8(it->absFilename()), - from_utf8(files.buffer_->filePath()))) << '\n' - << it->inzipName() << '\n' - << it->status() << '\n' - << "\n"; + 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); + } } - os << "\n"; - return os; + // 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; } }