#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 "support/lyxlib.h"
#include "support/lstrings.h"
+#include "LyX.h"
+#include "Session.h"
+
#include <sstream>
#include <fstream>
#include <utility>
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),
+ valid_(true), inset_list_()
+{
+ if (inset != NULL)
+ inset_list_.push_back(inset);
+}
string EmbeddedFile::embeddedFile(Buffer const * buf) const
}
-int const EmbeddedFile::parID() const
+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 par_it_ == ParConstIterator() ? 0 : par_it_->id();
+ return inset_list_[idx];
}
-void EmbeddedFile::setParIter(ParConstIterator const & pit)
+void EmbeddedFile::saveBookmark(Buffer const * buf, int idx) const
{
- par_it_ = pit;
+ 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
{
- 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();
+ if (embedded())
+ return embeddedFile(buf);
+ else
+ return absFilename();
+}
+
+
+void EmbeddedFile::invalidate()
+{
+ // Clear inset_list_ because they will be registered again.
+ inset_list_.clear();
+ valid_ = false;
}
bool EmbeddedFile::extract(Buffer const * buf) const
{
+
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(
+
+ if (!fs::exists(emb_file))
+ return false;
+
+ // if external file already exists ...
+ if (fs::exists(ext_file)) {
+ // 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"));
- copyFile = ret == 0;
+ if (ret != 0)
+ // if the user does not want to overwrite, we still consider it
+ // a successful operation.
+ return true;
}
- // copy file in the previous case, and a new case
- if (copyFile || (!fs::exists(ext_file) && fs::exists(emb_file))) {
- try {
- fs::copy_file(emb_file, ext_file, false);
+ // copy file
+ try {
+ // need to make directory?
+ string path = onlyPath(ext_file);
+ if (!fs::is_directory(path))
+ makedir(const_cast<char*>(path.c_str()), 0755);
+ fs::copy_file(emb_file, ext_file, false);
+ 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;
+ }
+ return false;
+}
+
+
+bool EmbeddedFile::updateFromExternalFile(Buffer const * buf) const
+{
+ string ext_file = absFilename();
+ string emb_file = embeddedFile(buf);
+
+ if (!fs::exists(ext_file))
+ return false;
+
+ // if embedded file already exists ...
+ if (fs::exists(emb_file)) {
+ // no need to copy if the files are the same
+ if (sum(*this) == sum(FileName(emb_file)))
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;
- }
+ // other wise, ask if overwrite
+ int const ret = Alert::prompt(
+ _("Update embedded file?"),
+ bformat(_("Embeddedl 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
+ try {
+ // need to make directory?
+ string path = onlyPath(emb_file);
+ if (!fs::is_directory(path))
+ makedir(const_cast<char*>(path.c_str()), 0755);
+ fs::copy_file(ext_file, emb_file, false);
+ 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(ext_file), from_utf8(emb_file)));
+ LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
}
+ return false;
}
-
+
+void EmbeddedFile::updateInsets(Buffer const * buf) const
+{
+ 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);
+}
+
+
bool EmbeddedFiles::enabled() const
{
return buffer_->params().embedded;
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;
- }
+ // 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->addInset(inset);
it->validate();
- return;
+ return *it;
}
+ // try to be more careful
file_list_.push_back(EmbeddedFile(abs_filename,
- getInzipName(abs_filename), status, pit));
+ getInzipName(abs_filename, inzipName), embed, inset));
+ return file_list_.back();
}
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)
+ // we do not update items that are manually inserted
+ if (it->refCount() > 0)
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;
+ 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();
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);
}
-bool EmbeddedFiles::extractAll() const
+EmbeddedFiles::EmbeddedFileList::const_iterator EmbeddedFiles::find(std::string filename) const
+{
+ 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();
+}
+
+
+bool EmbeddedFiles::extract() const
+{
+ EmbeddedFileList::const_iterator it = file_list_.begin();
+ EmbeddedFileList::const_iterator it_end = file_list_.end();
+ for (; it != it_end; ++it)
+ if (it->valid() && it->embedded())
+ if(!it->extract(buffer_))
+ return false;
+ return true;
+}
+
+
+bool EmbeddedFiles::updateFromExternalFile() const
{
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_);
+ if (it->valid() && it->embedded())
+ if (!it->updateFromExternalFile(buffer_))
+ return false;
return true;
}
-string const EmbeddedFiles::getInzipName(string const & abs_filename)
+string const EmbeddedFiles::getInzipName(string const & abs_filename, string const & name)
{
// 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())));
+ 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
- if (absolutePath(inzip_name) || prefixIs(inzip_name, ".."))
- inzip_name = onlyFilename(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;
- size_t i = 0;
if (i > 0)
inzip_name = convert<string>(i) + "_" + tmp;
it = file_list_.begin();
}
-istream & operator>> (istream & is, EmbeddedFiles & files)
+void EmbeddedFiles::updateInsets() const
{
- 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;
- }
- return is;
+ EmbeddedFiles::EmbeddedFileList::const_iterator it = begin();
+ EmbeddedFiles::EmbeddedFileList::const_iterator it_end = end();
+ for (; it != it_end; ++it)
+ if (it->valid() && it->refCount() > 0)
+ it->updateInsets(buffer_);
}
-ostream & operator<< (ostream & os, EmbeddedFiles const & files)
-{
- // 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;
-}
-
}