backend/wbprivate/workbench/wb_context.cpp (2,333 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 */ #define PY_SSIZE_T_CLEAN #include <Python.h> #include "wb_tunnel.h" #include <errno.h> #include <glib.h> #include <glib/gstdio.h> #include "sqlide/wb_sql_editor_form.h" #include "wb_context.h" #include "wb_context_ui.h" #include "wb_model_file.h" #include "wb_version.h" #include "base/string_utilities.h" #include "base/util_functions.h" #include "base/scope_exit_trigger.h" #include "grt/clipboard.h" #include "grt/plugin_manager.h" #include "cppdbc.h" #include "wb_module.h" #include "grts/structs.workbench.h" #include "model/wb_context_model.h" #include "model/wb_component_basic.h" #include "model/wb_component_logical.h" #include "model/wb_component_physical.h" #include "sqlide/wb_context_sqlide.h" #include "upgrade_helper.h" #include "grtui/grtdb_connect_dialog.h" #include "grtdb/db_helpers.h" #include "interfaces/interfaces.h" #include "grt/validation_manager.h" #include "base/threaded_timer.h" #include "base/log.h" #include "base/drawing.h" #include "mforms/mforms.h" #include "mforms/menubar.h" #include "mforms/toolbar.h" DEFAULT_LOG_DOMAIN(DOMAIN_WB_CONTEXT) #define PLUGIN_GROUP_PATH "/wb/registry/pluginGroups" #define PLUGIN_LIST_PATH "/wb/registry/plugins" #define TYPE_GROUP_FILE "data/db_datatype_groups.xml" #define SERVER_INSTANCE_LIST "server_instances.xml" #define FILE_CONNECTION_LIST "connections.xml" #define FILE_OTHER_CONNECTION_LIST "other_connections.xml" #define PAPER_LANDSCAPE "landscape" #define PAPER_PORTRAIT "portrait" // Options file. #define OPTIONS_FILE_NAME "wb_options.xml" #define OPTIONS_DOCUMENT_FORMAT "MySQL Workbench Options" #define OPTIONS_DOCUMENT_VERSION "1.0.1" // State file. #define STATE_FILE_NAME "wb_state.xml" #define STATE_DOCUMENT_FORMAT "MySQL Workbench Application State" #define STATE_DOCUMENT_VERSION "1.0.0" // Don't send a given refresh_request unless no new ones arrive in this time. #define UI_REQUEST_THROTTLE 0.3 #define DEFAULT_UNDO_STACK_SIZE 10 // auto-save every 1 minute (default) #define AUTO_SAVE_MODEL_INTERVAL (60) #define AUTO_SAVE_SQLEDITOR_INTERVAL 10 #if defined(_MSC_VER) || defined(__APPLE__) #define HAVE_BUNDLED_MYSQLDUMP #endif using namespace grt; using namespace bec; using namespace wb; using namespace base; static const char *argv0 = NULL; //---------------------------------------------------------------------- #ifndef Constructor____ static base::Mutex option_mutex; static void log_func(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { base::Logger::LogLevel level = base::Logger::LogLevel::Disabled; if (log_level & (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL)) level = base::Logger::LogLevel::Error; else if (log_level & G_LOG_LEVEL_WARNING) level = base::Logger::LogLevel::Warning; else if (log_level & (G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO)) level = base::Logger::LogLevel::Info; else if (log_level & (G_LOG_LEVEL_DEBUG)) level = base::Logger::LogLevel::Debug; base::Logger::log(level, log_domain ? log_domain : "", "%s", std::string(message).append("\n").c_str()); #ifndef _MSC_VER g_log_default_handler(log_domain, log_level, message, user_data); #endif } static grt::ValueRef get_app_option(const std::string &option, WBContext *wb) { base::MutexLock lock(option_mutex); if (option.empty()) return wb->get_wb_options(); if (wb->get_document().is_valid() && wb->get_document()->physicalModels().count()) { grt::DictRef model_options(wb->get_document()->physicalModels().get(0)->options()); if (model_options.get_int("useglobal", 0)) return wb->get_wb_options().get(option); if (model_options.has_key(option)) return model_options.get(option); } return wb->get_wb_options().get(option); } static void set_app_option(const std::string &option, grt::ValueRef value, WBContext *wb) { base::MutexLock lock(option_mutex); if (wb->get_document().is_valid() && wb->get_document()->physicalModels().is_valid() && wb->get_document()->physicalModels().count() > 0) { grt::DictRef model_options(wb->get_document()->physicalModels().get(0)->options()); if (model_options.get_int("useglobal", 0)) { wb->get_wb_options().set(option, value); return; } else if (model_options.has_key(option)) { model_options.set(option, value); return; } } wb->get_wb_options().set(option, value); } //----------------- WBOptions ---------------------------------------------------------------------- static bool parse_loglevel(const std::string &line) { bool ret = false; ret = base::Logger::active_level(line); if (ret) printf("Logger set to level '%s'. '%s'\n", line.c_str(), base::Logger::get_state().c_str()); return ret; } WBOptions::WBOptions(const std::string &appBinaryName) : binaryName(appBinaryName), force_sw_rendering(false), force_opengl_rendering(false), verbose(false), quit_when_done(false), testing(false), init_python(true), full_init(true), logLevelSet(false) { // Allocate this on heap so we silent gcc programOptions = new dataTypes::OptionsList(); logDebug("Creating WBOptions\n"); #if defined(_MSC_VER) || defined(__APPLE__) programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, 'h', "help", "Show help options", [this](const dataTypes::OptionEntry &entry, int *retval) { std::cout << programOptions->getHelp(binaryName); *retval = 0; return false; })); #endif #ifdef _MSC_VER programOptions->addEntry( dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, 0, "swrendering", "Force the diagram canvas to use software rendering instead of OpenGL", [this](const dataTypes::OptionEntry &entry, int *retval) { force_sw_rendering = entry.value.logicalValue; return true; })); #elif __APPLE__ #else programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, 0, "force-sw-render", "Force Xlib rendering", [this](const dataTypes::OptionEntry &entry, int *retval) { force_sw_rendering = entry.value.logicalValue; return true; })); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, 0, "force-opengl-render", "Force OpenGL rendering", [this](const dataTypes::OptionEntry &entry, int *retval) { force_opengl_rendering = entry.value.logicalValue; return true; })); #endif auto func = [this](const dataTypes::OptionEntry &entry, int *retval) { if (!entry.value.textValue.empty()) { open_at_startup_type = entry.longName; if (entry.longName == "query" || entry.longName == "admin") open_connection = entry.value.textValue; else open_at_startup = entry.value.textValue; } return true; }; programOptions->addEntry( dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "query", "Open a query tab and ask for connection if nothing is specified.\n" "If named connection is specified it will be opened,\n" "else connection will be created based on the given connection string,", func, "<connection>|<connection string>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "admin", "Open a administration tab to the named instance", func, "<instance>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "model", "Open the given EER model file", func, "<model file>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, "upgrade-mysql-dbs", "Open a migration wizard tab", [this](const dataTypes::OptionEntry &entry, int *retval) { if (entry.value.logicalValue) open_at_startup_type = entry.longName; return true; })); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, "migration", "Open a migration wizard tab", [this](const dataTypes::OptionEntry &entry, int *retval) { if (entry.value.logicalValue) open_at_startup_type = entry.longName; return true; })); programOptions->addEntry(dataTypes::OptionEntry( dataTypes::OptionArgumentType::OptionArgumentText, 0, "script", "Open the given SQL file in an connection, best in conjunction with a query parameter", func, "<sql file>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "run-script", "Execute Python code from a file", [this](const dataTypes::OptionEntry &entry, int *retval) { if (!entry.value.textValue.empty()) { run_language = "python"; open_at_startup_type = "run-script"; open_at_startup = entry.value.textValue; } return true; }, "<file>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "run", "Execute the given Python code", [this](const dataTypes::OptionEntry &entry, int *retval) { if (!entry.value.textValue.empty()) run_at_startup = entry.value.textValue; return true; }, "<code>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "run-python", "Execute Python code from a file", [this](const dataTypes::OptionEntry &entry, int *retval) { if (!entry.value.textValue.empty()) { run_language = "python"; run_at_startup = entry.value.textValue; } return true; }, "<code>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, "quit-when-done", "Quit Workbench when the script is done", [this](const dataTypes::OptionEntry &entry, int *retval) { quit_when_done = entry.value.logicalValue; return true; })); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, "log-to-stderr", "Also log to stderr", [](const dataTypes::OptionEntry &entry, int *retval) { Logger::log_to_stderr(true); return true; })); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "log-level", "Valid levels are: error, warning, info, debug1, debug2, debug3", [](const dataTypes::OptionEntry &entry, int *retval) { if (!parse_loglevel(entry.value.textValue)) { printf("Unable to parse log level value %s\n", entry.value.textValue.c_str()); *retval = 1; return false; } return true; }, "<level>")); programOptions->addEntry(dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentLogical, 'v', "verbose", "Enable diagnostics output", [this](const dataTypes::OptionEntry &entry, int *retval) { verbose = entry.value.logicalValue; return true; })); programOptions->addEntry(dataTypes::OptionEntry( dataTypes::OptionArgumentType::OptionArgumentLogical, "version", "Show Workbench version number and exit", [](const dataTypes::OptionEntry &entry, int *retval) { if (entry.value.logicalValue) { const char *type = APP_EDITION_NAME; if (strcmp(APP_EDITION_NAME, "Community") == (0)) // Extra parens to silence warning. type = "CE"; printf("MySQL Workbench %s (%s) %i.%i.%i %s build %i\n", type, APP_LICENSE_TYPE, APP_MAJOR_NUMBER, APP_MINOR_NUMBER, APP_RELEASE_NUMBER, APP_RELEASE_TYPE, APP_BUILD_NUMBER); *retval = 0; return false; } else return true; })); programOptions->addEntry( dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "configdir", "Specify configuration directory location, default is platform specific.", [this](const dataTypes::OptionEntry &entry, int *retval) { if (!entry.value.textValue.empty()) { printf("Using %s as data directory.\n", entry.value.textValue.c_str()); user_data_dir = entry.value.textValue; } return true; }, "<path>")); programOptions->addEntry( dataTypes::OptionEntry(dataTypes::OptionArgumentType::OptionArgumentText, "open", "Open the given file at startup (deprecated, use script, model etc.)", [this](const dataTypes::OptionEntry &entry, int *retval) { if (!entry.value.textValue.empty()) { printf( "Note: the \"open\" parameter is deprecated and will be removed in a future version" " of MySQL Workbench\n"); open_at_startup = entry.value.textValue; } return true; })); } WBOptions::~WBOptions() { delete programOptions; } void WBOptions::analyzeCommandLineArguments() { auto entry = programOptions->getEntry("log-level"); if (entry->value.textValue.empty()) { const char *log_setting = getenv("WB_LOG_LEVEL"); if (log_setting == NULL) { #if defined(_DEBUG) || defined(ENABLE_DEBUG) log_setting = "debug2"; #else log_setting = "info"; #endif } else logLevelSet = true; std::string level = base::tolower(log_setting); base::Logger::active_level(level); } else { logInfo("Logger set to level '%s'\n", base::Logger::active_level().c_str()); base::Logger::setLogLevelSpecifiedByUser(); } // Get last path and use it if (!programOptions->pathArgs.empty()) open_at_startup = programOptions->pathArgs.back(); } //----------------- WBContext ---------------------------------------------------------------------- extern void register_all_metaclasses(); WBContext::WBContext(bool verbose) : _frontendCallbacks(nullptr) { logDebug("Creating WBContext\n"); _asked_for_saving = false; _model_context = nullptr; _sqlide_context = new WBContextSQLIDE(); _file = nullptr; _save_point = nullptr; _tunnel_manager = nullptr; _model_import_file = nullptr; g_log_set_handler(NULL, (GLogLevelFlags)0xfffff, log_func, this); // register GRT object class implementations if (grt::GRT::get()->metaclassesNeedRegister()) { register_all_metaclasses(); } _user_interaction_blocked = 0; block_user_interaction(true); _send_messages_to_shell = true; _initialization_finished = false; _attachments_changed = false; bec::GRTManager::get()->setVerbose(verbose); bec::GRTManager::get()->set_app_option_slots(std::bind(get_app_option, std::placeholders::_1, this), std::bind(set_app_option, std::placeholders::_1, std::placeholders::_2, this)); bec::GRTManager::get()->update_plugin_arguments_pool = std::bind(&WBContext::update_plugin_arguments_pool, this, std::placeholders::_1); bec::GRTManager::get()->set_timeout_request_slot( std::bind(&WBContext::request_refresh, this, RefreshTimer, "", static_cast<NativeHandle>(0))); NotificationCenter::get()->add_observer(this, "GNDocumentOpened"); // register interface classes register_interfaces(); _clipboard = new bec::Clipboard(); bec::GRTManager::get()->set_clipboard(_clipboard); scoped_connect(grt::GRT::get()->get_undo_manager()->signal_changed(), std::bind(&WBContext::request_refresh, this, RefreshDocument, "", static_cast<NativeHandle>(0))); if (getenv("DEBUG_UNDO")) grt::GRT::get()->get_undo_manager()->enable_logging_to(&std::cout); _plugin_manager = bec::GRTManager::get()->get_plugin_manager(); _plugin_manager->set_registry_paths(PLUGIN_LIST_PATH, PLUGIN_GROUP_PATH); // create and register the module for Workbench stuff _workbench = grt::GRT::get()->get_native_module<WorkbenchImpl>(); _workbench->set_context(this); _components.push_back(new WBComponentBasic(this)); _components.push_back(new WBComponentPhysical(this)); _components.push_back(new WBComponentLogical(this)); } WBContext::~WBContext() { for(auto const &it: _messageHandlerList) { grt::GRT::get()->removeMessageHandler(it); } _messageHandlerList.clear(); NotificationCenter::get()->remove_observer(this); logDebug("Destroying WBContext\n"); //{ // workbench_WorkbenchRef app(get_root()); // app.options().unref_tree(); // app.registry().unref_tree(); // app.info().unref_tree(); // app.unref_tree(); //} delete _clipboard; _clipboard = 0; // delete _plugin_manager; this is deleted by the GRT, since its a module _frontendCallbacks = nullptr; closeModelFile(); // unset the log handler user data as the logger will be deleted // TODO: no longer needed since we have a static logger now. // g_log_set_handler(NULL, (GLogLevelFlags)0xfffff, log_func, NULL); std::vector<WBComponent *>::iterator it = _components.begin(); std::vector<WBComponent *>::const_iterator last = _components.end(); for (; last != it; ++it) { delete *it; *it = 0; } delete _sqlide_context; _sqlide_context = 0; } #endif // Constructor____ #ifndef Components____ WBComponent *WBContext::get_component_named(const std::string &name) { FOREACH_COMPONENT(_components, iter) if ((*iter)->get_name() == name) return (*iter); return 0; } void WBContext::foreach_component(const std::function<void(WBComponent *)> &slot) { FOREACH_COMPONENT(_components, iter) slot(*iter); } WBComponent *WBContext::get_component_handling(const model_ObjectRef &object) { FOREACH_COMPONENT(_components, iter) if ((*iter)->handles_figure(object)) return *iter; return 0; } #endif // Components____ //-------------------------------------------------------------------------------------------------- bec::UIForm *WBContext::get_active_form() { return wb::WBContextUI::get()->get_active_form(); } //-------------------------------------------------------------------------------------------------- bool wb::WBContext::is_commercial() { std::string edition = base::tolower(get_root()->info()->edition()); return (edition == "commercial") || (edition == "development"); } //-------------------------------------------------------------------------------------------------- bec::UIForm *WBContext::get_active_main_form() { return wb::WBContextUI::get()->get_active_main_form(); } void WBContext::finalize() { // Stop any scheduled events, animations etc. before continuing. ThreadedTimer::stop(); //_signal_app_closing(); NotificationCenter::get()->send("GNAppClosing", 0); do_close_document(true); // don't save state if initialization isn't finished at this time (otherwise we're probably // quitting before all the old state was loaded and writing stuff back would just reset everything) if (_initialization_finished) { save_app_options(); save_app_state(); save_connections(); } bec::GRTManager::get()->get_dispatcher()->shutdown(); if (_tunnel_manager) { delete _tunnel_manager; _tunnel_manager = nullptr; } if (_model_context) { delete _model_context; _model_context = nullptr; } // clear slots to managed bec::GRTManager::get()->set_status_slot(std::function<void(std::string)>{}); _plugin_manager->set_gui_plugin_callbacks(PluginManagerImpl::OpenGUIPluginSlot{}, PluginManagerImpl::ShowGUIPluginSlot{}, PluginManagerImpl::CloseGUIPluginSlot{}); } void WBContext::block_user_interaction(bool flag) { // Use a mutext to protect this whole function base::RecMutexLock _lock(_block_user_interaction_mutex); if (flag) _user_interaction_blocked++; else { if (_user_interaction_blocked > 0) _user_interaction_blocked--; } if (_user_interaction_blocked == 1 && flag) { if (_frontendCallbacks && _frontendCallbacks->lock_gui) _frontendCallbacks->lock_gui(true); } else if (_frontendCallbacks && _user_interaction_blocked == 0 && !flag) { if (_frontendCallbacks->lock_gui) _frontendCallbacks->lock_gui(false); } } #ifndef Setup____ //-------------------------------------------------------------------------------- bool WBContext::opengl_rendering_enforced() { return _force_opengl_rendering; } //-------------------------------------------------------------------------------------------------- bool WBContext::software_rendering_enforced() { bool result = false; if (!_force_opengl_rendering) { // See if the current adapter is one of those that we don't want to enable OpenGL by default. static std::string excluded_adapters[] = { "965", // Mobile Intel(R) 965 Express chip set Family "82945G" // Intel(R) 82945G Express chip set Family }; grt::StringListRef arguments(grt::Initialized); std::string videoAdapter = grt::StringRef::cast_from(_workbench->call_function("getVideoAdapter", arguments)); for (unsigned int i = 0; i < sizeof(excluded_adapters) / sizeof(excluded_adapters[0]); i++) if (videoAdapter.find(excluded_adapters[i]) != std::string::npos) { result = true; break; } } // Setting from preferences. if (get_root()->options()->options().get_int("workbench:ForceSWRendering", 0) != 0) result = true; // Setting from command line overrides the preferences setting. if (_force_sw_rendering) result = true; return result; } //-------------------------------------------------------------------------------------------------- // Need to define a local function wrapper for the show_error call as sigc templates can neither handle // overloaded functions nor default parameters. bool WBContext::show_error(const std::string &title, const std::string &message) { logError("%s", (message + '\n').c_str()); return mforms::Utilities::show_error(title, message, _("Close")) != 0; } //-------------------------------------------------------------------------------------------------- /** * Actually triggers the password find or user querying process for the given service. The given account * can be empty if the connection has no user set so we must be able to return it like the password. * Both, account as well as password must be allocated by the caller and passed on via references as * this function is called via the dispatcher. * For the same reason is the return value passed back as pointer, even though it's a bool. */ void *WBContext::do_request_password(const std::string &title, const std::string &service, bool force_asking, std::string *account, std::string *password) { bool ret = false; try { ret = mforms::Utilities::find_or_ask_for_password(title, service, *account, force_asking, *password); } catch (const std::exception &e) { show_error("Error Looking Up Password", e.what()); } return (void *)ret; } void *WBContext::do_find_connection_password(const std::string &hostId, const std::string &username, std::string *ret_password) { bool ret = false; try { ret = mforms::Utilities::find_password(hostId, username, *ret_password); } catch (const std::exception &e) { show_error("Error Looking Up Password", e.what()); } return (void *)ret; } bool WBContext::find_connection_password(const db_mgmt_ConnectionRef &conn, std::string &password) { /* return execute_in_main_thread<bool>("find_password", std::bind(&WBContext::do_find_connection_password, this, conn->hostIdentifier().c_str(), conn->parameterValues().get_string("userName").c_str(), &password));*/ void *ret = mforms::Utilities::perform_from_main_thread( std::bind(&WBContext::do_find_connection_password, this, conn->hostIdentifier(), conn->parameterValues().get_string("userName"), &password)); if (ret) return true; return false; } // throws grt::user_cancelled std::string WBContext::request_connection_password(const db_mgmt_ConnectionRef &conn, bool reset_password) { std::string password_tmp; std::string user_tmp = conn->parameterValues().get_string("userName"); void *ret = mforms::Utilities::perform_from_main_thread( std::bind(&WBContext::do_request_password, this, _("Connect to MySQL Server"), conn->hostIdentifier(), reset_password, &user_tmp, &password_tmp)); if (ret) return password_tmp; throw grt::user_cancelled("Canceled by user"); } bool WBContext::init_(WBFrontendCallbacks *callbacks, WBOptions *options) { logInfo("WbContext::init\n"); grt::ValueRef res; _force_opengl_rendering = options->force_opengl_rendering; _force_sw_rendering = options->force_sw_rendering; // Set callbacks _frontendCallbacks = callbacks; bec::GRTManager::get()->set_status_slot(_frontendCallbacks->show_status_text); grt::GRT::get()->setTesting(options->testing); // Already blocked (when constructed), but call it again so that lock_gui() gets called now. // Unlock not before a new document is created (see new_document()). block_user_interaction(true); block_user_interaction(false); // decrement the block counter to be back at 1 // set the path for the options dictionary that modules should use to store their stuff grt::GRT::get()->set_global_module_data_path("/wb/customData"); grt::GRT::get()->set_document_module_data_path("/wb/doc/customData"); mforms::App::get()->setBaseDir(options->basedir); bec::GRTManager::get()->set_datadir(options->basedir); bec::GRTManager::get()->set_basedir(options->basedir); bec::GRTManager::get()->set_user_datadir(options->user_data_dir); bec::GRTManager::get()->cleanup_tmp_dir(); mforms::App::get()->set_user_data_folder_path(options->user_data_dir); mforms::Utilities::set_message_answers_storage_path( base::makePath(options->user_data_dir, "mforms_remembered_dialog_responses")); bec::IconManager::get_instance()->set_basedir(options->basedir); // Setup image search paths std::string path; static const char *dirs[] = {"images", "images/icons", "images/grt", "images/grt/structs", "images/png", #ifdef _MSC_VER "images/home", #endif "images/ui", "images/sql", "images/sql/mac", "", NULL}; for (unsigned int i = 0; dirs[i] != NULL; i++) { mdc::ImageManager::get_instance()->add_search_path(base::makePath(options->basedir, dirs[i])); bec::IconManager::get_instance()->add_search_path(dirs[i]); } std::string loader_module_path = options->plugin_search_path; _tunnel_manager = new TunnelManager(); { // Set location of cdbc drivers and module loader (python etc) DLLs sql::DriverManager *dbc_driver_man = sql::DriverManager::getDriverManager(); // set the tunnel factory if (_tunnel_manager) dbc_driver_man->setTunnelFactoryFunction( std::bind(&TunnelManager::createTunnel, _tunnel_manager, std::placeholders::_1)); // set function to request connection password for user dbc_driver_man->setPasswordFindFunction( std::bind(&WBContext::find_connection_password, this, std::placeholders::_1, std::placeholders::_2)); dbc_driver_man->setPasswordRequestFunction( std::bind(&WBContext::request_connection_password, this, std::placeholders::_1, std::placeholders::_2)); mforms::Utilities::add_driver_shutdown_callback(std::bind(&sql::DriverManager::thread_cleanup, dbc_driver_man)); #ifdef _MSC_VER dbc_driver_man->set_driver_dir(options->basedir); #elif defined(__APPLE__) dbc_driver_man->set_driver_dir(options->cdbc_driver_search_path); #else if (getenv("DBC_DRIVER_PATH")) dbc_driver_man->set_driver_dir(getenv("DBC_DRIVER_PATH")); else dbc_driver_man->set_driver_dir(options->cdbc_driver_search_path); #endif } // Set callbacks _plugin_manager->set_gui_plugin_callbacks(callbacks->open_editor, callbacks->show_editor, callbacks->hide_editor); pushMessageHandler(new grt::SlotHolder(std::bind(&WBContext::handle_message, this, std::placeholders::_1))); // Set options _datadir = options->basedir; _user_datadir = options->user_data_dir; std::string modules_path; std::string libraries_path; std::string user_modules_path; std::string user_scripts_path; std::string user_libraries_path; user_modules_path = base::makePath(options->user_data_dir, "modules"); user_scripts_path = base::makePath(options->user_data_dir, "scripts"); user_libraries_path = base::makePath(options->user_data_dir, "libraries"); modules_path = options->module_search_path; #ifdef _MSC_VER modules_path = base::pathlistPrepend(modules_path, "."); #endif libraries_path = options->library_search_path; // create user_data_dir/modules dir if it does not exist yet if (!g_file_test(user_modules_path.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) g_mkdir_with_parents(user_modules_path.c_str(), 0700); if (!g_file_test(user_scripts_path.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) g_mkdir_with_parents(user_scripts_path.c_str(), 0700); if (!g_file_test(user_libraries_path.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) g_mkdir_with_parents(user_libraries_path.c_str(), 0700); bec::GRTManager::get()->set_search_paths( modules_path, base::pathlistPrepend(options->struct_search_path, base::makePath(options->basedir, "structs")), libraries_path); bec::GRTManager::get()->set_user_extension_paths(user_modules_path, user_libraries_path, user_scripts_path); std::list<std::string> exts; exts.push_back(".grt"); bec::GRTManager::get()->set_module_extensions(exts); // init major parts of the app get_sqlide_context()->init(); _frontendCallbacks->show_status_text(_("Initializing GRT...")); // Initialize GRT Manager. bec::GRTManager::get()->initialize(options->init_python, loader_module_path); bec::GRTManager::get()->get_shell()->set_save_directory(options->user_data_dir); bec::GRTManager::get()->get_shell()->set_saves_history(200); // limit history to 200 commands // add some handy shortcuts to shell tree bookmarks list bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/doc/physicalModels/0/catalog"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/doc/physicalModels/0/catalog/schemata/0/tables"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/doc/physicalModels/0/diagrams/0"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/doc/physicalModels/0/diagrams/0/figures"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/sqlEditors"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/migration"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/migration/sourceCatalog"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/migration/targetCatalog"); bec::GRTManager::get()->get_shell()->add_grt_tree_bookmark("/wb/registry/plugins"); grt::GRT::get()->send_output(strfmt("Looking for user plugins in %s\n", user_modules_path.c_str())); _frontendCallbacks->show_status_text(_("Initializing Workbench components...")); res = setup_context_grt(options); if (res.is_valid() && *grt::IntegerRef::cast_from(res) != 1) show_error(_("Initialization Error"), _("There was an error during initialization of Workbench, some functionality may not work.")); logInfo("System info:\n %s\n", _workbench->getSystemInfo(true).c_str()); // The GRT shell is now created on demand. No need to do this in advance (which might get us into // trouble on Windows, because the main window doesn't exist yet). try { bec::GRTManager::get()->initialize_shell(get_root()->options()->options().get_string("grtshell:ShellLanguage", "python")); } catch (std::exception &) { bec::GRTManager::get()->initialize_shell("python"); } get_root()->options()->signal_dict_changed()->connect(std::bind( &WBContext::option_dict_changed, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); _send_messages_to_shell = false; return true; } //-------------------------------------------------------------------------------------------------- static bool output_to_stdout(const grt::Message &msg, void *sender) { if (msg.type == grt::OutputMsg) { printf("%s", msg.text.c_str()); fflush(stdout); } else { fprintf(stderr, "%s", msg.format().c_str()); } return true; } void WBContext::warnIfRunningOnUnsupportedOS() { std::string os = get_local_os_name(); logDebug2("get_local_os_name() returned '%s'\n", os.c_str()); if (!_workbench->isOsSupported(os)) { mforms::Utilities::show_message_and_remember( "Unsupported Operating System", "You are running Workbench on an unsupported operating system. " "While it may work for you just fine, it wasn't designed to run on your platform. " "Please keep this in mind if you run into problems.", "OK", "", "", "wb.supported_os_check.suppress_warning", "Don't show this message again"); } } void WBContext::init_finish_(WBOptions *options) { // initialize plugins that have a initializer (start with builtins and then go through user plugins) // Initialization to be done ONLY when WB is first started. // This point is also reached when i.e. a document was opened by double clicking and a WB instance was already open // full_init is used to identify the initialization mode. if (options->full_init) { const std::vector<grt::Module *> &modules(grt::GRT::get()->get_modules()); grt::BaseListRef args(true); for (std::vector<grt::Module *>::const_iterator it = modules.begin(); it != modules.end(); ++it) { if ((*it)->has_function("initialize0")) { logDebug("Calling %s.initialize0()...\n", (*it)->name().c_str()); try { (*it)->call_function("initialize0", args); } catch (std::exception &e) { logError("Error calling %s.initialize0(): %s\n", (*it)->name().c_str(), e.what()); } } } for (std::vector<grt::Module *>::const_iterator it = modules.begin(); it != modules.end(); ++it) { if ((*it)->has_function("initialize")) { logDebug("Calling %s.initialize()...\n", (*it)->name().c_str()); try { (*it)->call_function("initialize", args); } catch (std::exception &e) { logError("Error calling %s.initialize(): %s\n", (*it)->name().c_str(), e.what()); } } } } // open initial document when GUI init finishes std::string initial_file; if (!options->open_at_startup.empty()) initial_file = options->open_at_startup; if (initial_file.empty() && get_wb_options().get_int("workbench.AutoReopenLastModel", 0)) { grt::StringListRef recentFiles(get_root()->options()->recentFiles()); for (size_t i = 0; i < recentFiles.count(); i++) { initial_file = recentFiles.get(i); if (g_str_has_suffix(initial_file.c_str(), ".mwb")) break; } if (!g_str_has_suffix(initial_file.c_str(), ".mwb")) initial_file.clear(); } if (!initial_file.empty()) { if (g_str_has_suffix(initial_file.c_str(), ".mwb") || options->open_at_startup_type == "model") open_document(initial_file); else if (g_str_has_suffix(initial_file.c_str(), ".sql") || g_str_has_suffix(initial_file.c_str(), ".dbquery") || options->open_at_startup_type == "query") options->open_at_startup_type = "query"; else if (g_str_has_suffix(initial_file.c_str(), ".py") && options->open_at_startup_type == "run-script") options->open_at_startup_type = "run-script"; else if (g_str_has_suffix(initial_file.c_str(), ".py") && options->open_at_startup_type == "script") { options->open_at_startup_type = "run-script"; logWarning("--script option is meant for SQL scripts, assuming --run-script was meant instead\n"); } else logError("Unknown file type %s\n", initial_file.c_str()); } block_user_interaction(false); // SSH tunnel manager is created on first creation of a connection. _frontendCallbacks->show_status_text(_("Ready.")); // Avoid our runtime tests to lock up when a modal warning dialog is displayed. if (options->open_at_startup_type != "run-script") warnIfRunningOnUnsupportedOS(); try { // execute action requested from command line if (options->open_at_startup_type == "query" || options->open_at_startup_type == "admin") { std::string connection_name = options->open_connection; db_mgmt_ConnectionRef conn; if (!connection_name.empty()) { conn = find_named_object_in_list(get_root()->rdbmsMgmt()->storedConns(), connection_name, true); if (!conn.is_valid()) { if (options->open_at_startup_type == "admin") // if --admin is specified, look for instances too, for // backwards compatibility with Windows Notifier { db_mgmt_ServerInstanceRef instance( find_named_object_in_list(get_root()->rdbmsMgmt()->storedInstances(), connection_name, true)); if (!instance.is_valid()) logError("No instance or connection named %s was found\n", connection_name.c_str()); else { logError("Falling back to instance called %s for --admin option\n", connection_name.c_str()); conn = instance->connection(); } } else // else we will try to parse the param as connection string { grt::BaseListRef args(true); args.ginsert(grt::StringRef(options->open_connection)); grt::ValueRef val = grt::GRT::get()->call_module_function("PyWbUtils", "connectionFromString", args); if (db_mgmt_ConnectionRef::can_wrap(val)) { db_mgmt_ConnectionRef tmp = db_mgmt_ConnectionRef::cast_from(val); if (!tmp.is_valid()) logInfo("Connection string was invalid...\n"); else { logDebug("Found connection string [%s]...\n", tmp->name().c_str()); conn = tmp; } } } } } // XXX: review this, if connection_name is empty we may try to open an invalid connection. if (conn.is_valid() || connection_name.empty()) { logInfo("Opening SQL Editor window to '%s'...\n", connection_name.c_str()); try { if (options->open_at_startup_type == "admin") add_new_admin_window(conn); else { if (conn.is_valid()) add_new_query_window(conn); else { if (!options->open_at_startup.empty()) open_script_file(options->open_at_startup); else add_new_query_window(); } } } catch (std::exception &e) { logError("Error opening SQL editor to '%s': %s\n", connection_name.c_str(), e.what()); throw; } } else { std::string message = strfmt(_("Invalid connection name '%s' given for --query"), connection_name.c_str()); logWarning("%s\n", message.c_str()); throw std::runtime_error(message); } } else if (options->open_at_startup_type == "run-script") { std::string script_file = options->open_at_startup; pushMessageHandler(new grt::SlotHolder(std::bind(output_to_stdout, std::placeholders::_1, std::placeholders::_2))); bec::GRTManager::get()->get_shell()->run_script_file(script_file); grt::GRT::get()->popMessageHandler(); } else if (options->open_at_startup_type == "migration") { logInfo("Opening Migration Wizard...\n"); add_new_plugin_window("wb.migration.open", "Migration Wizard"); } else if (options->open_at_startup_type == "upgrade-mysql-dbs") { logInfo("Opening Database Copy Wizard...\n"); add_new_plugin_window("wb.db.copy.open", "Database Copy Wizard"); } if (!options->run_at_startup.empty()) { std::string lang = options->run_language; if (lang.empty()) { lang = "python"; pushMessageHandler(new grt::SlotHolder(std::bind(output_to_stdout, std::placeholders::_1, std::placeholders::_2))); } bec::GRTManager::get()->get_shell()->run_script(options->run_at_startup, lang); grt::GRT::get()->popMessageHandler(); } } catch (std::runtime_error &e) { // Errors from script execution are logged to the command line so we don't need to print out // another message. Notify user about it in the UI if we are supposed to not quit when done. if (!options->quit_when_done) { show_error(_("Error executing startup action"), _("An error occurred while " "the application executed the given startup action. The returned error is:\n\n") + std::string(e.what()) + _("\n\nSee also the output window.")); } } _initialization_finished = true; if (options->quit_when_done && (!options->run_at_startup.empty() || options->open_at_startup_type == "script" || options->open_at_startup_type == "run-script")) _frontendCallbacks->quit_application(); } //-------------------------------------------------------------------------------------------------- void WBContext::pushMessageHandler(grt::SlotHolder *slot) { _messageHandlerList.push_back(slot); grt::GRT::get()->pushMessageHandler(slot); } //-------------------------------------------------------------------------------------------------- bool WBContext::handle_message(const grt::Message &msg) { // No need to log messages here. That happens already in the grt manager. if (_send_messages_to_shell) { bec::GRTManager::get()->get_shell()->handle_msg(msg); return true; } else { if (bec::GRTManager::get()->get_messages_list()) { bec::GRTManager::get()->get_messages_list()->handle_message(msg); return true; } } return false; } //-------------------------------------------------------------------------------------------------- void WBContext::init_rdbms_modules() { logDebug("Initializing rdbms modules\n"); // Init MySQL first. grt::Module *module = grt::GRT::get()->get_module("DbMySQL"); if (!module) throw std::logic_error("DbMySQL module not found"); grt::BaseListRef args(true); module->call_function("initializeDBMSInfo", args); // Will done prior to Migration init, to speed up app startup. //grt::GRT::get()->initializeOtherRDBMS(); } grt::ValueRef WBContext::setup_context_grt(WBOptions *options) { std::shared_ptr<grt::internal::Unserializer> unserializer = grt::GRT::get()->get_unserializer(); // init the GRT tree nodes, set default options init_grt_tree(options, unserializer); // Load last application state. This will only load it into the grt tree. // Components that have stored their settings will later read those values and reapply them. // This must be done as early as possible to provide all other parts their last saved state // when they are loading/initializing. load_app_state(unserializer); init_plugin_groups_grt(options); init_plugins_grt(options); // Initialize RDBMS specific modules. must happen before connections are loaded. init_rdbms_modules(); // Table templates can be initialized only after rdbms info because it needs column datatypes. init_templates(); for(auto &it: _components) { it->setup_context_grt(options); } // App options must be loaded after everything else is initialized. load_app_options(false); // Rescan plugins so that list of disabled plugins is applied. _plugin_manager->rescan_plugins(); return grt::IntegerRef(1); } void WBContext::init_templates() { // Default table templates list grt::DictRef options(get_root()->options()->options()); if (!options.has_key("TableTemplates")) { grt::ListRef<db_Table> templates = grt::ListRef<db_Table>::cast_from(grt::GRT::get()->unserialize(base::makePath(get_datadir(), "data/table_templates.xml"))); options.set("TableTemplates", templates); } } void WBContext::init_grt_tree(WBOptions *options, std::shared_ptr<grt::internal::Unserializer> unserializer) { grt::DictRef root(true); workbench_WorkbenchRef app(grt::Initialized); root.set("wb", app); // setup application subtree { app_InfoRef info(grt::Initialized); GrtVersionRef version(grt::Initialized); info->owner(app); version->majorNumber(APP_MAJOR_NUMBER); version->minorNumber(APP_MINOR_NUMBER); version->releaseNumber(APP_RELEASE_NUMBER); version->buildNumber(APP_BUILD_NUMBER); version->status(1); info->name("MySQL Workbench"); info->version(version); info->copyright("Oracle and/or its affiliates"); info->license(APP_LICENSE_TYPE); info->edition(APP_EDITION_NAME); app->info(info); } { app_OptionsRef options(grt::Initialized); options->owner(app); append_contents(options->paperTypes(), get_paper_types(unserializer)); set_default_options(options->options()); app->options(options); } { app_RegistryRef registry(grt::Initialized); registry->owner(app); registry->appDataDirectory(bec::GRTManager::get()->get_basedir()); registry->appExecutablePath(argv0 ? argv0 : ""); app->registry(registry); } // ------------------ db_mgmt_ManagementRef mgmt_info(grt::Initialized); // load datatype groups from XML ListRef<db_DatatypeGroup> grouplist; grouplist = ListRef<db_DatatypeGroup>::cast_from( grt::GRT::get()->unserialize(base::makePath(options->basedir, TYPE_GROUP_FILE), unserializer)); for (size_t c = grouplist.count(), i = 0; i < c; i++) { grouplist[i]->owner(mgmt_info); mgmt_info->datatypeGroups().insert(grouplist[i]); } app->rdbmsMgmt(mgmt_info); grt::GRT::get()->set_root(root); } void WBContext::init_plugin_groups_grt(WBOptions *options) { struct group_def { const char *category; const char *name; const char *accessibilityName; } std_groups[] = {{"Database", "Database", "Database"}, {"Catalog", "Editors", "Editors"}, {"Application", "Workbench", "Workbench"}, {"Model", "Validation", "Validation"}, {"Model", "Export", "Export"}, {"Home", "Home", "Home"}, {"Home", "Home/Connections", "Connections"}, {"Home", "Home/ModelFiles", "Model Files"}, {"Home", "Home/Instances", "Instances"}, {"Model", "Menu/Text", "Model Text"}, {"SQLEditor", "Menu/Text", "SQL Editor Text"}, {"Model", "Menu/Model", "Model"}, {"Model", "Menu/Utilities", "Utilities"}, {"Catalog", "Menu/Catalog", "Catalog"}, {"Catalog", "Menu/Objects", "Objects"}, {"Database", "Menu/Database", "Database"}, {"Utilities", "Filter", "Filter"}, {"Utilities", "Menu/Utilities", "Utilities"}, {"SQLEditor", "Menu/SQL/Editor", "SQL Editor"}, {"SQLEditor", "Menu/SQL/Script", "SQL Script"}, {"SQLEditor", "Menu/SQL/Utilities", "SQL Utilities"}, {"Others", "Menu/Ungrouped", "Others"}}; std::map<std::string, app_PluginGroupRef> groups; grt::ListRef<app_PluginGroup> group_list = grt::ListRef<app_PluginGroup>::cast_from(grt::GRT::get()->get(PLUGIN_GROUP_PATH)); for (unsigned int i = 0; i < sizeof(std_groups) / sizeof(group_def); i++) { app_PluginGroupRef group(grt::Initialized); group->category(std_groups[i].category); group->name(std_groups[i].name); group->accessibilityName(std_groups[i].accessibilityName); group_list.insert(group); groups[std_groups[i].name] = group; } } void WBContext::init_plugins_grt(WBOptions *options) { std::map<std::string, bool> scanned_dir_list; std::list<std::string> exts; #if defined(_MSC_VER) exts.push_back(".wbp.be"); #endif exts.push_back(".wbp"); // scan user plugins std::string plugin_path = normalize_path(base::makePath(options->user_data_dir, "plugins")); grt::GRT::get()->send_output(strfmt("Looking for user plugins in %s\n", plugin_path.c_str())); bec::GRTManager::get()->do_scan_modules(plugin_path, exts, false); scanned_dir_list[plugin_path] = true; std::vector<std::string> paths = base::split(options->plugin_search_path, G_SEARCHPATH_SEPARATOR_S); for (size_t c = paths.size(), i = 0; i < c; i++) { if (scanned_dir_list.find(paths[i]) == scanned_dir_list.end() && g_file_test(paths[i].c_str(), G_FILE_TEST_IS_DIR)) { std::string full_path = normalize_path(base::makePath(options->user_data_dir, paths[i])); if (scanned_dir_list.find(full_path) == scanned_dir_list.end()) { grt::GRT::get()->send_output(strfmt("Looking for plugins in %s\n", full_path.c_str())); bec::GRTManager::get()->do_scan_modules(paths[i], exts, false); } scanned_dir_list[paths[i]] = true; } } _plugin_manager->rescan_plugins(); ValidationManager::scan(); } void WBContext::init_properties_grt(workbench_DocumentRef &doc) { app_DocumentInfoRef info(grt::Initialized); info->name("Properties"); info->owner(doc); info->caption("New Model"); info->version("1.0"); info->project("Name of the project"); info->dateCreated(fmttime(0, DATETIME_FMT)); info->dateChanged(fmttime(0, DATETIME_FMT)); info->author(g_get_real_name()); doc->info(info); } static void set_default(grt::DictRef dict, const char *option, int value) { if (!dict.has_key(option)) dict.gset(option, value); } static void set_default(grt::DictRef dict, const char *option, const std::string &value) { if (!dict.has_key(option) || option[0] == '@') dict.gset(option, value); } /** **************************************************************************** * @brief Sets Workbench specific options * * To get a configuration option, use GRTManager::get_app_option() * **************************************************************************** */ void WBContext::set_default_options(grt::DictRef options) { set_default(options, "workbench:ForceSWRendering", 0); set_default(options, "workbench:OSSHideMissing", 0); set_default(options, "workbench:UndoEntries", DEFAULT_UNDO_STACK_SIZE); set_default(options, "workbench:AutoSaveModelInterval", AUTO_SAVE_MODEL_INTERVAL); set_default(options, "workbench:AutoSaveSQLEditorInterval", AUTO_SAVE_SQLEDITOR_INTERVAL); set_default(options, "workbench.AutoReopenLastModel", 0); set_default(options, "workbench:SaveSQLWorkspaceOnClose", 1); set_default(options, "workbench:InternalSchema", ".mysqlworkbench"); set_default(options, "workbench.physical:DeleteObjectConfirmation", "ask"); set_default(options, "HomeScreen:HeadingMessage", 1); // By default display Welcome Message set_default(options, "grtshell:ShellLanguage", "python"); set_default(options, "@grtshell:ShellLanguage/Items", "python"); // URL of latest versions file (used by version updater) set_default(options, "VersionsFileURL", "http://wb.mysql.com/versions.php"); // SQL parsing options set_default(options, "SqlIdentifiersCS", 1); set_default(options, "SqlMode", ""); set_default(options, "SqlDelimiter", "$$"); set_default(options, "SqlEditor::SyntaxCheck::MaxErrCount", 100); // All editors set_default(options, "Editor:TabIndentSpaces", 0); set_default(options, "Editor:TabWidth", 4); set_default(options, "Editor:IndentWidth", 4); // DB SQL editor set_default(options, "DbSqlEditor:SchemaTreeRestoreState", 1); set_default(options, "DbSqlEditor:CodeCompletionEnabled", 1); set_default(options, "DbSqlEditor:AutoStartCodeCompletion", 1); set_default(options, "DbSqlEditor:CodeCompletionUpperCaseKeywords", 0); set_default(options, "DbSqlEditor:ProgressStatusUpdateInterval", 500); // in ms set_default(options, "DbSqlEditor:KeepAliveInterval", 600); // in seconds set_default(options, "DbSqlEditor:ReadTimeOut", 30); // in seconds set_default(options, "DbSqlEditor:ConnectionTimeOut", 60); // in seconds set_default(options, "DbSqlEditor:MaxQuerySizeToHistory", 65536); set_default(options, "DbSqlEditor:ContinueOnError", 0); // continue running sql script bypassing failed statements set_default(options, "DbSqlEditor:AutocommitMode", 1); // when enabled, each statement will be committed immediately set_default(options, "DbSqlEditor:IsDataChangesCommitWizardEnabled", 1); set_default(options, "DbSqlEditor:ShowSchemaTreeSchemaContents", 1); set_default(options, "DbSqlEditor:SafeUpdates", 1); set_default(options, "DbSqlEditor:ShowWarnings", 1); set_default(options, "DbSqlEditor:ReformatViewDDL", 1); set_default(options, "DbSqlEditor:OnlineDDLAlgorithm", "DEFAULT"); set_default(options, "DbSqlEditor:OnlineDDLLock", "DEFAULT"); set_default(options, "DbSqlEditor:DiscardUnsavedQueryTabs", 0); set_default(options, "DbSqlEditor:SQLCommentTypeForHotkey", "--"); set_default(options, "DbSqlEditor:DisableAutomaticContextHelp", 1); set_default(options, "DbSqlEditor:Reformatter:UpcaseKeywords", 1); set_default(options, "DbSqlEditor::MaxResultsets", 50); // Migration set_default(options, "Migration:ConnectionTimeOut", 60); // in seconds // Recordset set_default(options, "Recordset:FloatingPointVisibleScale", 3); set_default(options, "Recordset:FieldValueTruncationThreshold", 256); set_default(options, "SqlEditor:LimitRows", 1); set_default(options, "SqlEditor:LimitRowsCount", 1000); set_default(options, "SqlEditor:PreserveRowFilter", 1); set_default(options, "SqlEditor:geographicLocationURL", "http://www.openstreetmap.org/?mlat=%LAT%&mlon=%LON%"); // Name templates set_default(options, "PkColumnNameTemplate", "id%table%"); set_default(options, "DefaultPkColumnType", "INT"); set_default(options, "ColumnNameTemplate", "%table%col"); set_default(options, "DefaultColumnType", "VARCHAR(45)"); set_default(options, "FKNameTemplate", "fk_%stable%_%dtable%"); set_default(options, "FKColumnNameTemplate", "%table%_%column%"); set_default(options, "AuxTableTemplate", "%stable%_has_%dtable%"); // Model Defaults set_default(options, "DefaultFigureNotation", "workbench/default"); set_default(options, "DefaultConnectionNotation", "crowsfoot"); set_default(options, "AlignToGrid", 0); set_default(options, "SynchronizeObjectColors", 1); // MySQL Defaults set_default(options, "DefaultTargetMySQLVersion", base::getVersion()); set_default(options, "db.mysql.Table:tableEngine", "InnoDB"); set_default(options, "SqlGenerator.Mysql:SQL_MODE", "ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"); #ifdef HAVE_BUNDLED_MYSQLDUMP set_default(options, "mysqldump", ""); set_default(options, "mysqlclient", ""); #else set_default(options, "mysqldump", "mysqldump"); set_default(options, "mysqlclient", "mysql"); #endif #ifdef _MSC_VER std::string homedir = mforms::Utilities::get_special_folder(mforms::Documents); set_default(options, "dumpdirectory", homedir + "\\dumps"); #else std::string homedir = "~"; set_default(options, "dumpdirectory", homedir + "/dumps"); #endif // FK defaults set_default(options, "db.ForeignKey:deleteRule", "NO ACTION"); set_default(options, "db.ForeignKey:updateRule", "NO ACTION"); // Default Colors set_default(options, "workbench.model.Layer:Color", "#F0F1FE"); set_default(options, "workbench.model.NoteFigure:Color", "#FEFDED"); set_default(options, "workbench.physical.Diagram:DrawLineCrossings", 0); set_default(options, "workbench.physical.ObjectFigure:Expanded", 1); set_default(options, "workbench.physical.TableFigure:ShowColumnTypes", 1); set_default(options, "workbench.physical.TableFigure:ShowColumnFlags", 0); set_default(options, "workbench.physical.TableFigure:MaxColumnTypeLength", 20); set_default(options, "workbench.physical.TableFigure:MaxColumnsDisplayed", 30); set_default(options, "workbench.physical.RoutineGroupFigure:MaxRoutineNameLength", 20); set_default(options, "workbench.physical.TableFigure:Color", "#98BFDA"); set_default(options, "workbench.physical.ViewFigure:Color", "#FEDE58"); set_default(options, "workbench.physical.RoutineGroupFigure:Color", "#98D8A5"); // Default Fonts set_default(options, "workbench.physical.FontSet:Name", "Default (Western)"); set_default(options, "workbench.physical.TableFigure:TitleFont", DEFAULT_FONT_FAMILY " Bold 12"); set_default(options, "workbench.physical.TableFigure:SectionFont", DEFAULT_FONT_FAMILY " Bold 11"); set_default(options, "workbench.physical.TableFigure:ItemsFont", DEFAULT_FONT_FAMILY " 11"); set_default(options, "workbench.physical.ViewFigure:TitleFont", DEFAULT_FONT_FAMILY " Bold 12"); set_default(options, "workbench.physical.RoutineGroupFigure:TitleFont", DEFAULT_FONT_FAMILY " Bold 12"); set_default(options, "workbench.physical.RoutineGroupFigure:ItemsFont", DEFAULT_FONT_FAMILY " 12"); set_default(options, "workbench.physical.Connection:CaptionFont", DEFAULT_FONT_FAMILY " 11"); set_default(options, "workbench.physical.Layer:TitleFont", DEFAULT_FONT_FAMILY " 11"); set_default(options, "workbench.model.NoteFigure:TextFont", DEFAULT_FONT_FAMILY " 11"); #if defined(_MSC_VER) set_default(options, "workbench.general.Resultset:Font", DEFAULT_FONT_FAMILY " 8"); if (get_local_os_name().find("Windows XP") != std::string::npos) { set_default(options, "workbench.general.Editor:Font", DEFAULT_MONOSPACE_FONT_FAMILY_ALT " 10"); set_default(options, "workbench.scripting.ScriptingShell:Font", DEFAULT_MONOSPACE_FONT_FAMILY_ALT " 10"); set_default(options, "workbench.scripting.ScriptingEditor:Font", DEFAULT_MONOSPACE_FONT_FAMILY_ALT " 10"); } else { set_default(options, "workbench.general.Editor:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 10"); set_default(options, "workbench.scripting.ScriptingShell:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 10"); set_default(options, "workbench.scripting.ScriptingEditor:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 10"); } #elif defined(__APPLE__) set_default(options, "workbench.general.Resultset:Font", DEFAULT_FONT_FAMILY " 11"); set_default(options, "workbench.general.Editor:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 13"); set_default(options, "workbench.scripting.ScriptingShell:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 13"); set_default(options, "workbench.scripting.ScriptingEditor:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 13"); #else set_default(options, "workbench.general.Resultset:Font", DEFAULT_FONT_FAMILY " 11"); set_default(options, "workbench.general.Editor:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 11"); set_default(options, "workbench.scripting.ScriptingShell:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 11"); set_default(options, "workbench.scripting.ScriptingEditor:Font", DEFAULT_MONOSPACE_FONT_FAMILY " 11"); #endif std::string colors; colors = "#FFEEEC\n"; colors += "#FEFDED\n"; colors += "#EAFFE5\n"; colors += "#ECFDFF\n"; colors += "#F0F1FE\n"; colors += "#FFEBFA\n"; set_default(options, "workbench.model.Figure:ColorList", colors); colors = "#98BFDA\n"; colors += "#FEDE58\n"; colors += "#98D8A5\n"; colors += "#FE9898\n"; colors += "#FE98FE\n"; colors += "#FFFFFF\n"; set_default(options, "workbench.model.ObjectFigure:ColorList", colors); set_default(options, "@ColorScheme/Items", "System Default:0,Windows 7:1,Windows 8:2,Windows 8 (alternative):3,High Contrast:4"); // Advanced options // Option can't be turned off by default. We've got a situation with connector c++ on windows // if tunnel will get closed just before calling mysql_ping() (keep alive) then ping will try to use invalid // mysql->net structure. set_default(options, "SSH:keepalive", 60); set_default(options, "SSH:connectTimeout", 10); set_default(options, "SSH:BufferSize", 10240); set_default(options, "SSH:maxFileSize", 100*ONE_MB); // Set limit to 100MB by default. set_default(options, "SSH:logSize", 100*ONE_MB); // Set limit to 100MB by default. set_default(options, "SSH:readWriteTimeout", 5); set_default(options, "SSH:commandTimeout", 1); set_default(options, "SSH:commandRetryCount", 3); #ifndef _MSC_VER set_default(options, "SSH:pathtosshconfig", base::expand_tilde("~/.ssh/config")); #endif // Other options set_default(options, "workbench.physical.Connection:ShowCaptions", 0); set_default(options, "workbench.physical.Connection:CenterCaptions", 0); // By the time we make it here, Logger's log level has already been set to default (whatever it may be). // NOTE that there's a cornercase we ignore: if user set --log-level or WB_LOG_LEVEL, this is what // Logger::active_level() will return instead. // But since set_default() only has effect the first time the Workbench is run, this shouldn't really matter for the // user while keeping our code simpler. set_default(options, "workbench.logger:LogLevel", base::Logger::active_level()); } grt::ListRef<app_PaperType> WBContext::get_paper_types(std::shared_ptr<grt::internal::Unserializer> unserializer) { return grt::ListRef<app_PaperType>::cast_from( grt::GRT::get()->unserialize(base::makePath(get_datadir(), "data/paper_types.xml"), unserializer)); } static void strip_options_dict(grt::DictRef dict) { std::vector<std::string> keys; { grt::DictRef::const_iterator iter = dict.begin(); grt::DictRef::const_iterator end = dict.end(); while (iter != end) { if (iter->first[0] == '@') keys.push_back(iter->first); ++iter; } } for (std::vector<std::string>::const_iterator end = keys.end(), iter = keys.begin(); iter != end; ++iter) { dict.remove(*iter); } } void WBContext::setLogLevelFromGuiPreferences(const grt::DictRef &dict) { // don't set if user already specified log level (via commmandline or shell env variable) if (base::Logger::wasLogLevelSpecifiedByUser()) return; std::string currentLogLevel = base::Logger::active_level(); std::string prefsLogLevel = dict.get_string("workbench.logger:LogLevel", currentLogLevel); if (currentLogLevel != prefsLogLevel) { bool ok = base::Logger::active_level(prefsLogLevel); if (ok) logInfo("Log level changed to '%s' according to UI option\n", prefsLogLevel.c_str()); else assert(0); } } void WBContext::load_app_options(bool update) { // load ui related stuff (menus, toolbars etc) wb::WBContextUI::get()->load_app_options(update); // load saved options std::string options_xml = base::makePath(_user_datadir, OPTIONS_FILE_NAME); if (g_file_test(options_xml.c_str(), G_FILE_TEST_EXISTS)) { try { app_OptionsRef curOptions(get_root()->options()); xmlDocPtr xmlDocument = grt::GRT::get()->load_xml(options_xml); if (!xmlDocument) { throw std::runtime_error( _("The file is not a valid MySQL Workbench options file.\n" "The file will skipped and settings are reset to their default values.")); } base::ScopeExitTrigger free_on_leave(std::bind(xmlFreeDoc, xmlDocument)); std::string doctype, version; grt::GRT::get()->get_xml_metainfo(xmlDocument, doctype, version); // Older option files without a version number are considered as 1.0.0 and // upgraded from there to latest version. if (version.empty()) version = "1.0.0"; else // Document format has been introduced in 1.0.1. if (doctype != OPTIONS_DOCUMENT_FORMAT) { throw std::runtime_error( _("The file is not a valid MySQL Workbench options file.\n" "The file will skipped and settings are reset to their default values.")); } // Try to upgrade document at XML level. if (version != OPTIONS_DOCUMENT_VERSION) attempt_options_upgrade(xmlDocument, version); grt::ValueRef options_value = grt::GRT::get()->unserialize_xml(xmlDocument, options_xml); app_OptionsRef options(app_OptionsRef::cast_from(options_value)); if (options.is_valid()) { // strip stuff that are not options strip_options_dict(options->options()); strip_options_dict(options->commonOptions()); // set loaded options dict grt::merge_contents(curOptions->options(), options->options(), true); grt::merge_contents(curOptions->commonOptions(), options->commonOptions(), true); setLogLevelFromGuiPreferences(options->options()); // set loaded recent files list (if they exist) while (curOptions->recentFiles().count() > 0) curOptions->recentFiles().remove(0); for (grt::StringListRef::const_iterator file = options->recentFiles().begin(); file != options->recentFiles().end(); ++file) { if (g_file_test((*file).c_str(), G_FILE_TEST_EXISTS)) curOptions->recentFiles().insert(*file); } // set loaded disabled plugins list grt::replace_contents(curOptions->disabledPlugins(), options->disabledPlugins()); // merge paper types (so we get custom types) grt::merge_contents_by_id(grt::ObjectListRef::cast_from(curOptions->paperTypes()), grt::ObjectListRef::cast_from(options->paperTypes()), false); grt::ListRef<app_PaperType> paperTypes = curOptions->paperTypes(); for (size_t c = paperTypes.count(), i = 0; i < c; i++) { app_PaperTypeRef value(paperTypes[i]); if (value.is_valid()) value->owner(curOptions); } // Load custom application colors and color scheme. base::ColorScheme scheme = base::ColorSchemeStandard; grt::ValueRef value = curOptions->options()["ColorScheme"]; if (value.is_valid()) { grt::IntegerRef int_value = grt::IntegerRef::cast_from(value); scheme = (base::ColorScheme)*int_value; } base::Color::set_active_scheme(scheme); options->reset_references(); } } catch (std::exception &exc) { mforms::Utilities::show_error(_("Error while loading options"), strfmt("The file '%s' could not be loaded: %s", options_xml.c_str(), exc.what()), _("Close")); grt::GRT::get()->send_error(strfmt("Error while loading '%s': %s", options_xml.c_str(), exc.what())); } } else { // No config file, so maybe initial start. Set at least a default color scheme. base::Color::set_active_scheme(base::ColorSchemeStandard); } // commit options option_dict_changed(); cleanup_options(); // load options specific for other parts of wb for(auto &it: _components) { it->load_app_options(update); } // load list of server instances db_mgmt_ManagementRef mgmt = get_root()->rdbmsMgmt(); std::string inst_list_xml = base::makePath(get_user_datadir(), SERVER_INSTANCE_LIST); if (g_file_test(inst_list_xml.c_str(), G_FILE_TEST_EXISTS)) { try { grt::ListRef<db_mgmt_ServerInstance> list( grt::ListRef<db_mgmt_ServerInstance>::cast_from(grt::GRT::get()->unserialize(inst_list_xml))); if (list.is_valid()) { while (mgmt->storedInstances().count() > 0) mgmt->storedInstances().remove(0); for (size_t c = list.count(), i = 0; i < c; i++) { // starting from 5.2.16, passwords are not stored in the profile anymore mgmt->storedInstances().insert(list.get(i)); } } } catch (std::exception &exc) { grt::GRT::get()->send_warning(strfmt("Error loading '%s': %s", inst_list_xml.c_str(), exc.what())); } } } void WBContext::load_other_connections() { // load list of non-MySQL connections unsigned int connection_count = 0; unsigned int total_connections = 0; db_mgmt_ManagementRef mgmt = get_root()->rdbmsMgmt(); std::string conn_list_xml = base::makePath(get_user_datadir(), FILE_OTHER_CONNECTION_LIST); if (g_file_test(conn_list_xml.c_str(), G_FILE_TEST_EXISTS)) { try { grt::ListRef<db_mgmt_Connection> list( grt::ListRef<db_mgmt_Connection>::cast_from(grt::GRT::get()->unserialize(conn_list_xml))); total_connections = (int)list->count(); if (list.is_valid()) { replace_contents(mgmt->otherStoredConns(), list); GRTLIST_FOREACH(db_mgmt_Connection, list, iter) (*iter)->owner(mgmt); } ++connection_count; } catch (std::exception &exc) { logError("Error loading %s: %s\n", conn_list_xml.c_str(), exc.what()); } } logInfo("Loaded %u/%u new non-MySQL connections\n", connection_count, total_connections); } void WBContext::attempt_options_upgrade(xmlDocPtr xmldoc, const std::string &version) { std::vector<std::string> ver = base::split(version, "."); int major = base::atoi<int>(ver[0], 0); int minor = base::atoi<int>(ver[1], 0); int revision = base::atoi<int>(ver[2], 0); // Version * -> 1.0.1 // Performed changes: // * Removed formPositions tag from options tag. if (major == 1 && minor == 0 && revision == 0) { XMLTraverser xml(xmldoc); std::vector<xmlNodePtr> options_list(xml.scan_objects_of_type("app.Options")); for (size_t c = options_list.size(), i = 0; i < c; i++) xml.delete_object_item(options_list[i], "formPositions"); revision = 1; } } void WBContext::save_app_options() { std::string options_file = base::makePath(_user_datadir, OPTIONS_FILE_NAME); app_OptionsRef options(get_root()->options()); // set owner of options to nil so that it wont point to a bogus object when loading GrtObjectRef owner(options->owner()); options->owner(GrtObjectRef()); grt::GRT::get()->serialize(options, options_file + ".tmp", OPTIONS_DOCUMENT_FORMAT, OPTIONS_DOCUMENT_VERSION); g_remove(options_file.c_str()); g_rename(std::string(options_file + ".tmp").c_str(), options_file.c_str()); options->owner(owner); FOREACH_COMPONENT(_components, iter) (*iter)->save_app_options(); } void WBContext::save_instances() { // save instance list db_mgmt_ManagementRef mgmt = get_root()->rdbmsMgmt(); if (!mgmt.is_valid()) return; std::string inst_list_xml = base::makePath(get_user_datadir(), SERVER_INSTANCE_LIST); grt::GRT::get()->serialize(mgmt->storedInstances(), inst_list_xml); } void WBContext::save_connections() { db_mgmt_ManagementRef mgmt = get_root()->rdbmsMgmt(); if (!mgmt.is_valid()) { logError("Failed to save connections (Invalid RDBMS management reference).\n"); return; } // save other connections list if (mgmt->otherStoredConns()->count()) { std::string conn_list_xml = base::makePath(get_user_datadir(), FILE_OTHER_CONNECTION_LIST); grt::GRT::get()->serialize(mgmt->otherStoredConns(), conn_list_xml); logDebug("Saved connection list (Non-MySQL: %u)\n", (unsigned int)mgmt->otherStoredConns()->count()); } grt::GRT::get()->serialize(mgmt->storedConns(), base::makePath(get_user_datadir(), FILE_CONNECTION_LIST)); logDebug("Saved connection list (MySQL: %u)\n", (unsigned int)mgmt->storedConns()->count()); } //-------------------------------------------------------------------------------------------------- void WBContext::load_app_state(std::shared_ptr<grt::internal::Unserializer> unserializer) { // Load saved state. std::string state_xml = base::makePath(_user_datadir, STATE_FILE_NAME); if (g_file_test(state_xml.c_str(), G_FILE_TEST_EXISTS)) { xmlDocPtr xmlDocument = NULL; try { xmlDocument = grt::GRT::get()->load_xml(state_xml); base::ScopeExitTrigger free_on_leave(std::bind(xmlFreeDoc, xmlDocument)); std::string doctype, version; grt::GRT::get()->get_xml_metainfo(xmlDocument, doctype, version); if (doctype != STATE_DOCUMENT_FORMAT) { throw std::runtime_error( _("The file is not a valid MySQL Workbench state file.\n" "The file will skipped and the application starts in its default state.")); } grt::DictRef current_state(get_root()->state()); grt::DictRef new_state = grt::DictRef::cast_from(grt::GRT::get()->unserialize_xml(xmlDocument, state_xml)); // Store new state in grt tree. grt::merge_contents(current_state, new_state, true); } catch (std::exception &exc) { mforms::Utilities::show_error(_("Error while loading application state"), strfmt("The file '%s' could not be loaded: %s", state_xml.c_str(), exc.what()), _("Close")); grt::GRT::get()->send_warning(strfmt("Error while loading '%s': %s", state_xml.c_str(), exc.what())); } } // restore stuff from grt shell bec::GRTManager::get()->get_shell()->restore_state(); } //-------------------------------------------------------------------------------------------------- void WBContext::save_app_state() { // Keep the current version number so we can compare on next startup if a new version was // launched the first time. std::string version = strfmt("%i.%i.%i", APP_MAJOR_NUMBER, APP_MINOR_NUMBER, APP_RELEASE_NUMBER); save_state("last-run-as", "global", version); std::string state_file = base::makePath(_user_datadir, STATE_FILE_NAME); grt::GRT::get()->serialize(get_root()->state(), state_file + ".tmp", STATE_DOCUMENT_FORMAT, STATE_DOCUMENT_VERSION); g_remove(state_file.c_str()); g_rename(std::string(state_file + ".tmp").c_str(), state_file.c_str()); try { bec::GRTManager::get()->get_shell()->store_state(); } catch (std::exception &exc) { std::string message = base::strfmt("Error saving GRT shell state: %s", exc.what()); grt::GRT::get()->send_error(message); } } //-------------------------------------------------------------------------------------------------- void WBContext::add_recent_file(const std::string &file) { grt::StringListRef recentFiles(get_root()->options()->recentFiles()); recentFiles.remove_value(file); recentFiles.insert(file, 0); // TODO: Make the max number of files configurable. while (recentFiles.count() > 20) recentFiles.remove(20); save_app_options(); wb::WBContextUI::get()->refresh_home_documents(); } //-------------------------------------------------------------------------------------------------- void WBContext::option_dict_changed(grt::internal::OwnedDict *options, bool, const std::string &) { if (get_wb_options() == grt::DictRef(options)) { ssize_t undo_size = get_wb_options().get_int("workbench:UndoEntries", DEFAULT_UNDO_STACK_SIZE); if (undo_size == 0) undo_size = 1; grt::GRT::get()->get_undo_manager()->set_undo_limit(undo_size); } } #endif // Setup____ /** * Cancels all pending grt idle tasks and refreshes in the context. Useful to avoid crashes * or meaningless GUI overhead when closing down (parts of) WB. * Warning: canceling idle tasks unconditionally might lead to other problems, so use with extreme care. */ bool WBContext::cancel_idle_tasks() { bool result = bec::GRTManager::get()->cancel_idle_tasks(); MutexLock lock(_pending_refresh_mutex); _pending_refreshes.clear(); return result; } void WBContext::flush_idle_tasks(bool force) { try { bec::GRTManager::get()->perform_idle_tasks(); if (_user_interaction_blocked) { return; } mdc::Timestamp now = mdc::get_time(); std::list<RefreshRequest> refreshes; { MutexLock lock(_pending_refresh_mutex); // separate the requests that can be executed now std::list<RefreshRequest>::iterator iter = _pending_refreshes.begin(); while (iter != _pending_refreshes.end()) { std::list<RefreshRequest>::iterator next = iter; ++next; if (force || (now - iter->timestamp >= UI_REQUEST_THROTTLE)) { refreshes.push_back(*iter); _pending_refreshes.erase(iter); } iter = next; } } // send the refresh requests for (std::list<RefreshRequest>::iterator iter = refreshes.begin(); iter != refreshes.end(); ++iter) _frontendCallbacks->refresh_gui(iter->type, iter->str, iter->ptr); } catch (std::exception &exc) { logException("WBContext: exception in flush idle task", exc); } } void WBContext::request_refresh(RefreshType type, const std::string &str, NativeHandle ptr) { MutexLock lock(_pending_refresh_mutex); mdc::Timestamp now = mdc::get_time(); // check if dupe for (std::list<RefreshRequest>::iterator iter = _pending_refreshes.begin(); iter != _pending_refreshes.end(); ++iter) { if (iter->type == type && iter->str == str && iter->ptr == ptr) { // if its a dupe, update the timestamp so that notifications are only sent when // there's no more fresh requests arriving iter->timestamp = now; return; } } RefreshRequest refresh; refresh.type = type; refresh.str = str; refresh.ptr = ptr; refresh.timestamp = now; #if !defined(_MSC_VER) && !defined(__APPLE__) // XXX: check this requirement. Probably already fixed since this hack was added. // Do not remove the following refresh! W/o it linux version hangs at times. if (_frontendCallbacks->refresh_gui && _pending_refreshes.empty()) _frontendCallbacks->refresh_gui(RefreshNeeded, "", (NativeHandle)0); #endif _pending_refreshes.push_back(refresh); } #ifndef Document____ //-------------------------------------------------------------------------------- // Creating/Loading documents void WBContext::new_document() { try { _frontendCallbacks->show_status_text(_("Creating new document...")); // Ask whether unsaved changes should be saved. if (has_unsaved_changes()) { int answer = mforms::Utilities::show_message( _("New Document"), _("Only one model can be open at a time. Do you want to save pending changes to the document?\n\n" "If you don't save your changes, they will be lost."), _("Save"), _("Cancel"), _("Don't Save")); if (answer == mforms::ResultOk) { if (!save_as(_filename)) return; } else if (answer == mforms::ResultCancel) return; } block_user_interaction(true); // close the current document do_close_document(false); _model_context = new WBContextModel(); // create an empty document and add a physical model to it workbench_DocumentRef doc(grt::Initialized); workbench_WorkbenchRef wb(get_root()); wb->doc(doc); // mark the document as a global object, so that child objects have changes tracked for undo doc->mark_global(); doc->owner(wb); { // setup default page settings app_PageSettingsRef page(grt::Initialized); page->owner(doc); page->paperType(grt::find_named_object_in_list(wb->options()->paperTypes(), "iso-a4")); if (!page->paperType().is_valid()) { // if there is no paperType available, create A4 app_PaperTypeRef paperType(grt::Initialized); paperType->owner(page); paperType->name("iso-a4"); paperType->caption("A4 (210 mm x 297 mm)"); paperType->width(210.0); paperType->height(297.0); paperType->marginsSet(0); page->paperType(paperType); } page->marginTop(6.35); page->marginBottom(14.46); page->marginLeft(6.35); page->marginRight(6.35); page->orientation(PAPER_PORTRAIT); doc->pageSettings(page); } init_properties_grt(doc); _file = new ModelFile(get_auto_save_dir()); scoped_connect(_file->signal_changed(), std::bind(&WBContext::request_refresh, this, RefreshDocument, "", static_cast<NativeHandle>(0))); _file->create(); bec::GRTManager::get()->set_db_file_path(_file->get_db_file_path()); _filename = ""; wb->docPath(_filename); _model_context->model_created(_file, doc); reset_document(); _save_point = grt::GRT::get()->get_undo_manager()->get_latest_undo_action(); request_refresh(RefreshDocument, ""); bec::GRTManager::get()->run_once_when_idle(std::bind(_frontendCallbacks->perform_command, "reset_layout")); bec::GRTManager::get()->run_once_when_idle(std::bind(&WBContext::block_user_interaction, this, false)); _frontendCallbacks->show_status_text(_("New document.")); } catch (grt::grt_runtime_error &error) { show_error(error.what(), error.detail); _frontendCallbacks->show_status_text(_("Error creating new document.")); } } /** Tells backend that frontend has finished preparing for a newly created or loaded model. Call as last thing done from the RefreshNewModel handler. */ void WBContext::new_model_finish() { _model_context->realize(); } void WBContext::open_script_file(const std::string &file) { execute_in_main_thread("openscript", std::bind(&WBContextSQLIDE::open_document, _sqlide_context, file), false); } void WBContext::open_recent_document(int index) { if (index - 1 < (int)get_root()->options()->recentFiles().count()) { std::string file = get_root()->options()->recentFiles().get(index - 1); if (g_str_has_suffix(file.c_str(), ".mwb")) open_document(file); else open_script_file(file); } } bool WBContext::open_file_by_extension(const std::string &path, bool interactive) { if (g_str_has_suffix(path.c_str(), ".mwbplugin") || g_str_has_suffix(path.c_str(), ".mwbpluginz")) { // install plugin if (interactive) return wb::WBContextUI::get()->start_plugin_install(path); install_module_file(path); return true; } else if (g_str_has_suffix(path.c_str(), ".mwb")) { // open document return open_document(path); } else if (g_str_has_suffix(path.c_str(), ".sql")) { SqlEditorForm *form = _sqlide_context->get_active_sql_editor(); if (form) { form->open_file(path, true); return true; } _sqlide_context->open_document(path); return false; } else { if (interactive) { show_error(_("Unrecognized File Type"), base::strfmt(_("MySQL Workbench does not know how to open file %s"), path.c_str())); } return false; } } void WBContext::reset_document() { grt::GRT::get()->get_undo_manager()->reset(); wb::WBContextUI::get()->reset(); _clipboard->clear(); _clipboard->set_content_description(""); // refresh internal environment of loaders grt::GRT::get()->refresh_loaders(); } int WBContext::closeModelFile() { if (_model_import_file) { delete _model_import_file; _model_import_file = 0; } return 0; }; std::string WBContext::getTempDir() { if (_model_import_file) return _model_import_file->get_tempdir_path(); return ""; } std::string WBContext::getDbFilePath() { if (_model_import_file) return _model_import_file->get_db_file_path(); return ""; }; workbench_DocumentRef WBContext::openModelFile(const std::string &file) { workbench_DocumentRef doc; closeModelFile(); _model_import_file = new ModelFile(get_auto_save_dir()); try { if (base::string_compare(file, get_filename(), false) == 0) { mforms::Utilities::show_message("Open Document", "Error while including another model. A model cannot be added to itself.", "OK"); return doc; } _model_import_file->open(file); // _manager->set_db_file_path(_file->get_db_file_path()); doc = _model_import_file->retrieve_document(); } catch (std::exception &exc) { show_exception(strfmt(_("Cannot open document '%s'."), file.c_str()), exc); } return doc; }; bool WBContext::open_document(const std::string &file) { if (_model_context != NULL) { // A model is already loaded. Warn the user it will be closed. Ask for saving pending changes // if there are any. if (has_unsaved_changes()) { int answer = execute_in_main_thread<int>("check save changes", std::bind(mforms::Utilities::show_message, _("Open Document"), _("Only one model can be open at a time. Do you wish to " "save pending changes to the currently open model?\n\n" "If you don't they will be lost."), _("Save"), _("Cancel"), _("Don't Save"))); if (answer == mforms::ResultOk) { if (!save_as(_filename)) return false; } else if (answer == mforms::ResultCancel) return false; } else { int answer = execute_in_main_thread<int>( "replace document", std::bind(mforms::Utilities::show_message, _("Open Document"), _("Opening another model will close the currently open model.\n\n" "Do you wish to proceed opening it?"), _("Open"), _("Cancel"), "")); if (answer != mforms::ResultOk) return false; } execute_in_main_thread("close document", std::bind(&WBContext::do_close_document, this, false), true); } _frontendCallbacks->show_status_text(strfmt(_("Loading %s..."), file.c_str())); ValidationManager::clear(); GUILock lock(this, _("Model file is being loaded"), strfmt(_("The model %s is loading now and will be available " "in a moment.\n\n Please stand by..."), file.c_str())); bec::GRTManager::get()->block_idle_tasks(); workbench_DocumentRef doc; _model_context = new WBContextModel; FOREACH_COMPONENT(_components, iter) (*iter)->block_model_notifications(); _file = new ModelFile(get_auto_save_dir()); scoped_connect(_file->signal_changed(), std::bind(&WBContext::request_refresh, this, RefreshDocument, "", static_cast<NativeHandle>(0))); try { _file->open(file); bec::GRTManager::get()->set_db_file_path(_file->get_db_file_path()); doc = _file->retrieve_document(); } /* catch (grt::grt_runtime_exception &exc) { show_exception(strfmt(_("Cannot open document '%s'."), file.c_str()), exc); new_document(); _manager->unblock_idle_tasks(); FOREACH_COMPONENT(_components, iter) (*iter)->unblock_model_notifications(); lock_gui(false); return false; }*/ catch (std::exception &exc) { show_exception(strfmt(_("Cannot open document '%s'."), file.c_str()), exc); // if open fails, just let it fail new_document(); bec::GRTManager::get()->unblock_idle_tasks(); FOREACH_COMPONENT(_components, iter) (*iter)->unblock_model_notifications(); return false; } std::list<std::string> warnings(_file->get_load_warnings()); if (!warnings.empty()) { if (warnings.size() == 1) { mforms::Utilities::show_warning( _("Corrected Model File"), strfmt(_("The model in file '%s' contained a problem which was successfully recovered:\n %s"), file.c_str(), warnings.front().c_str()), _("Close")); } else { std::string msg = strfmt(_("The model in file '%s' contained problems which were successfully recovered:\n"), file.c_str()); int i = 0; for (std::list<std::string>::const_iterator iter = warnings.begin(); iter != warnings.end(); ++iter) { if (i++ >= 2) { msg.append("(see log for more details)"); break; } msg.append(" -").append(*iter).append("\n"); } mforms::Utilities::show_warning(_("Corrected Model File"), msg, _("Close")); } grt::GRT::get()->send_output( base::strfmt("%i problems found and corrected during load of file '%s':\n", (int)warnings.size(), file.c_str())); for (std::list<std::string>::const_iterator iter = warnings.begin(); iter != warnings.end(); ++iter) grt::GRT::get()->send_output(base::strfmt(" - %s\n", iter->c_str())); _file->copy_file(file, file + ".beforefix"); grt::GRT::get()->send_output(base::strfmt("Original file backed up to %s\n", (file + ".beforefix").c_str())); } // 5.0 -> 5.1 was done at 1.2.0, if document is from 5.0, make a backup std::vector<std::string> version_parts = base::split(_file->in_disk_document_version(), "."); if (version_parts.size() >= 2 && version_parts[0] == "1" && base::atoi<int>(version_parts[1], 0) <= 2) { std::string::size_type dot = file.rfind('.'); std::string bakpath; if (file.substr(dot) == ".mwb") bakpath = file.substr(0, dot).append(".wb50.mwb"); else bakpath = file + ".wb50.mwb"; _file->copy_file(file, bakpath); // make backup grt::GRT::get()->send_info(strfmt("Model file is from 5.0, making backup to %s", bakpath.c_str())); } else if (version_parts.size() >= 2 && version_parts[0] == "1" && base::atoi<int>(version_parts[1], 0) <= 3) { std::string::size_type dot = file.rfind('.'); std::string bakpath; if (file.substr(dot) == ".mwb") bakpath = file.substr(0, dot).append(".wb51.mwb"); else bakpath = file + ".wb51.mwb"; _file->copy_file(file, bakpath); // make backup grt::GRT::get()->send_info(strfmt("Model file is from 5.1, making backup to %s", bakpath.c_str())); } // add the file to the recent files list and sync the options file { NotificationInfo info; info["path"] = file; NotificationCenter::get()->send("GNDocumentOpened", 0, info); } workbench_WorkbenchRef wb(get_root()); wb->doc(doc); doc->owner(wb); // mark the document as a global object, so that child objects have changes tracked for undo doc->mark_global(); // check if paperType is properly set (if its null it could be a custom type // not available locally) if (!doc->pageSettings()->paperType().is_valid()) { doc->pageSettings()->paperType(grt::find_named_object_in_list(get_root()->options()->paperTypes(), "A4")); } FOREACH_COMPONENT(_components, iter) (*iter)->unblock_model_notifications(); reset_document(); _model_context->model_loaded(_file, doc); _filename = file; _save_point = grt::GRT::get()->get_undo_manager()->get_latest_undo_action(); wb->docPath(_filename); request_refresh(RefreshDocument, ""); _frontendCallbacks->show_status_text(_("Document loaded.")); bec::GRTManager::get()->unblock_idle_tasks(); if (_frontendCallbacks->perform_command) bec::GRTManager::get()->run_once_when_idle(std::bind(_frontendCallbacks->perform_command, "reset_layout")); return true; } //-------------------------------------------------------------------------------------------------- /** * Separate close confirmation and actual close action into two actions to allow better UI updates. * This method checks if the document can be closed. That is: * - it is either unchanged * - the user confirmed to save it (and it was saved). * * @result True if the document can be closed, false otherwise. */ bool WBContext::can_close_document() { if (!_asked_for_saving && has_unsaved_changes()) { int answer = execute_in_main_thread<int>("check save changes", std::bind(mforms::Utilities::show_message, _("Close Document"), _("Do you want to save pending changes to the document?\n\n" "If you don't save your changes, they will be lost."), _("Save"), _("Cancel"), _("Don't Save"))); if (answer == mforms::ResultOk) { if (!save_as(_filename)) return false; } else if (answer == mforms::ResultCancel) return false; _asked_for_saving = true; } return true; } //-------------------------------------------------------------------------------------------------- /** * Closes the current document. This function can be called from any thread. * * If there are pending changes the user is asked to save them. If can_close_document was called before * then the user should not be bothered again about pending changes. * * XXX: this should finally be changed. can_close is the function which can cancel the closing process. * When we reach here it's too late. This function is called from a destructor and hence cannot be cancelled. */ bool WBContext::close_document() { if (can_close_document()) { _asked_for_saving = false; block_user_interaction(true); // close the current document execute_in_main_thread("close document", std::bind(&WBContext::do_close_document, this, false), true); block_user_interaction(false); bec::GRTManager::get()->has_unsaved_changes(false); return true; } return false; } //-------------------------------------------------------------------------------------------------- void WBContext::do_close_document(bool destroying) { // This method must only be called from the main thread. assert(bec::GRTManager::get()->in_main_thread()); if (_model_context) _model_context->model_closed(); if (!destroying && _frontendCallbacks->refresh_gui) { // close all open editors _frontendCallbacks->refresh_gui(RefreshCloseEditor, "", (NativeHandle)0); } ValidationManager::clear(); delete _file; _file = 0; // reset undo manager before destroying views to make sure that old refs to // figures will be released and bridges will be deleted 1st grt::GRT::get()->get_undo_manager()->reset(); _save_point = grt::GRT::get()->get_undo_manager()->get_latest_undo_action(); FOREACH_COMPONENT(_components, iter) (*iter)->close_document(); if (!destroying && _frontendCallbacks->refresh_gui) { // Cancel all pending model related events. _pending_refreshes.remove_if(CancelRefreshCandidate()); _frontendCallbacks->refresh_gui(RefreshCloseDocument, "", (NativeHandle)0); } } /** Tells backend that the frontend has finished doing cleanup for document close. */ void WBContext::close_document_finish() { workbench_DocumentRef doc(get_document()); _filename = ""; get_root()->docPath(""); if (_model_context) _model_context->unrealize(); get_root()->doc(workbench_DocumentRef()); delete _model_context; _model_context = 0; // reset circular references in the document once the app goes idle if (doc.is_valid()) doc->reset_references(); // reset once again just to be sure grt::GRT::get()->get_undo_manager()->reset(); _save_point = grt::GRT::get()->get_undo_manager()->get_latest_undo_action(); } //-------------------------------------------------------------------------------- // Saving grt::ValueRef WBContext::save_grt() { // update last change timestamp app_DocumentInfoRef info(get_document()->info()); info->dateChanged(fmttime(0, DATETIME_FMT)); std::string zip_comment; try { workbench_DocumentRef doc(get_document()); GrtObjectRef owner(doc->owner()); doc->owner(GrtObjectRef()); // temporarily clear non-persistent owner _file->store_document(doc); doc->owner(owner); ListRef<db_Schema> schemata(doc->physicalModels()[0]->catalog()->schemata()); if (schemata.count()) zip_comment += "model-schemas: "; size_t last = schemata.count() - 1; for (size_t sc = schemata.count(), si = 0; si < sc; si++) { db_SchemaRef schema(schemata[si]); zip_comment += schema->name(); if (si != last) zip_comment += ", "; } if (!zip_comment.empty()) zip_comment += '\n'; } catch (std::exception &exc) { show_exception(_("Could not store document data."), exc); return grt::ValueRef(); } try { if (!_file->save_to(_filename, zip_comment)) return grt::ValueRef(); } catch (std::exception &exc) { show_exception(strfmt(_("Could not save document to %s"), _filename.c_str()), exc); return grt::ValueRef(); } // add the file to the recent files list and sync the options file // add_recent_file(_filename); { NotificationInfo info; info["path"] = _filename; NotificationCenter::get()->send("GNDocumentOpened", 0, info); } bec::GRTManager::get()->has_unsaved_changes(false); _attachments_changed = false; _save_point = grt::GRT::get()->get_undo_manager()->get_latest_undo_action(); request_refresh(RefreshDocument, ""); return grt::IntegerRef(1); } std::string WBContext::get_filename() const { return _filename; } std::string WBContext::get_auto_save_dir() { return bec::GRTManager::get()->get_user_datadir(); } //-------------------------------------------------------------------------------------------------- /** * Removes outdated settings that were used previously, but are no longer needed. */ void WBContext::cleanup_options() { logDebug("Cleaning up old options\n"); grt::DictRef options = get_root()->options()->options(); options.remove("workbench.physical.ConnectionFigure:CaptionFont"); options.remove("workbench.model.Layer:TitleFont"); options.remove("workbench.model.NoteFigure:TitleFont"); options.remove("workbench.physical:DeleteObjectConfirmation"); options.remove("Sidebar:RightAligned"); } //-------------------------------------------------------------------------------------------------- bool WBContext::save_as(const std::string &path) { if (_frontendCallbacks->refresh_gui) execute_in_main_thread("commit_changes", std::bind(_frontendCallbacks->refresh_gui, RefreshFinishEdits, "", (NativeHandle)0), true); if (path.empty()) { std::string s = execute_in_main_thread<std::string>( "save", std::bind(_frontendCallbacks->show_file_dialog, "save", _("Save Model"), "mwb")); if (s.empty()) return false; if (!base::hasSuffix(s, ".mwb")) s.append(".mwb"); _filename = s; } else _filename = path; try { _frontendCallbacks->show_status_text(strfmt(_("Saving %s..."), _filename.c_str())); if (grt::IntegerRef::cast_from(save_grt()) == 1) { _frontendCallbacks->show_status_text(strfmt(_("%s saved."), _filename.c_str())); return true; } else _frontendCallbacks->show_status_text(_("Error saving document.")); } catch (grt::grt_runtime_error &error) { show_exception(_("Error saving document"), error); _frontendCallbacks->show_status_text(_("Error saving document.")); } return false; } bool WBContext::has_unsaved_changes() { if (bec::GRTManager::get()->has_unsaved_changes()) return true; if (grt::GRT::get()->get_undo_manager()->get_latest_closed_undo_action() != _save_point) return true; if (_file && _file->has_unsaved_changes()) return true; if (_attachments_changed) return true; return false; } bool WBContext::save_changes() { save_as(_filename); return !has_unsaved_changes(); } #endif // Document____ #ifndef Plugins____ //-------------------------------------------------------------------------------- // Plugin Handling void WBContext::update_plugin_arguments_pool(bec::ArgumentPool &args) { // value must be asked interactively if (args.find("app.PluginInputDefinition:string") == args.end()) { // don't add placeholder if it already has a value args["app.PluginInputDefinition:string"] = grt::StringRef(""); } args["app.PluginFileInput::save"] = grt::StringRef(""); args["app.PluginFileInput::open"] = grt::StringRef(""); args["app.PluginFileInput:filename:save"] = grt::StringRef(""); args["app.PluginFileInput:filename:open"] = grt::StringRef(""); if (_model_context && _model_context->get_active_model(true).is_valid()) return _model_context->update_plugin_arguments_pool(args); if (_sqlide_context->get_active_sql_editor()) return _sqlide_context->update_plugin_arguments_pool(args); } void WBContext::report_bug(const std::string &errorInfo) { grt::Module *module; module = grt::GRT::get()->get_module("Workbench"); if (!module) throw std::runtime_error("Workbench module not found"); // Setst he parameters for the python plugin grt::BaseListRef args(true); args.ginsert(grt::StringRef(errorInfo)); module->call_function("reportBug", args); } void WBContext::execute_plugin(const std::string &plugin_name, const ArgumentPool &defaults) { app_PluginRef plugin(_plugin_manager->get_plugin(plugin_name)); if (!plugin.is_valid()) throw grt::grt_runtime_error("Invalid plugin", "Invalid plugin " + plugin_name); ArgumentPool argpool(defaults); update_plugin_arguments_pool(argpool); app_PluginFileInputRef finput(argpool.needs_file_input(plugin)); if (finput.is_valid()) { std::string fname; fname = _frontendCallbacks->show_file_dialog(finput->dialogType(), finput->dialogTitle(), finput->fileExtensions()); if (fname.empty()) { _frontendCallbacks->show_status_text(_("Cancelled.")); return; } argpool.add_file_input(finput, fname); } // build the argument list grt::BaseListRef fargs = argpool.build_argument_list(plugin); // internal plugins are executed directly in the main thread if (plugin->pluginType() == INTERNAL_PLUGIN_TYPE || plugin->pluginType() == STANDALONE_GUI_PLUGIN_TYPE) { grt::ValueRef result = execute_plugin_grt(plugin, fargs); plugin_finished(result, plugin); } else { bec::GRTManager::get()->execute_grt_task(strfmt(_("Performing %s..."), plugin->caption().c_str()), std::bind(&WBContext::execute_plugin_grt, this, plugin, fargs), std::bind(&WBContext::plugin_finished, this, std::placeholders::_1, plugin)); } } grt::ValueRef WBContext::execute_plugin_grt(const app_PluginRef &plugin, const grt::BaseListRef &args) { grt::ValueRef result; if (plugin.is_instance(app_DocumentPlugin::static_class_name())) { throw std::logic_error("not implemented"); /* FIXME app_DocumentPluginRef doc_plugin(app_DocumentPluginRef::cast_from(plugin)); workbench_DocumentRef document(get_document()); bool flag= false; for (size_t c= doc_plugin.documentStructNames().count(), i= 0; i < c; i++) { if (document.is_instance(*doc_plugin.documentStructNames().get(i))) { flag= true; break; } } if (flag) { _plugin_manager->open_plugin(plugin, document); } else throw grt::grt_runtime_error(_("Invalid document type for plugin."), _("The plugin cannot be executed because the current document type is not known to it.")); */ } else { GTimer *timer = g_timer_new(); g_timer_start(timer); if (_model_context) _model_context->begin_plugin_exec(); bec::GRTManager::get()->soft_lock_globals_tree(); try { bool skip_undo = false; if (*plugin->pluginType() != "normal") skip_undo = true; grt::AutoUndo undo(skip_undo); std::string s = *plugin->pluginType(); _plugin_manager->open_plugin(plugin, args); undo.end_or_cancel_if_empty(plugin->caption()); // TODO: remove this obsolete code once it is confirmed that updating the catalog tree here is not necessary. // Btw: why is only the catalog tree updated here? It's not the right place here anyway. // request_refresh(RefreshSchemaList, ""); } catch (const std::exception &exc) { grt::GRT::get()->send_error(strfmt("Error executing plugin %s: %s", plugin->name().c_str(), exc.what())); result = grt::StringRef(strfmt("%s", exc.what())); } bec::GRTManager::get()->soft_unlock_globals_tree(); if (_model_context) _model_context->end_plugin_exec(); g_timer_stop(timer); double elapsed = g_timer_elapsed(timer, NULL); g_timer_destroy(timer); grt::GRT::get()->send_verbose(strfmt("%s finished in %.2fs\n", plugin->name().c_str(), elapsed)); } return result; } void WBContext::plugin_finished(const grt::ValueRef &result, const app_PluginRef &plugin) { if (*plugin->showProgress()) _frontendCallbacks->show_status_text(strfmt(_("Execution of \"%s\" finished."), plugin->caption().c_str())); if (result.is_valid()) { std::string message = *grt::StringRef::cast_from(result); show_error(strfmt("Error during \"%s\"", plugin->caption().c_str()), message); } // request a refresh on the toolbars and menus in case some button state has changed bec::UIForm *form = get_active_main_form(); if (form) { mforms::MenuBar *menu = form->get_menubar(); if (menu) menu->validate(); mforms::ToolBar *tbar = form->get_toolbar(); if (tbar) tbar->validate(); } } //-------------------------------------------------------------------------------- // Object Editors void WBContext::close_gui_plugin(NativeHandle handle) { _plugin_manager->forget_gui_plugin_handle(handle); // TODO: really closing the plugin produces flicker when an existing editor is reused. Needs investigation. //_plugin_manager->close_and_forget_gui_plugin(handle); } void WBContext::register_builtin_plugins(grt::ListRef<app_Plugin> plugins) { _plugin_manager->register_plugins(plugins); } bool WBContext::activate_live_object(const GrtObjectRef &object) { try { return get_sqlide_context()->activate_live_object(object); } catch (grt::grt_runtime_error &exc) { show_exception(_("Activate Live Object"), exc); } return false; } #endif // Plugins____ #ifndef DB_Querying____ std::shared_ptr<SqlEditorForm> WBContext::add_new_query_window(const db_mgmt_ConnectionRef &targetConnection, bool restore_session) { db_mgmt_ConnectionRef target(targetConnection); if (!target.is_valid()) { grtui::DbConnectionDialog dialog(get_root()->rdbmsMgmt()); logDebug("No connection specified, showing connection selection dialog...\n"); target = dialog.run(); if (!target.is_valid()) { logDebug("Connection selection dialog was cancelled\n"); _frontendCallbacks->show_status_text(_("Connection cancelled")); return SqlEditorForm::Ref(); } } _frontendCallbacks->show_status_text(_("Opening SQL Editor...")); SqlEditorForm::Ref form; try { _frontendCallbacks->show_status_text(_("Connecting...")); form = get_sqlide_context()->create_connected_editor(target); if (form->connection_details().find("dbmsProductVersion") != form->connection_details().end()) { // check that we're connecting to a known and supported version of the server if (!bec::is_supported_mysql_version(form->connection_details()["dbmsProductVersion"])) { logError("Unsupported server version: %s %s\n", form->connection_details()["dbmsProductName"].c_str(), form->connection_details()["dbmsProductVersion"].c_str()); if (mforms::Utilities::show_message_and_remember( base::strfmt("Connection Warning (%s)", targetConnection->name().c_str()), base::strfmt( "Incompatible/nonstandard server version or connection protocol detected (%s).\n\n" "A connection to this database can be established but some MySQL Workbench features may not work " "properly since the database is not fully compatible with the supported versions of MySQL.\n\n" "MySQL Workbench is developed and tested for MySQL Server versions 5.6, 5.7 and 8.0.\n" "Please note: there may be some incompatibilities with version 8.4.\n" "For MySQL Server older than 5.6, please use MySQL Workbench version 6.3.", bec::sanitize_server_version_number(form->connection_details()["dbmsProductVersion"]).c_str()), "Continue Anyway", "Cancel", "", "wb.supported_server_check.suppress_warning", "Don't show this message again") != mforms::ResultOk) { _frontendCallbacks->show_status_text(_("Unsupported server")); return SqlEditorForm::Ref(); } } } save_connections(); // lastConnected time changed (and potentially the serverVersion). } catch (grt::user_cancelled &e) { if (target.is_valid()) logInfo("Connection to %s cancelled by user: %s\n", target->name().c_str(), e.what()); else logInfo("Connection cancelled by user: %s\n", e.what()); _frontendCallbacks->show_status_text(_("Connection cancelled")); return SqlEditorForm::Ref(); } catch (grt::server_denied &sd) { SqlEditorForm::report_connection_failure(sd, target); return SqlEditorForm::Ref(); } catch (std::exception &exc) { SqlEditorForm::report_connection_failure(exc.what(), target); return SqlEditorForm::Ref(); } try { _frontendCallbacks->create_main_form_view(WB_MAIN_VIEW_DB_QUERY, form); } catch (std::exception &exc) { _frontendCallbacks->show_status_text(_("Could not open SQL Editor.")); show_error(_("Cannot Open SQL Editor"), strfmt(_("Error in frontend for SQL Editor: %s"), exc.what())); return SqlEditorForm::Ref(); } // Restore the last workspace *after* the UI has setup the WQE frontend. if (restore_session) form->restore_last_workspace(); _frontendCallbacks->show_status_text(_("SQL Editor Opened.")); return form; } std::shared_ptr<SqlEditorForm> WBContext::add_new_query_window() { _frontendCallbacks->show_status_text(_("Opening SQL Editor...")); SqlEditorForm::Ref form; form = get_sqlide_context()->create_connected_editor(db_mgmt_ConnectionRef()); try { _frontendCallbacks->create_main_form_view(WB_MAIN_VIEW_DB_QUERY, form); } catch (std::exception &exc) { _frontendCallbacks->show_status_text(_("Could not open SQL Editor.")); show_error(_("Cannot Open SQL Editor"), strfmt(_("Error in frontend for SQL Editor: %s"), exc.what())); return SqlEditorForm::Ref(); } _frontendCallbacks->show_status_text(_("SQL Editor Opened.")); form->update_title(); return form; } #endif // DB_Querying____ #ifndef Admin____ void WBContext::add_new_admin_window(const db_mgmt_ConnectionRef &target) { std::shared_ptr<SqlEditorForm> conn(add_new_query_window(target)); if (conn) { grt::BaseListRef args(true); db_query_EditorRef editor(_sqlide_context->get_grt_editor_object(conn.get())); args.ginsert(editor); args.ginsert(grt::StringRef("admin_server_status")); grt::GRT::get()->call_module_function("WbAdmin", "openAdminSection", args); } } #endif // Admin__ #ifndef AutoStartPlugins____ void WBContext::add_new_plugin_window(const std::string &plugin_id, const std::string &caption) { _frontendCallbacks->show_status_text(strfmt(_("Starting %s Module..."), caption.c_str())); try { grt::BaseListRef args(AnyType); app_PluginRef plugin(_plugin_manager->get_plugin(plugin_id)); if (plugin.is_valid()) _plugin_manager->open_plugin(plugin, args); else { _frontendCallbacks->show_status_text(strfmt(_("%s plugin not found"), caption.c_str())); return; } } catch (std::exception &exc) { logError("Error opening %s: %s\n", caption.c_str(), exc.what()); _frontendCallbacks->show_status_text(strfmt(_("Could not open %s: %s"), caption.c_str(), exc.what())); return; } } #endif // AutoStartPlugins____ #ifndef Utilities____ workbench_WorkbenchRef WBContext::get_root() { return workbench_WorkbenchRef::cast_from(grt::DictRef::cast_from(grt::GRT::get()->root()).get("wb")); } workbench_DocumentRef WBContext::get_document() { return workbench_DocumentRef::cast_from(get_root()->doc()); } grt::DictRef WBContext::get_wb_options() { return get_root()->options()->options(); } // XXX: we have mforms::Utilities::perform_from_main_thread. void WBContext::execute_in_main_thread(const std::string &name, const std::function<void()> &function, bool wait) { bec::GRTManager::get()->get_dispatcher()->call_from_main_thread<void>(function, wait, false); } void WBContext::show_exception(const std::string &operation, const std::exception &exc) { const grt::grt_runtime_error *rt = dynamic_cast<const grt::grt_runtime_error *>(&exc); if (rt) { if (bec::GRTManager::get()->in_main_thread()) show_error(operation, std::string(rt->what()) + "\n" + rt->detail); else bec::GRTManager::get()->run_once_when_idle( std::bind(&WBContext::show_error, this, operation, std::string(rt->what()) + "\n" + rt->detail)); } else { if (bec::GRTManager::get()->in_main_thread()) show_error(operation, exc.what()); else bec::GRTManager::get()->run_once_when_idle(std::bind(&WBContext::show_error, this, operation, std::string(exc.what()))); } } void WBContext::show_exception(const std::string &operation, const grt::grt_runtime_error &exc) { if (bec::GRTManager::get()->in_main_thread()) show_error(operation, std::string(exc.what()) + "\n" + exc.detail); else bec::GRTManager::get()->run_once_when_idle( std::bind(&WBContext::show_error, this, operation, std::string(exc.what()) + "\n" + exc.detail)); } #endif // Utilities____ bool WBContext::install_module_file(const std::string &path) { std::string module_dir = bec::GRTManager::get()->get_user_module_path(); std::string target_path; std::string lang_extension; { std::string fname = base::basename(path); lang_extension = base::extension(fname); if (!lang_extension.empty()) fname = base::strip_extension(fname); target_path = module_dir + "/" + fname; } if (lang_extension == ".py") { // python doesnt like . in middle of filename if (g_str_has_suffix(target_path.c_str(), ".grt")) target_path[target_path.length() - 4] = '_'; else if (!g_str_has_suffix(target_path.c_str(), "_grt")) target_path.append("_grt"); } else if (lang_extension == ".lua") { show_error("Install Plugin", "Lua is no longer supported in this version."); } else if (lang_extension == ".mwbpluginz") { lang_extension = ".mwbplugin"; } else if (lang_extension == ".mwbplugin") { // do nothing } else { show_error("Install Plugin", strfmt("The file %s is not of a known plugin type.", path.c_str())); return false; } // add back the lang_extension target_path.append(lang_extension); if (module_dir.empty()) { show_error("Could Not Install Plugin", "User module install directory is not known"); return false; } if (g_file_test(target_path.c_str(), G_FILE_TEST_EXISTS)) { logInfo("A plugin file named '%s' is already installed.\n", base::basename(path).c_str()); if (mforms::Utilities::show_message( "Install Plugin", strfmt("A plugin file named '%s' is already installed, would you like to replace it?", base::basename(path).c_str()), "Replace", "Cancel", "") != mforms::ResultOk) { logInfo("Plugin replacment denied.\n"); return false; } logInfo("Plugin replacment accepted.\n"); } if (lang_extension == ".mwbplugin") { if (*path.rbegin() == 'z') { try { ModelFile::unpack_zip(path, module_dir); } catch (const std::exception &exc) { show_error("Could Not Install Plugin", strfmt("Plugin %s could not be installed: %s", path.c_str(), exc.what())); return false; } } else { if (!copy_folder(path.c_str(), target_path.c_str())) { show_error("Could Not Install Plugin", strfmt("Plugin %s could not be copied to modules folder.", path.c_str())); return false; } } } else if (!base::copyFile(path.c_str(), target_path.c_str())) { int err = errno; show_error("Could Not Install Plugin", g_strerror(err)); grt::GRT::get()->send_output( strfmt("ERROR: could not copy module '%s' to '%s': %s\n", path.c_str(), target_path.c_str(), g_strerror(err))); return false; } std::string message = strfmt("Plugin %s installed.", path.c_str()); logInfo("%s\n", message.c_str()); _frontendCallbacks->show_status_text(message); mforms::Utilities::show_message( "Plugin Installed", strfmt("Plugin %s was installed, please restart Workbench to use it.", path.c_str()), "OK"); grt::GRT::get()->send_output(strfmt("Copied module %s to '%s'\n", path.c_str(), target_path.c_str())); grt::GRT::get()->send_output("Please restart Workbench for the change to take effect.\n"); return true; } bool WBContext::uninstall_module(grt::Module *module) { std::string path = module->path(); if (path.empty()) { logWarning("Can't uninstall module %s\n", module->name().c_str()); return false; } grt::StringListRef disabled_plugins(get_root()->options()->disabledPlugins()); // remove all plugins from this module from the disabled list grt::ListRef<app_Plugin> pl(_plugin_manager->get_plugin_list()); for (grt::ListRef<app_Plugin>::const_iterator p = pl.begin(); p != pl.end(); ++p) { if ((*p)->moduleName() == module->name()) disabled_plugins.remove_value((*p)->name()); } // unregister the module grt::GRT::get()->unregister_module(module); _plugin_manager->rescan_plugins(); // delete the file if (module->is_bundle()) path = module->bundle_path(); auto ext = base::extension(path); if (ext == ".py") { std::string pyc = path + "c"; // For python, we need to also remove the pyc file if (base::file_exists(pyc)) mforms::Utilities::move_to_trash(pyc); } mforms::Utilities::move_to_trash(path); mforms::Utilities::move_to_trash(path + "c"); return false; } void WBContext::run_script_file(const std::string &filename) { logDebug("Executing script %s...\n", filename.c_str()); bec::GRTManager::get()->push_status_text(base::strfmt("Executing script %s...", filename.c_str())); grt::AutoUndo undo; try { bec::GRTManager::get()->get_shell()->run_script_file(filename); } catch (std::exception &exc) { undo.cancel(); logError("Script failed: %s\n", exc.what()); bec::GRTManager::get()->replace_status_text("Script execution failed"); return; } undo.end_or_cancel_if_empty(strfmt("Execute Script %s", base::basename(filename).c_str())); logDebug("Script finished.\n"); bec::GRTManager::get()->pop_status_text(); } std::string WBContext::create_attached_file(const std::string &group, const std::string &tmpl) { if (group == "script") return _file->add_script_file(tmpl); else if (group == "note") return _file->add_note_file(tmpl); else throw std::invalid_argument("invalid attachment group name"); } std::string WBContext::recreate_attached_file(const std::string &name, const std::string &data) { _file->undelete_file(name); _file->set_file_contents(name, data); return name; } void WBContext::save_attached_file_contents(const std::string &name, const char *data, size_t size) { _attachments_changed = true; _file->set_file_contents(name, data, size); } std::string WBContext::get_attached_file_contents(const std::string &name) { return _file->get_file_contents(name); } std::string WBContext::get_attached_file_tmp_path(const std::string &name) { return _file->get_path_for(name); } int WBContext::export_attached_file_contents(const std::string &name, const std::string &export_to) { try { _file->copy_file_to(name, export_to); } catch (grt::os_error &exc) { logWarning("Error exporting %s: %s\n", name.c_str(), exc.what()); return 0; } return 1; } void WBContext::delete_attached_file(const std::string &name) { _file->delete_file(name); } /** * Returns the value for a state given by name as string, if it exists or the default value if not. */ std::string WBContext::read_state(const std::string &name, const std::string &domain, const std::string &default_value) { workbench_WorkbenchRef wb = get_root(); grt::DictRef dict = wb->state(); return dict.get_string(domain + ":" + name, default_value); } /** * Returns the value for a state given by name as int, if it exists or the default value if not. */ int WBContext::read_state(const std::string &name, const std::string &domain, const int &default_value) { grt::DictRef dict = get_root()->state(); return (int)dict.get_int(domain + ":" + name, default_value); } /** * Returns the value for a state given by name as double, if it exists or the default value if not. */ double WBContext::read_state(const std::string &name, const std::string &domain, const double &default_value) { grt::DictRef dict = get_root()->state(); return dict.get_double(domain + ":" + name, default_value); } /** * Returns the value for a state given by name as bool, if it exists or the default value if not. */ bool WBContext::read_state(const std::string &name, const std::string &domain, const bool &default_value) { grt::DictRef dict = get_root()->state(); return dict.get_int(domain + ":" + name, default_value ? 1 : 0) == 1; } /** * Returns the value for a state given by name as ValueRef, if it exists or the default value if not. */ grt::ValueRef WBContext::read_state(const std::string &name, const std::string &domain) { grt::DictRef dict = get_root()->state(); return dict.get(domain + ":" + name); } /** * Stores the given string state value in the grt tree. */ void WBContext::save_state(const std::string &name, const std::string &domain, const std::string &value) { grt::DictRef dict = get_root()->state(); // Set new value for the given state name in that domain. dict.gset(domain + ":" + name, value); } /** * Stores the given int state value in the grt tree. */ void WBContext::save_state(const std::string &name, const std::string &domain, const int &value) { grt::DictRef dict = get_root()->state(); // Set new value for the given state name in that domain. dict.gset(domain + ":" + name, value); } /** * Stores the given double state value in the grt tree. */ void WBContext::save_state(const std::string &name, const std::string &domain, const double &value) { grt::DictRef dict = get_root()->state(); // Set new value for the given state name in that domain. dict.gset(domain + ":" + name, value); } /** * Stores the given bool state value in the grt tree. */ void WBContext::save_state(const std::string &name, const std::string &domain, const bool &value) { grt::DictRef dict = get_root()->state(); // Set new value for the given state name in that domain. dict.gset(domain + ":" + name, value ? 1 : 0); } /** * Stores the given ValueRef state value in the grt tree. */ void WBContext::save_state(const std::string &name, const std::string &domain, grt::ValueRef value) { grt::DictRef dict = get_root()->state(); // Set new value for the given state name in that domain. dict.set(domain + ":" + name, value); } //-------------------------------------------------------------------------------------------------- void WBContext::handle_notification(const std::string &name, void *sender, std::map<std::string, std::string> &info) { if (name == "GNDocumentOpened") add_recent_file(info["path"]); } //-------------------------------------------------------------------------------------------------- static struct RegisterNotifDocs_wb_context { RegisterNotifDocs_wb_context() { base::NotificationCenter::get()->register_notification("GNDocumentOpened", "modeling", "Sent when a Workbench document file is opened.", "", "path - path of the file that was opened"); base::NotificationCenter::get()->register_notification("GNAppClosing", "application", "Sent right before Workbench closes.", "", ""); } } initdocs_wb_context;