> I will remove that small feature though, because embedding status will > be lost if a file without any embedded file is reopened. This can > upset the user if s/he insert a figure later and expect it to be > embedded.
Updated patch attached. This time, embedding will always result in a zipped file. OK to apply? Bo
Index: src/insets/InsetGraphics.h =================================================================== --- src/insets/InsetGraphics.h (revision 19920) +++ src/insets/InsetGraphics.h (working copy) @@ -78,6 +78,8 @@ void editGraphics(InsetGraphicsParams const &, Buffer const &) const; /// bool getStatus(Cursor &, FuncRequest const &, FuncStatus &) const; + /// all graphics can be embedded + void registerEmbeddedFiles(Buffer const &, EmbeddedFiles &, ParConstIterator const &) const; protected: InsetGraphics(InsetGraphics const &); /// Index: src/insets/InsetExternal.cpp =================================================================== --- src/insets/InsetExternal.cpp (revision 19920) +++ src/insets/InsetExternal.cpp (working copy) @@ -481,6 +481,13 @@ } +void InsetExternal::registerEmbeddedFiles(Buffer const &, + EmbeddedFiles & files, ParConstIterator const & pit) const +{ + files.registerFile(params_.filename.absFilename(), EmbeddedFile::AUTO, pit); +} + + void InsetExternal::edit(Cursor & cur, bool) { InsetExternalMailer(*this).showDialog(&cur.bv()); Index: src/insets/InsetGraphics.cpp =================================================================== --- src/insets/InsetGraphics.cpp (revision 19920) +++ src/insets/InsetGraphics.cpp (working copy) @@ -71,6 +71,7 @@ #include "Mover.h" #include "OutputParams.h" #include "sgml.h" +#include "EmbeddedFiles.h" #include "frontends/alert.h" @@ -230,6 +231,14 @@ } +void InsetGraphics::registerEmbeddedFiles(Buffer const &, + EmbeddedFiles & files, ParConstIterator const & pit) const +{ + files.registerFile(params().filename.absFilename(), + EmbeddedFile::AUTO, pit); +} + + void InsetGraphics::edit(Cursor & cur, bool) { InsetGraphicsMailer(*this).showDialog(&cur.bv()); Index: src/insets/InsetInclude.cpp =================================================================== --- src/insets/InsetInclude.cpp (revision 19920) +++ src/insets/InsetInclude.cpp (working copy) @@ -958,6 +958,16 @@ } +void InsetInclude::registerEmbeddedFiles(Buffer const & buffer, + EmbeddedFiles & files, ParConstIterator const & pit) const +{ + // include and input are temprarily not considered. + if (isVerbatim(params_) || isListings(params_)) + files.registerFile(includedFilename(buffer, params_).absFilename(), + EmbeddedFile::AUTO, pit); +} + + string const InsetIncludeMailer::name_("include"); InsetIncludeMailer::InsetIncludeMailer(InsetInclude & inset) Index: src/insets/Inset.h =================================================================== --- src/insets/Inset.h (revision 19920) +++ src/insets/Inset.h (working copy) @@ -49,6 +49,7 @@ class ParIterator; class Text; class TocList; +class EmbeddedFiles; namespace graphics { class PreviewLoader; } @@ -438,6 +439,9 @@ /// Add an entry to the TocList /// pit is the ParConstIterator of the paragraph containing the inset virtual void addToToc(TocList &, Buffer const &, ParConstIterator const &) const {} + /// report files that can be embedded with the lyx file + virtual void registerEmbeddedFiles(Buffer const &, EmbeddedFiles &, + ParConstIterator const &) const {}; /// Fill keys with BibTeX information virtual void fillWithBibKeys(Buffer const &, BiblioInfo &, InsetIterator const &) const { return; } Index: src/insets/InsetInclude.h =================================================================== --- src/insets/InsetInclude.h (revision 19920) +++ src/insets/InsetInclude.h (working copy) @@ -18,6 +18,7 @@ #include "RenderButton.h" #include "MailInset.h" #include "Counters.h" +#include "EmbeddedFiles.h" #include "support/FileName.h" @@ -103,6 +104,9 @@ bool getStatus(Cursor &, FuncRequest const &, FuncStatus &) const; /// void updateLabels(Buffer const & buffer, ParIterator const &); + /// child document can be embedded + void registerEmbeddedFiles(Buffer const &, EmbeddedFiles &, + ParConstIterator const &) const; protected: InsetInclude(InsetInclude const &); /// Index: src/insets/InsetExternal.h =================================================================== --- src/insets/InsetExternal.h (revision 19920) +++ src/insets/InsetExternal.h (working copy) @@ -14,6 +14,7 @@ #include "Inset.h" #include "ExternalTransforms.h" +#include "EmbeddedFiles.h" #include "support/FileName.h" #include "support/Translator.h" @@ -147,6 +148,9 @@ void edit(Cursor & cur, bool left); /// bool getStatus(Cursor &, FuncRequest const &, FuncStatus &) const; + /// external file can be embedded + void registerEmbeddedFiles(Buffer const &, EmbeddedFiles &, + ParConstIterator const &) const; protected: InsetExternal(InsetExternal const &); Index: src/EmbeddedFiles.cpp =================================================================== --- src/EmbeddedFiles.cpp (revision 0) +++ src/EmbeddedFiles.cpp (revision 0) @@ -0,0 +1,307 @@ +// -*- 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 <config.h> + +#include "EmbeddedFiles.h" +#include "Buffer.h" +#include "BufferParams.h" +#include "Paragraph.h" +#include "ParIterator.h" +#include "debug.h" +#include "gettext.h" +#include "Format.h" + +#include "frontends/alert.h" + +#include <boost/filesystem/operations.hpp> + +#include "support/filetools.h" +#include "support/fs_extras.h" +#include "support/convert.h" + +#include <sstream> +#include <fstream> +#include <utility> + +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; + +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::zipFiles; + + +EmbeddedFile::EmbeddedFile(string const & file, string const & inzip_name, + STATUS status, ParConstIterator const & pit) + : DocFileName(file, true), inzip_name_(inzip_name), status_(status), + par_it_(pit), valid_(true) +{} + + +string EmbeddedFile::embeddedFile(Buffer const * buf) const +{ + return addName(buf->temppath(), inzip_name_); +} + + +void EmbeddedFile::setParIter(ParConstIterator const & pit) +{ + par_it_ = pit; +} + + +bool EmbeddedFiles::enabled() const +{ + return buffer_->params().embedded; +} + + +void EmbeddedFiles::enable(bool flag) +{ + if (enabled() != flag) { + // file will be changed + buffer_->markDirty(); + buffer_->params().embedded = flag; + } +} + + +void EmbeddedFiles::registerFile(string const & filename, + EmbeddedFile::STATUS status, ParConstIterator const & pit) +{ + 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) + break; + // find this filename + if (it != file_list_.end()) { + it->setParIter(pit); + it->setStatus(status); + it->validate(); + return; + } + // 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 + if (absolutePath(inzip_name)) + inzip_name = onlyFilename(inzip_name); + // if this name has been used... + // use _1_name, _2_name etc + if (!validInzipName(inzip_name)) { + size_t i = 1; + string tmp = inzip_name; + do { + inzip_name = convert<string>(i) + "_" + tmp; + } while (!validInzipName(inzip_name)); + } + file_list_.push_back(EmbeddedFile(abs_filename, inzip_name, status, pit)); +} + + +void EmbeddedFiles::update() +{ + // 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) + 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; +} + + +bool EmbeddedFiles::write(DocFileName const & filename) +{ + // file in the temporary path has the content + string const content = FileName(addName(buffer_->temppath(), + onlyFilename(filename.toFilesystemEncoding()))).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(); + // 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()) { + // use external file if possible + if (it->status() != EmbeddedFile::EMBEDDED && fs::exists(it->absFilename())) + filenames.push_back(make_pair(it->absFilename(), it->inzipName())); + // use embedded file (AUTO or EMBEDDED mode) + else if(fs::exists(it->embeddedFile(buffer_))) + filenames.push_back(make_pair(it->embeddedFile(buffer_), it->inzipName())); + else + lyxerr << "File " << it->absFilename() << " does not exist. Skip embedding it. " << endl; + } + } + // 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); + // 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; + } + return true; +} + + +bool EmbeddedFiles::validInzipName(string const & name) +{ + EmbeddedFileList::iterator it = file_list_.begin(); + EmbeddedFileList::iterator it_end = file_list_.end(); + for (; it != it_end; ++it) + if (it->inzipName() == name) + return false; + return true; +} + + +istream & operator>> (istream & is, EmbeddedFiles & files) +{ + 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; +} + + +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; +} + +} Index: src/Buffer.h =================================================================== --- src/Buffer.h (revision 19920) +++ src/Buffer.h (working copy) @@ -396,6 +396,11 @@ TocBackend & tocBackend(); TocBackend const & tocBackend() const; //@} + + //@{ + EmbeddedFiles & embeddedFiles(); + EmbeddedFiles const & embeddedFiles() const; + //@} private: /** Inserts a file into a document Index: src/BufferParams.h =================================================================== --- src/BufferParams.h (revision 19920) +++ src/BufferParams.h (working copy) @@ -261,6 +261,8 @@ std::string parentname; /// bool compressed; + /// + bool embedded; /// the author list for the document AuthorList & authors(); Index: src/Makefile.am =================================================================== --- src/Makefile.am (revision 19920) +++ src/Makefile.am (working copy) @@ -127,6 +127,8 @@ DispatchResult.h \ DocIterator.cpp \ DocIterator.h \ + EmbeddedFiles.h \ + EmbeddedFiles.cpp \ Encoding.cpp \ Encoding.h \ ErrorList.cpp \ Index: src/EmbeddedFiles.h =================================================================== --- src/EmbeddedFiles.h (revision 0) +++ src/EmbeddedFiles.h (revision 0) @@ -0,0 +1,244 @@ +// -*- C++ -*- +/** + * \file EmbeddedFiles.h + * 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. + * + */ + +#ifndef EMBEDDEDFILES_H +#define EMBEDDEDFILES_H + +#include "support/FileName.h" + +#include <vector> +#include <utility> + +#include "ParIterator.h" +#include "Paragraph.h" + +/** + +This file, and the embedding dialog implemented in src/frontends, implements +an 'Embedded Files' feature of lyx. + + +Expected features: +========================= + +1. With embedding enabled (disabled by default), .lyx file can embed graphics, +listings, bib file etc. + +2. Embedding of certain files are automatic (graphics, bib etc), and +other files can be embedded manually. + +3. Embedded file.lyx file is a zip file, with file.lyx, manifest.txt +and embedded files. There is no subdirectory in this zip file (with current +implementation). + +4. If no file is embedded, file.lyx is in plain text format. This is desired +by many users because pure-text format allows easy manipulation and better +version control. + +5. Embedded files can be "EMBEDDED", "EXTERNAL", or "AUTO". In the +"AUTO" mode, external files will be used if available; otherwise the +embedded version will be used. In this way, users can work as usual by +modifying external listings, graphics, and do not have to worry about +embedding. "EMBEDDED" and "EXTERNAL" modes ignore or use external files +respectively. + +6. An embedding dialog is provided to change embedding status (buffer +level or individual embedded files), manually embed, extract, view +or edit files. + +Overall, this feature allows two ways of editing a .lyx file + +a. The continuous use of the pure-text .lyx file format with external +files. This is the default file format, and allows external editing +of .lyx file and better use of version control systems. + +b. The embedded way. Figures etc are inserted to .lyx file and will +be embedded. These embedded files can be viewed or edited through +the embedding dialog. This file can be shared with others more +easily. The advantage of lyx' embedding approach is that external files +will be automatically used and embedded if the file is in "AUTO" mode. + + +Implementation: +====================== + +1. An EmbeddedFiles class is implemented to keep the embedded files ( +class EmbeddedFile). (c.f. src/EmbeddedFiles.[h|cpp]) +This class keeps a manifest that has + a. external relative filename + b. inzip filename (no directory structure), name aliasing is used if + two external files share the same name. + c. embedding mode. +It also provides functions to + a. manipulate manifest + b. scan a buffer for embeddable files + c. look up inzipname from external filename + d. look up external filename from inzipname + +2. When a file is saved, it is scanned for embedded files. (c.f. +EmbeddedFiles::update(), Inset::registerEmbeddedFiles()). + +3. When a lyx file file.lyx is saved, it is save to tmppath() first. +If there is any embedded files, these files are compressed along with +file.lyx and a manifest.txt. If there is no embedded file, or if +embedding is disabled, file.lyx is saved in the usual pure-text form. +(c.f. Buffer::writeFile(), EmbeddedFiles::write()) + +4. When a lyx file.lyx file is opened, if it is a zip file, it is +decompressed to tmppath(). If manifest.txt and file.lyx exists in +tmppath(), the manifest is read to buffer, and tmppath()/file.lyx is +read as usual. If file.lyx is not a zip file, it is read as usual. +(c.f. bool Buffer::readFile()) + +5. A menu item Document -> Embedded Files is provided to open +a embedding dialog. It handles a EmbddedFiles point directly. +From this dialog, a user can disable embedding, change embedding status, +or embed other files, extract, view, edit files. + +6. When a lyx file is loaded, Embedded files can have + a. both external and internal copy + b. only external copy (filename()) + c. only embedded copy (temppath()/inzipname) +And each can have "AUTO", "EXTERNAL", "EMBEDDED" status. Proper +handling of each case is required. + +7. If embedding of a .lyx file with embedded files is disabled, all its +embedded files are copied to their respective external filenames. This +is why external filename will exist even if a file is at "EMBEDDED" status. + +8. Individual embeddable insets should find ways to handle embedded files. +InsetGraphics replace params().filename with its temppath()/inzipname version +when the inset is created. The filename appears as /tmp/..../inzipname +when lyx runs. When params().filename is saved, lyx checks if this is an +embedded file (check path == temppath()), if so, save filename() instead. +(c.f. InsetGraphic::read(), InsetGraphics::edit(), InsetGraphicsParams::write()) + + +*/ + +namespace lyx { + +class Buffer; + +class EmbeddedFile : public support::DocFileName +{ +public: + /** + Embedding status of this DocFileName. + */ + enum STATUS { + // uninitialized/invalid status + NONE, + // If the external version of the file is available, it will be used + // to generate output, and be embedded to the saved lyx file. + // Otherwise, embedded version will be used. + AUTO, + // Always use embedded version. + EMBEDDED, + // Do not embed this file, always use external version. + EXTERNAL + }; + + EmbeddedFile(std::string const & file, std::string const & inzip_name, + STATUS status, ParConstIterator const & pit); + + /// filename in the zip file, usually the relative path + std::string inzipName() const { return inzip_name_; } + /// embedded file, equals to temppath()/inzipName() + std::string embeddedFile(Buffer const * buf) const; + + /// paragraph id + void setParIter(ParConstIterator const & pit); + int const parID() const { return par_it_->id(); } + + /// embedding status of this file + bool embedded() const { return status_ != EXTERNAL; } + STATUS status() const { return status_; } + void setStatus(STATUS status) { status_ = status; } + + // A flag indicating whether or not this filename is valid. + // When lyx runs, InsetGraphics etc may be added or removed so filename + // maybe obsolete. In Buffer::updateEmbeddedFiles, the EmbeddedFiles is first + // invalidated (c.f. invalidate()), and all insets are asked to register + // embedded files. In this way, EmbeddedFileList will be refreshed, with + // status setting untouched. + bool valid() const { return valid_; } + void validate() { valid_ = true; } + void invalidate() { valid_ = false; } + +private: + /// filename in zip file + std::string inzip_name_; + /// the status of this docfile + STATUS status_; + /// + bool valid_; + /// Current position of the item, used to locate the files + /// A figure may be referred by several items. In this case + /// only the last location is recorded. + ParConstIterator par_it_; +}; + + +class EmbeddedFiles { +public: + typedef std::vector<EmbeddedFile> EmbeddedFileList; +public: + /// + EmbeddedFiles(Buffer * buffer = NULL): file_list_(), buffer_(buffer) {} + /// + ~EmbeddedFiles() {} + + /// return buffer params embedded flag + bool enabled() const; + /// set buffer params embedded flag + void enable(bool flag); + + /// add a file item + void registerFile(std::string const & filename, + EmbeddedFile::STATUS status=EmbeddedFile::AUTO, + ParConstIterator const & pit = ParConstIterator()); + + /// scan the buffer and get a list of EmbeddedFile + void update(); + + /// write a zip file + bool write(support::DocFileName const & filename); + + void clear() { file_list_.clear(); } + + /// + EmbeddedFileList::iterator begin() { return file_list_.begin(); } + EmbeddedFileList::iterator end() { return file_list_.end(); } + EmbeddedFileList::const_iterator begin() const { return file_list_.begin(); } + EmbeddedFileList::const_iterator end() const { return file_list_.end(); } + + /// + friend std::istream & operator>> (std::istream & is, EmbeddedFiles &); + + friend std::ostream & operator<< (std::ostream & os, EmbeddedFiles const &); +private: + /// if a inzip name already exists + bool validInzipName(std::string const & name); + /// list of embedded files + EmbeddedFileList file_list_; + /// + Buffer * buffer_; +}; + + +std::istream & operator>> (std::istream & is, EmbeddedFiles &); + +std::ostream & operator<< (std::ostream & os, EmbeddedFiles const &); + +} +#endif Index: src/Buffer.cpp =================================================================== --- src/Buffer.cpp (revision 19920) +++ src/Buffer.cpp (working copy) @@ -54,6 +54,7 @@ #include "TocBackend.h" #include "Undo.h" #include "version.h" +#include "EmbeddedFiles.h" #include "insets/InsetBibitem.h" #include "insets/InsetBibtex.h" @@ -98,6 +99,7 @@ using std::ostream; using std::ostringstream; using std::ofstream; +using std::ifstream; using std::pair; using std::stack; using std::vector; @@ -132,6 +134,7 @@ using support::tempName; using support::trim; using support::sum; +using support::unzipToDir; namespace Alert = frontend::Alert; namespace os = support::os; @@ -194,6 +197,9 @@ /// Container for all sort of Buffer dependant errors. map<string, ErrorList> errorLists; + /// all embedded files of this buffer + EmbeddedFiles embedded_files; + /// timestamp and checksum used to test if the file has been externally /// modified. (Used to properly enable 'File->Revert to saved', bug 4114). time_t timestamp_; @@ -204,7 +210,7 @@ Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_) : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_), filename(file), file_fully_loaded(false), inset(params), - toc_backend(&parent), timestamp_(0), checksum_(0) + toc_backend(&parent), embedded_files(&parent), timestamp_(0), checksum_(0) { inset.setAutoBreakRows(true); lyxvc.buffer(&parent); @@ -351,6 +357,18 @@ } +EmbeddedFiles & Buffer::embeddedFiles() +{ + return pimpl_->embedded_files; +} + + +EmbeddedFiles const & Buffer::embeddedFiles() const +{ + return pimpl_->embedded_files; +} + + string const Buffer::getLatexName(bool const no_path) const { string const name = changeExtension(makeLatexName(fileName()), ".tex"); @@ -634,8 +652,32 @@ bool Buffer::readFile(FileName const & filename) { + FileName fname(filename); // Check if the file is compressed. - string const format = getFormatFromContents(filename); + string format = getFormatFromContents(filename); + if (format == "zip") { + // decompress to a temp directory + LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl; + unzipToDir(filename.toFilesystemEncoding(), temppath()); + // + FileName manifest(addName(temppath(), "manifest.txt")); + FileName lyxfile(addName(temppath(), + onlyFilename(filename.toFilesystemEncoding()))); + // if both manifest.txt and file.lyx exist, this is am embedded file + if (fs::exists(manifest.toFilesystemEncoding()) && + fs::exists(lyxfile.toFilesystemEncoding())) { + params().embedded = true; + fname = lyxfile; + // read manifest file + ifstream is(manifest.toFilesystemEncoding().c_str()); + is >> pimpl_->embedded_files; + is.close(); + LYXERR(Debug::FILES) << filename << " is a embedded file. Its manifest is:\n" + << pimpl_->embedded_files; + } + } + // The embedded lyx file can also be compressed, for backward compatibility + format = getFormatFromContents(fname); if (format == "gzip" || format == "zip" || format == "compress") { params().compressed = true; } @@ -643,8 +685,8 @@ // remove dummy empty par paragraphs().clear(); Lexer lex(0, 0); - lex.setFile(filename); - if (readFile(lex, filename) != success) + lex.setFile(fname); + if (readFile(lex, fname) != success) return false; return true; @@ -845,20 +887,34 @@ bool retval = false; + FileName content; + if (params().embedded) + // first write the .lyx file to the temporary directory + content = FileName(addName(temppath(), + onlyFilename(fname.toFilesystemEncoding()))); + else + content = fname; + if (params().compressed) { - gz::ogzstream ofs(fname.toFilesystemEncoding().c_str(), ios::out|ios::trunc); + gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc); if (!ofs) return false; retval = write(ofs); } else { - ofstream ofs(fname.toFilesystemEncoding().c_str(), ios::out|ios::trunc); + ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc); if (!ofs) return false; retval = write(ofs); } + if (retval && params().embedded) { + // write file.lyx and all the embedded files to the zip file fname + // if embedding is enabled, and there is any embedded file + pimpl_->embedded_files.update(); + return pimpl_->embedded_files.write(fname); + } return retval; } Index: src/BufferParams.cpp =================================================================== --- src/BufferParams.cpp (revision 19920) +++ src/BufferParams.cpp (working copy) @@ -353,6 +353,9 @@ listings_params = string(); pagestyle = "default"; compressed = false; + // temporarily enable embedding for testing. Will set to false + // when embedding GUI is added + embedded = true; for (int iter = 0; iter < 4; ++iter) { user_defined_bullet(iter) = ITEMIZE_DEFAULTS[iter]; temp_bullet(iter) = ITEMIZE_DEFAULTS[iter]; Index: po/POTFILES.in =================================================================== --- po/POTFILES.in (revision 19920) +++ po/POTFILES.in (working copy) @@ -7,6 +7,7 @@ src/Color.cpp src/Converter.cpp src/CutAndPaste.cpp +src/EmbeddedFiles.cpp src/Exporter.cpp src/Font.cpp src/Format.cpp Index: development/scons/scons_manifest.py =================================================================== --- development/scons/scons_manifest.py (revision 19920) +++ development/scons/scons_manifest.py (working copy) @@ -56,6 +56,7 @@ Dimension.h DispatchResult.h DocIterator.h + EmbeddedFiles.h Encoding.h ErrorList.h Exporter.h @@ -166,6 +167,7 @@ CutAndPaste.cpp DepTable.cpp DocIterator.cpp + EmbeddedFiles.cpp Encoding.cpp ErrorList.cpp Exporter.cpp