lyx_mirror/src/EmbeddedFiles.cpp
Bo Peng 6dc7cbe1e7 Continue working on the embedding feature. An additional parameter updateFile is passed to
EmbeddedFile::enable() and EmbeddedFiles::enable() to differentiate different usages.
A member variable bibfiles_ is added to InsetBibtex because it is not efficient to obtain
EmbeddedFileList each time from params()['bibfiles'] and params()['embed'].


git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@23576 a592a061-630c-0410-9148-cb99ea01b6c8
2008-03-09 06:05:22 +00:00

504 lines
14 KiB
C++

// -*- C++ -*-
/**
* \file EmbeddedFileList.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 <config.h>
#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 <sstream>
#include <fstream>
#include <utility>
using namespace std;
using namespace lyx::support;
namespace lyx {
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_ = calcInzipName(buffer_path);
}
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;
}
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, bool updateFile)
{
// 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 (inzip_name_ != calcInzipName(buf->filePath()))
syncInzipFile(buf->filePath());
if (updateFile)
updateFromExternalFile();
}
} else {
if (embedded() && updateFile)
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() 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(*this);
}
bool EmbeddedFile::isReadableFile() const
{
return availableFile().isReadableFile();
}
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
*/
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;
}
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)
{
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 EmbeddedFileList::enable(bool flag, Buffer & buffer, bool updateFile)
{
// update embedded file list
update(buffer);
int count_embedded = 0;
int count_external = 0;
std::vector<EmbeddedFile>::iterator it = begin();
std::vector<EmbeddedFile>::iterator it_end = end();
// an exception may be thrown
for (; it != it_end; ++it) {
it->enable(flag, &buffer, updateFile);
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();
if (!updateFile)
return;
// 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 EmbeddedFileList::registerFile(EmbeddedFile const & file,
Inset const * inset, Buffer const & buffer)
{
BOOST_ASSERT(!buffer.embedded() || file.enabled());
// try to find this file from the list
std::vector<EmbeddedFile>::iterator it = begin();
std::vector<EmbeddedFile>::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*>(inset)->updateEmbeddedFile(*it);
}
it->addInset(inset);
return;
}
//
push_back(file);
back().addInset(inset);
}
void EmbeddedFileList::update(Buffer const & buffer)
{
clear();
for (InsetIterator it = inset_iterator_begin(buffer.inset()); it; ++it)
it->registerEmbeddedFiles(*this);
}
bool EmbeddedFileList::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<pair<string, string> > filenames;
// add content.lyx to filenames
filenames.push_back(make_pair(content, "content.lyx"));
// prepare list of embedded file
update(buffer);
std::vector<EmbeddedFile>::iterator it = begin();
std::vector<EmbeddedFile>::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;
}
} // namespace lyx