backend/wbprivate/workbench/wb_model_file.cpp (700 lines of code) (raw):
/*
* Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms, as
* designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <algorithm>
#include <fcntl.h>
#include <zip.h>
#include "wb_model_file.h"
#include <algorithm>
#include <set>
#include <stdexcept>
#include <errno.h>
#include "grt.h"
#include "base/log.h"
#include "base/string_utilities.h"
#include "base/file_utilities.h"
#include "base/file_functions.h"
#include "base/util_functions.h"
#include "mforms/utilities.h"
#include "mdc_image.h"
#include "grt/grt_manager.h"
#include "grts/structs.workbench.h"
#include <glib/gstdio.h>
#define DOCUMENT_FORMAT "MySQL Workbench Model"
// version history:
// switched to 1.1.6 in 5.0.20
// switched to 1.2.0 in 5.1.0
// switched from 1.2.0 to 1.3.0 in 5.1.7
// updated to 1.4.0 in 5.2.0
// updated to 1.4.1 in 5.2.19
// updated to 1.4.2 in 5.2.33
// updated to 1.4.3 in 5.2.40
// updated to 1.4.4 in 5.2.42
#define DOCUMENT_VERSION "1.4.4"
#define ZIP_FILE_FORMAT "1.0"
#define IMAGES_DIR "@images"
#define NOTES_DIR "@notes"
#define SCRIPTS_DIR "@scripts"
#define DB_DIR "@db"
#define DB_FILE "data.db"
#define ZIP_FILE_COMMENT DOCUMENT_FORMAT " archive " ZIP_FILE_FORMAT
/* Auto-saving
*
* Auto-saving works by saving the model document file (the XML) to the expanded document folder
* from time to time, named as document-autosave.mwb.xml. The expanded document folder is
* automatically deleted when it is closed normally.
* When a document is opened, it will check if there already is a document folder for that file
* and if so, the recovery function will kick in, using the autosave XML file.
*/
DEFAULT_LOG_DOMAIN("model")
using namespace bec;
using namespace wb;
using namespace base;
const std::string ModelFile::lock_filename("lock");
void ModelFile::copy_file(const std::string &srcfile, const std::string &destfile) {
char buffer[4098];
FILE *sf = base_fopen(srcfile.c_str(), "rb");
if (!sf)
throw grt::os_error("Could not open file " + srcfile, errno);
FILE *tf = base_fopen(destfile.c_str(), "w+");
if (!tf) {
fclose(sf);
throw grt::os_error("Could not create file " + destfile, errno);
}
size_t c;
while ((c = fread(buffer, 1, sizeof(buffer), sf)) > 0) {
if (fwrite(buffer, 1, c, tf) < c) {
int err = errno;
fclose(sf);
fclose(tf);
throw grt::os_error("Error copying to file " + destfile, err);
}
}
fclose(sf);
fclose(tf);
}
static int rmdir_recursively(const char *path) {
int res = 0;
GError *error = NULL;
GDir *dir;
const char *dir_entry;
gchar *entry_path;
dir = g_dir_open(path, 0, &error);
if (!dir && error) {
res = error->code;
g_error_free(error);
return res;
}
while ((dir_entry = g_dir_read_name(dir))) {
if (strcmp(dir_entry, ".") == 0 || strcmp(dir_entry, "..") == 0)
continue;
entry_path = g_build_filename(path, dir_entry, NULL);
if (g_file_test(entry_path, G_FILE_TEST_IS_DIR))
(void)rmdir_recursively(entry_path);
else
(void)g_remove(entry_path);
g_free(entry_path);
}
(void)g_rmdir(path);
g_dir_close(dir);
return res;
}
std::string ModelFile::create_document_dir(const std::string &dir, const std::string &prefix) {
std::string path;
char s[12];
int i = 0;
strcpy(s, "d");
for (;;) {
path = dir + "/" + prefix + s;
try {
(void)base::create_directory(path, 0700); // return false means dir already exists
} catch (base::file_error &exc) {
throw grt::os_error(strfmt("Cannot create directory for document: %s", exc.what()));
}
try {
_temp_dir_lock = new base::LockFile(base::makePath(path, lock_filename.c_str()));
break;
} catch (const base::file_locked_error &) {
// continue
}
sprintf(s, "d%i", ++i);
}
return path;
}
ModelFile::ModelFile(const std::string &tmpdir) : _temp_dir_lock(0), _dirty(false) {
_temp_dir = tmpdir;
}
ModelFile::~ModelFile() {
cleanup();
}
void ModelFile::copy_file_to(const std::string &file, const std::string &dest) {
copy_file(get_path_for(file), dest);
}
void ModelFile::open(const std::string &path) {
bool file_is_zip;
bool file_is_autosave = false;
RecMutexLock lock(_mutex);
if (base::is_directory(path) && path[path.size() - 1] == 'd') {
file_is_zip = false;
file_is_autosave = true;
} else {
FILE *f = base_fopen(path.c_str(), "rb");
if (!f)
throw grt::os_error("Could not open file " + path + ": " + strerror(errno));
unsigned char buffer[10];
size_t c;
if ((c = fread(buffer, 1, 10, f)) < 10) {
fclose(f);
if (c == 0)
throw std::runtime_error("File is empty.");
else
throw std::runtime_error("Invalid or corrupt file.");
}
fclose(f);
if (buffer[0] == 0x50 && buffer[1] == 0x4b && buffer[2] == 0x03 && buffer[3] == 0x04 && buffer[4] == 0x14)
file_is_zip = true;
else {
// file_is_zip= false;
if (strncmp((char *)buffer, "<?xml", c < 5 ? c : 5) == 0) // check if this is a XML, otherwise assume zip
file_is_zip = false;
else
file_is_zip = true;
}
}
std::string basename = base::basename(path);
// do some sanity checks
if (basename.empty()) {
throw std::runtime_error("Invalid path " + path);
}
std::string auto_save_dir = file_is_autosave ? path : base::makePath(_temp_dir, basename).append("d"); // default
std::list<std::string> possible_autosaves = base::scan_for_files_matching(auto_save_dir + "*");
for (std::list<std::string>::const_iterator d = possible_autosaves.begin(); d != possible_autosaves.end(); ++d) {
gchar *path;
gsize length;
// check if this autosave is active/in use
if (base::LockFile::check(base::makePath(*d, lock_filename.c_str())) != base::LockFile::NotLocked)
continue;
if (g_file_get_contents(base::makePath(*d, "real_path").c_str(), &path, &length, NULL)) {
// ensure this autosave is for the model being opened
if (std::string(path, length) == path) {
auto_save_dir = *d;
g_free(path);
break;
}
g_free(path);
} else // if the autosave dir has no file, then maybe it's from a version that still didn't save the path info
break;
}
bool recover = false;
if (!auto_save_dir.empty() && g_file_test(auto_save_dir.c_str(), G_FILE_TEST_EXISTS)) {
// See if we can acquire the auto save lock. If that fails then another instance of WB has the lock already
// so we don't do anything with this file.
try {
base::LockFile test_lock(base::makePath(auto_save_dir, lock_filename.c_str()));
} catch (const base::file_locked_error &) {
mforms::Utilities::show_warning(
_("Opening Document"),
base::strfmt(_("Could not lock the document %s.\n"
"The file is probably already opened in another instance of the application."),
path.c_str()),
_("OK"));
return;
}
time_t file_ts;
base::file_mtime(path, file_ts);
time_t autosave_ts;
base::file_mtime(base::makePath(auto_save_dir, MAIN_DOCUMENT_NAME), autosave_ts);
if (autosave_ts == 0)
base::file_mtime(auto_save_dir, autosave_ts);
// document dir already exists, ask if it should be recovered or deleted
if (mforms::Utilities::show_warning(
_("Document Recovery"),
base::strfmt(_("The document %s was not properly closed in a previous session on %s.\n"
"The file you're about to open was last saved %s.\n"
"Would you like to use the recovered model? Continuing without recovering will remove the "
"auto-saved data."),
path.c_str(), base::fmttime(autosave_ts, DATETIME_FMT).c_str(),
base::fmttime(file_ts, DATETIME_FMT).c_str()),
_("Recover"), _("Continue"), "") == mforms::ResultOk) {
logInfo("Recovering %s...", path.c_str());
recover = true;
_content_dir = auto_save_dir;
if (g_file_test((auto_save_dir + "/" + MAIN_DOCUMENT_AUTOSAVE_NAME).c_str(), G_FILE_TEST_EXISTS)) {
g_remove((auto_save_dir + "/" + MAIN_DOCUMENT_NAME).c_str());
int rc = g_rename((auto_save_dir + "/" + MAIN_DOCUMENT_AUTOSAVE_NAME).c_str(),
(auto_save_dir + "/" + MAIN_DOCUMENT_NAME).c_str());
if (rc < 0) {
// Rename failed, so try copying the file.
try {
copy_file((auto_save_dir + "/" + MAIN_DOCUMENT_AUTOSAVE_NAME).c_str(),
(auto_save_dir + "/" + MAIN_DOCUMENT_NAME).c_str());
} catch (const std::exception &exc) {
mforms::Utilities::show_error("Error recovering file",
base::strfmt("There was an error recovering the document: %s\n", exc.what()),
"OK", "", "");
g_rename(auto_save_dir.c_str(), (auto_save_dir + ".cantrecover").c_str());
recover = false;
}
}
}
} else // Cancel recovery
{
logInfo("Cleaning up leftover auto-save directory %s", auto_save_dir.c_str());
rmdir_recursively(auto_save_dir.c_str());
}
}
if (!recover) {
_content_dir = create_document_dir(_temp_dir, basename);
if (file_is_zip) {
unpack_zip(path, _content_dir);
check_and_fix_data_file_bug();
} else {
std::string destpath = _content_dir;
destpath.append("/");
destpath.append(MAIN_DOCUMENT_NAME);
// assume old XML format and "convert" it
copy_file(path, destpath);
}
_dirty = false;
} else {
_dirty = true;
// re-lock it for ourselves
_temp_dir_lock = new base::LockFile(base::makePath(_content_dir, lock_filename.c_str()));
}
}
//--------------------------------------------------------------------------------------------------
/**
* Returns a previously set zip file comment (if any) without unzipping the file.
*/
std::string ModelFile::read_comment(const std::string &path) {
std::string schemas;
int err;
zip *z = zip_open(path.c_str(), 0, &err);
if (z != NULL) {
int length;
const char *value = zip_get_archive_comment(z, &length, 0);
if (value && length > 0) {
std::string comment(value, length);
if (length > -1) {
std::size_t found = comment.find("model-schemas:");
if (found != std::string::npos) {
const char *ptr = comment.c_str() + found + 15;
while (*ptr) {
if (*ptr != '\n')
schemas += *ptr;
ptr++;
}
}
}
}
zip_close(z);
}
return schemas;
}
//--------------------------------------------------------------------------------------------------
void ModelFile::create() {
RecMutexLock lock(_mutex);
_content_dir = create_document_dir(_temp_dir, "newmodel.mwb");
add_db_file(_content_dir);
_dirty = false;
}
std::string ModelFile::get_path_for(const std::string &file) {
return _content_dir + "/" + file;
}
//--------------------------------------------------------------------------------------------------
// reading
workbench_DocumentRef ModelFile::retrieve_document() {
RecMutexLock lock(_mutex);
xmlDocPtr xmldoc = grt::GRT::get()->load_xml(get_path_for(MAIN_DOCUMENT_NAME));
retry:
try {
workbench_DocumentRef doc(unserialize_document(xmldoc, get_path_for(MAIN_DOCUMENT_NAME)));
xmlFreeDoc(xmldoc);
xmldoc = NULL;
// Here the xml content is syntactically correct. Now do some semantic checks for sanity.
if (!semantic_check(doc))
throw std::logic_error(_("Invalid model file content."));
return doc;
} catch (grt::grt_runtime_error &exc) {
if (strstr(exc.detail.c_str(), "Type mismatch: expected object of type"))
if (check_and_fix_duplicate_uuid_bug(xmldoc))
goto retry;
throw;
} catch (std::exception &) {
if (xmldoc)
xmlFreeDoc(xmldoc);
throw;
}
return workbench_DocumentRef();
}
//--------------------------------------------------------------------------------------------------
bool ModelFile::semantic_check(workbench_DocumentRef doc) {
// 1) Is there a valid physical model in the document?
if (!doc->physicalModels().is_valid() || doc->physicalModels().count() == 0)
return false;
return true;
}
//--------------------------------------------------------------------------------------------------
std::list<std::string> ModelFile::unpack_zip(const std::string &zipfile, const std::string &destdir) {
std::list<std::string> unpacked_files;
if (g_mkdir_with_parents(destdir.c_str(), 0700) < 0)
throw grt::os_error(strfmt(_("Cannot create temporary directory for open document: %s"), destdir.c_str()), errno);
int err;
#ifdef ZIP_DISABLE_DEPRECATED
// Would be good if we could test for zip_fdopen, but there's no way in the preprocessor.
// And there's no version macro either for libzip.
// Define ZIP_DISABLE_DEPRECATED to use the newer APIs.
int fd = base_open(zipfile, O_RDONLY, S_IREAD); // Error checking is done already before.
zip *z = zip_fdopen(fd, 0, &err);
#else
zip *z = zip_open(zipfile.c_str(), 0, &err); // Older versions of libzip.
#endif
if (z == NULL) {
if (err == ZIP_ER_NOZIP)
throw std::runtime_error("The file is not a Workbench document.");
else if (err == ZIP_ER_MEMORY)
throw grt::os_error("Cannot allocate enough memory to open document.");
else if (err == ZIP_ER_NOENT)
throw grt::os_error("File not found.");
#ifdef ZIP_DISABLE_DEPRECATED
// the new API doesn't offer a way to extract an error without a pertaining zip*, which is NULL in this case
std::string msg = "error opening zip archive";
#else
int len = zip_error_to_str(NULL, 0, 0, err);
std::string msg;
if (len > 0) {
char *buf = (char *)g_malloc(len + 1);
zip_error_to_str(buf, len + 1, 0, err);
msg = buf;
g_free(buf);
} else
msg = "error opening zip archive";
#endif
zip_close(z);
throw std::runtime_error(strfmt(_("Cannot open document file: %s"), msg.c_str()));
}
#ifdef ZIP_DISABLE_DEPRECATED
zip_int64_t count = zip_get_num_entries(z, 0);
#else
int count = zip_get_num_files(z);
#endif
for (int i = 0; i < count; i++) {
zip_file *file = zip_fopen_index(z, i, 0);
if (!file) {
const char *err = zip_strerror(z);
zip_close(z);
throw std::runtime_error(strfmt(_("Error opening document file: %s"), err));
}
const char *zname = zip_get_name(z, i, 0);
if (strcmp(zname, "/") == 0 || strcmp(zname, "\\") == 0) {
zip_fclose(file);
continue;
}
std::string dirname = base::dirname(zname);
std::string basename = base::basename(zname);
// skip lock file as it is already locked and inaccessible
if (basename == lock_filename) {
zip_fclose(file);
continue;
}
std::string outpath = destdir;
if (!dirname.empty()) {
outpath.append("/");
outpath.append(dirname);
if (g_mkdir_with_parents(outpath.c_str(), 0700) < 0) {
zip_fclose(file);
zip_close(z);
throw grt::os_error(_("Error creating temporary directory while opending document."), errno);
}
}
outpath.append("/");
outpath.append(basename);
FILE *outfile = base_fopen(outpath.c_str(), "w+");
if (!outfile) {
zip_fclose(file);
zip_close(z);
throw grt::os_error(_("Error creating temporary file while opending document."), errno);
}
unpacked_files.push_back(outpath);
char buffer[4098];
ssize_t c;
while ((c = (size_t)zip_fread(file, buffer, sizeof(buffer))) > 0) {
if ((ssize_t)fwrite(buffer, 1, c, outfile) < c) {
int err = ferror(outfile);
fclose(outfile);
zip_fclose(file);
zip_close(z);
throw grt::os_error(_("Error writing temporary file while opending document."), err);
}
}
if (c < 0) {
std::string err = zip_file_strerror(file) ? zip_file_strerror(file) : "";
zip_fclose(file);
zip_close(z);
throw std::runtime_error(strfmt(_("Error opening document file: %s"), err.c_str()));
}
zip_fclose(file);
fclose(outfile);
}
zip_close(z);
return unpacked_files;
}
static void zip_dir_contents(zip *z, const std::string &destdir, const std::string &partial) {
GError *error = 0;
GDir *dir = g_dir_open(destdir.empty() ? "." : destdir.c_str(), 0, &error);
if (!dir) {
zip_close(z);
std::string err = error ? error->message : "Cannot open document directory.";
g_error_free(error);
throw grt::os_error(err);
}
// must add stuff in 2 steps, 1st files only and then dirs only
for (int add_directories = 0; add_directories < 2; add_directories++) {
const gchar *entry;
while ((entry = g_dir_read_name(dir))) {
std::string tmp = destdir;
if (!tmp.empty())
tmp.append("/").append(entry);
else
tmp.append(entry);
if (g_file_test(tmp.c_str(), G_FILE_TEST_IS_DIR)) {
if (add_directories) {
try {
zip_dir_contents(z, destdir.empty() ? entry : destdir + G_DIR_SEPARATOR + entry, tmp);
} catch (...) {
g_dir_close(dir);
throw;
}
}
} else {
if (!add_directories) {
zip_source *src = zip_source_file(z, tmp.c_str(), 0, 0);
#ifdef _MSC_VER
if (!src || zip_file_add(z, tmp.c_str(), src, ZIP_FL_OVERWRITE | ZIP_FL_ENC_UTF_8) < 0) {
zip_source_free(src);
g_dir_close(dir);
throw std::runtime_error(zip_strerror(z));
}
#else
if (!src || zip_add(z, tmp.c_str(), src) < 0) {
zip_source_free(src);
g_dir_close(dir);
throw std::runtime_error(zip_strerror(z));
}
#endif
}
}
}
g_dir_rewind(dir);
}
g_dir_close(dir);
}
void ModelFile::pack_zip(const std::string &zipfile, const std::string &destdir, const std::string &comment) {
std::string curdir;
{
gchar *cwd = g_get_current_dir();
curdir = cwd;
g_free(cwd);
}
// change to the document data directory (after opening the zip file)
// so that the zip won't have the full paths of the contents stored
if (g_chdir(destdir.c_str()) < 0)
throw grt::os_error("chdir failed.");
// zip_open will open an existing file even if ZIP_CREATE is specified, so
// we have to 1st delete the file...
int err = 0;
/* XXX: doesn't work yet as zip_close creates a temporary file without considering the name encoding
* and opening with zip_fdopen doesn't set the file name member of the zip struct
* which then crashes when an attempt is made to derived a temp name from that.
#ifdef ZIP_DISABLE_DEPRECATED
int fd = base_open(zipfile, O_CREAT, S_IWRITE);
zip *z = zip_fdopen(fd, 0, &err);
#else
zip *z = zip_open(zipfile.c_str(), 0, &err); // Older versions of libzip.
#endif
#*/
zip *z = zip_open(zipfile.c_str(), ZIP_CREATE, &err);
if (!z) {
if (err == ZIP_ER_MEMORY)
throw grt::os_error("Cannot allocate enough temporary memory to save document.");
else if (err == ZIP_ER_NOENT)
throw grt::os_error("File or directory not found.");
else
throw grt::os_error("Cannot create file.");
}
std::string zip_comment = ZIP_FILE_COMMENT;
if (!comment.empty()) {
zip_comment += '\n';
zip_comment += comment;
}
#if defined(zip_uint16_t) || defined(_MSC_VER)
zip_set_archive_comment(z, zip_comment.c_str(), (zip_uint16_t)zip_comment.size());
#else
zip_set_archive_comment(z, zip_comment.c_str(), (int)zip_comment.size());
#endif
try {
zip_dir_contents(z, "", "");
if (zip_close(z) < 0) {
std::string err = zip_strerror(z) ? zip_strerror(z) : "";
throw std::runtime_error(strfmt(_("Error writing zip file: %s"), err.c_str()));
}
g_chdir(curdir.c_str());
} catch (...) {
zip_close(z);
g_chdir(curdir.c_str());
throw;
}
}
workbench_DocumentRef ModelFile::unserialize_document(xmlDocPtr xmldoc, const std::string &path) {
std::string doctype, version;
grt::GRT::get()->get_xml_metainfo(xmldoc, doctype, version);
_loaded_version = version;
// reset list of warnings found during load
_load_warnings.clear();
if (doctype != DOCUMENT_FORMAT)
throw std::runtime_error("The file does not contain a Workbench document.");
if (version != DOCUMENT_VERSION) {
// first phase of document upgrade will upgrade it at XML level
if (!attempt_xml_document_upgrade(xmldoc, version))
throw std::runtime_error("The document was created in an incompatible version of the application.");
}
check_and_fix_inconsistencies(xmldoc, version);
grt::ValueRef value(grt::GRT::get()->unserialize_xml(xmldoc, path));
if (!value.is_valid())
throw std::runtime_error("Error unserializing document data.");
if (!workbench_DocumentRef::can_wrap(value))
throw std::runtime_error("Loaded file does not contain a valid Workbench document.");
workbench_DocumentRef doc(workbench_DocumentRef::cast_from(value));
// send phase will upgrade at GRT level
doc = attempt_document_upgrade(doc, xmldoc, version);
cleanup_upgrade_data();
check_and_fix_inconsistencies(doc, version);
return doc;
}
//--------------------------------------------------------------------------------------------------
/**
* Core save routine for model files. It does a backup of the existing model file of the given name
* (if there is one). Checks are performed to ensure existing backup files can be removed and existing
* model files can be renamed to .bak.
*/
bool ModelFile::save_to(const std::string &path, const std::string &comment) {
RecMutexLock lock(_mutex);
#ifdef _MSC_VER
const int read_write = _S_IWRITE | _S_IREAD;
#else
const int read_write = S_IRUSR | S_IWUSR;
#endif
if (g_file_test(path.c_str(), G_FILE_TEST_EXISTS)) {
std::string tmp = path + ".bak";
if (g_file_test(tmp.c_str(), G_FILE_TEST_EXISTS)) {
if (g_access(tmp.c_str(), 2) != 0) // Windows has no constants defined like W_OK.
{
int result = mforms::Utilities::show_warning(
_("Backup file is read-only"),
_("A backup file for this model already exists and must be removed, but is read only."
"\n\nDo you want to delete it anyway?"),
_("Delete"), _("Cancel"));
if (result != mforms::ResultOk)
return false;
if (g_chmod(tmp.c_str(), read_write) != 0) {
mforms::Utilities::show_error(
_("Cannot change permission"),
strfmt(_("The read-only state of the file:\n\n%s\n\ncannot be changed. Giving up -"
" the model file will not be saved."),
tmp.c_str()),
_("OK"));
return false;
}
}
g_remove(tmp.c_str());
}
// Check if the existing model file is read-only.
if (g_access(path.c_str(), 2) != 0) {
int result = mforms::Utilities::show_warning(
_("Model file is read-only"), _("The model file is read-only.\n\nDo you want to overwrite it anyway?"),
_("Overwrite File"), _("Cancel"));
if (result != mforms::ResultOk)
return false;
if (g_chmod(path.c_str(), read_write) != 0) {
mforms::Utilities::show_error(
_("Cannot change permission"),
strfmt(_("The read-only state of the file:\n\n%s\n\ncannot be changed. Giving up -"
" the model file will not be saved."),
path.c_str()),
_("OK"));
return false;
}
}
if (g_rename(path.c_str(), tmp.c_str()) < 0)
throw grt::os_error("Saving the document failed. The existing model file " + path +
" could not"
"be backed up. The system returned the error: \n\n",
errno);
}
for (std::list<std::string>::const_iterator iter = _delete_queue.begin(); iter != _delete_queue.end(); ++iter)
g_remove(get_path_for(*iter).c_str());
_delete_queue.clear();
// saving the file for real can delete the autosave
g_remove(get_path_for("document-autosave.mwb.xml").c_str());
g_remove(get_path_for("real_path").c_str());
if (g_path_is_absolute(path.c_str()))
pack_zip(path, _content_dir, comment);
else {
char *prefix = g_get_current_dir();
pack_zip(std::string(prefix).append("/").append(path), _content_dir, comment);
g_free(prefix);
}
_dirty = false;
return true;
}
//--------------------------------------------------------------------------------------------------
void ModelFile::cleanup() {
RecMutexLock lock(_mutex);
delete _temp_dir_lock;
_temp_dir_lock = 0;
if (!_content_dir.empty())
rmdir_recursively(_content_dir.c_str());
}
void ModelFile::add_db_file(const std::string &content_dir) {
std::string db_tpl_file_path = bec::GRTManager::get()->get_data_file_path("data/" DB_FILE);
std::string db_file_dir_path = content_dir + "/" + DB_DIR;
add_attachment_file(db_file_dir_path, db_tpl_file_path);
}
std::string ModelFile::get_rel_db_file_path() {
return DB_DIR "/" DB_FILE;
}
std::string ModelFile::get_db_file_dir_path() {
return _content_dir + "/" + DB_DIR;
}
std::string ModelFile::get_db_file_path() {
return get_db_file_dir_path() + "/" + DB_FILE;
}
/** Adds an external file to the document.
*/
std::string ModelFile::add_attachment_file(const std::string &destdir, const std::string &path) {
std::string prefix = destdir + "/";
if (!path.empty()) {
prefix += base::basename(path);
}
int i = 1;
std::string destfile = prefix;
if (!g_file_test(destdir.c_str(), G_FILE_TEST_IS_DIR)) {
if (g_mkdir_with_parents(destdir.c_str(), 0700) < 0)
throw grt::os_error("Could not create directory for attached file");
}
// if path is not supplied, default value of destfile would be filled with the dirname only
if (path.empty())
destfile = strfmt("%s%i", prefix.c_str(), i++);
while (g_file_test(destfile.c_str(), G_FILE_TEST_EXISTS))
destfile = strfmt("%s%i", prefix.c_str(), i++);
if (path.empty()) {
FILE *f = base_fopen(destfile.c_str(), "w+");
if (f)
fclose(f);
else
throw grt::os_error("Error creating attached file");
} else {
try {
ModelFile::copy_file(path, destfile);
} catch (std::exception &exc) {
throw std::runtime_error(std::string("Error adding file to document: ").append(exc.what()));
}
}
destfile = base::basename(destdir).append("/").append(base::basename(destfile));
return destfile;
}
std::string ModelFile::add_image_file(const std::string &path) {
_dirty = true;
return add_attachment_file(_content_dir + "/" + IMAGES_DIR, path);
}
std::string ModelFile::add_script_file(const std::string &path) {
_dirty = true;
return add_attachment_file(_content_dir + "/" + SCRIPTS_DIR, path);
}
std::string ModelFile::add_note_file(const std::string &path) {
_dirty = true;
return add_attachment_file(_content_dir + "/" + NOTES_DIR, path);
}
bool ModelFile::has_file(const std::string &name) {
RecMutexLock lock(_mutex);
return g_file_test(get_path_for(name).c_str(), G_FILE_TEST_EXISTS) != 0;
}
void ModelFile::set_file_contents(const std::string &path, const std::string &data) {
set_file_contents(path, data.c_str(), data.size());
}
void ModelFile::set_file_contents(const std::string &path, const char *data, size_t size) {
std::string fpath = get_path_for(path);
GError *error = NULL;
g_file_set_contents(fpath.c_str(), data, (gssize)size, &error);
if (error != NULL)
throw std::runtime_error(std::string("Error while setting file contents: ") + error->message);
}
std::string ModelFile::get_file_contents(const std::string &path) {
gchar *contents = 0;
gsize length;
std::string tmp;
if (g_file_get_contents(get_path_for(path).c_str(), &contents, &length, NULL)) {
tmp = std::string(contents, length);
g_free(contents);
return tmp;
}
throw std::runtime_error("Error reading attached file contents.");
}
// writing
void ModelFile::store_document(const workbench_DocumentRef &doc) {
grt::GRT::get()->serialize(doc, get_path_for(MAIN_DOCUMENT_NAME), DOCUMENT_FORMAT, DOCUMENT_VERSION);
_dirty = true;
}
void ModelFile::store_document_autosave(const workbench_DocumentRef &doc) {
grt::GRT::get()->serialize(doc, get_path_for("document-autosave.mwb.xml"), DOCUMENT_FORMAT, DOCUMENT_VERSION);
}
void ModelFile::delete_file(const std::string &path) {
if (std::find(_delete_queue.begin(), _delete_queue.end(), path) == _delete_queue.end()) {
_dirty = true;
_delete_queue.push_back(path);
}
}
bool ModelFile::undelete_file(const std::string &path) {
std::list<std::string>::iterator iter;
if ((iter = std::find(_delete_queue.begin(), _delete_queue.end(), path)) == _delete_queue.end())
return false;
_dirty = true;
_delete_queue.erase(iter);
return true;
}
//--------------------------------------------------------------------------------------------------
cairo_surface_t *ModelFile::get_image(const std::string &path) {
return mdc::surface_from_png_image(get_path_for(path));
}
//--------------------------------------------------------------------------------------------------
void ModelFile::check_and_fix_data_file_bug() {
// WB up to 5.2.21 used G_DIR_SEPARATOR for data file paths. This was incorrect
// as the @db\data.db was being treated as a filename outside Windows, instead
// of a file in a subdirectory called @db. The issue was corrected, but the problem
// that files created until 5.2.21 had the data.db file in the wrong place.
// This is not a problem if the files are opened in the same platform, but it is
// when a file written in Windows is opened elsewhere. To solve that, we
// will rename @db\data.db to data.db in the @db subdirectory. One extra complication
// is that some model files will have both data files, because they were opened
// in a different platform and extra data was added since.
#ifndef _MSC_VER
// check if @db\data.db exists and is a file
std::string data_filename_in_windows = _content_dir + "/" + DB_DIR + "\\" + DB_FILE;
if (g_file_test(data_filename_in_windows.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) {
// if so, we rename it to @db/data.db, but first check if @db/data.db itself exists
if (g_file_test(get_db_file_path().c_str(), G_FILE_TEST_EXISTS)) {
// file already exists, rename it and leave it there
g_rename(get_db_file_path().c_str(), get_db_file_path().append(".old").c_str());
}
g_rename(data_filename_in_windows.c_str(), get_db_file_path().c_str());
}
#endif
}