backend/wbprivate/workbench/wb_context_ui_home.cpp (894 lines of code) (raw):

/* * Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <glib/gstdio.h> #include "base/file_functions.h" #include "base/file_utilities.h" #include "base/string_utilities.h" #include "base/log.h" #include "base/ui_form.h" #include "mforms/menu.h" #include "grt.h" #include "grts/structs.app.h" #include "grts/structs.h" #include "grt/editor_base.h" #include "workbench/wb_context_ui.h" #include "workbench/wb_command_ui.h" #include "workbench/about_box.h" #include "model/wb_context_model.h" #include "sqlide/wb_context_sqlide.h" #include "sqlide/wb_sql_editor_form.h" #include "new_server_instance_wizard.h" #include "server_instance_editor.h" #include "grtui/grtdb_connection_editor.h" #include "new_connection_wizard.h" #include "select_option_dialog.h" #include "wb_model_file.h" #include "base/data_types.h" #include "mforms/home_screen.h" #include "mforms/home_screen_connections.h" #include "mforms/home_screen_documents.h" #include "mforms/menubar.h" #include <zip.h> DEFAULT_LOG_DOMAIN(DOMAIN_WB_CONTEXT_UI); using namespace bec; using namespace wb; using namespace base; /** * Helper method to construct a human-readable server description. */ std::string get_server_info(db_mgmt_ServerInstanceRef instance) { std::string text; std::string system = instance->serverInfo().get_string("sys.system"); if (instance->serverInfo().get_int("remoteAdmin")) text = strfmt("Host: %s Type: %s", instance->loginInfo().get_string("ssh.hostName").c_str(), system.c_str()); else { if (instance->serverInfo().get_int("windowsAdmin") /* || system == "Windows"*/) { std::string host = instance->loginInfo().get_string("wmi.hostName"); if (host == "localhost" || host.empty() || host == "127.0.0.1") { text = "Local Type: Windows"; } else text = "Host: " + host + " Type: Windows"; } else { std::string host = instance->connection().is_valid() ? instance->connection()->parameterValues().get_string("hostName") : "Invalid"; if (host == "localhost" || host.empty() || host == "127.0.0.1") { text = strfmt("Local Type: %s", system.c_str()); } else text = strfmt("Host: %s Type: DB Only", host.c_str()); } } return text; } //-------------------------------------------------------------------------------------------------- template <class T> void get_groups_for_movement(grt::ListRef<T> items, const grt::ValueRef &object, std::vector<std::string> &groups) { grt::Ref<T> item = grt::Ref<T>::cast_from(object); std::string item_name = item->name(); size_t current_group_separator_position = item_name.find("/"); std::string current_group = ""; if (current_group_separator_position != std::string::npos) { current_group = item_name.substr(0, current_group_separator_position); groups.push_back("*Ungrouped*"); } for (typename grt::ListRef<T>::const_iterator end = items.end(), inst = items.begin(); inst != end; ++inst) { std::string name = (*inst)->name(); size_t group_separator_position = name.find("/"); if (group_separator_position != std::string::npos) { std::string group_name = name.substr(0, group_separator_position); bool found = false; for (std::vector<std::string>::iterator end = groups.end(), index = groups.begin(); index != end && !found; index++) { found = (*index).compare(0, group_separator_position, group_name) == 0; } if (!found && group_name != current_group) groups.push_back(group_name); } } } template <class T> bool validate_group_for_movement(grt::ListRef<T> items, const grt::ValueRef &object, std::string group) { bool ret_val = false; // Validates the group is valid... size_t group_position = group.find("/"); if (group.length() == 0) Utilities::show_warning(_("Move To Group"), _("You must select the target group from the list or type a new group."), _("Ok")); else if (group_position != std::string::npos) Utilities::show_warning(_("Move To Group"), _("The selected group is invalid, should not contain the \"/\" character."), _("Ok")); else { // Gets the connection and connection list... grt::Ref<T> item = grt::Ref<T>::cast_from(object); // Creates the new connection name... std::string item_name = item->name(); std::string new_item_name = ""; group_position = item_name.find("/"); if (group == "*Ungrouped*") new_item_name = item_name.substr(group_position + 1); else { if (group_position != std::string::npos) new_item_name = group + "/" + item_name.substr(group_position + 1); else new_item_name = group + "/" + item_name; } size_t item_index = bec::find_list_ref_item_position<T>(items, new_item_name, MatchAny, NULL, FindFull); if (item_index != grt::BaseListRef::npos) Utilities::show_warning( _("Move To Group"), _("Unable to perform the movement as there's an entry with the same name in the target group"), _("Ok")); else ret_val = true; } return ret_val; } template <class T> void update_item_group(const grt::ValueRef &object, std::string group) // At this point the movement is considered valid so we just do it... { grt::Ref<T> item = grt::Ref<T>::cast_from(object); std::string item_name = item->name(); size_t current_group_separator_position = item_name.find("/"); std::string new_item_name = ""; if (group == "*Ungrouped*") new_item_name = item_name.substr(current_group_separator_position + 1); else { if (current_group_separator_position != std::string::npos) new_item_name = group + "/" + item_name.substr(current_group_separator_position + 1); else new_item_name = group + "/" + item_name; } item->name(new_item_name); } template <class T> void move_item_to_group(std::string group, grt::ListRef<T> items, const grt::ValueRef &object) { size_t current_item_index = 0, current_group_index = 0, sibling_index = 0, target_index = 0; bool sibling_move = false; bool item_move = false; // To ensure the grouping order is maintained after a move to group operation it is needed to consider // - If the item being moved is in a group or not // - If the item is being moved to a group or not // - Removing the first item from a group may affect the group ordering, to prevent that, the next item in the group // is being moved to the position of the first item // - When moved to a group, the item will be placed after the last element in the group // - Moving to a new group or ungrouping the item doesn't affect it's position on the list // grt::Type object_type = object.type(); grt::Ref<T> item = grt::Ref<T>::cast_from(object); std::string item_name = item->name(); std::string item_group = ""; size_t group_indicator_position = item_name.find("/"); // Gets the position of the item being moved... current_item_index = find_list_ref_item_position<T>(items, item_name); if (group_indicator_position != std::string::npos) { item_group = item_name.substr(0, group_indicator_position + 1); // Gets the position of the first element on the group current_group_index = find_list_ref_item_position<T>(items, item_group); if (current_item_index == current_group_index) { sibling_index = find_list_ref_item_position<T>(items, item_group, MatchAfter, &item); if (sibling_index != grt::BaseListRef::npos) sibling_move = true; } } if (group != "*Ungrouped*") { // Gets the position of the last element on the target group if exists ( happily the function does that when passing // MatchBefore without a reference ) std::string tmp_group = group + "/"; target_index = find_list_ref_item_position<T>(items, tmp_group, MatchLast); if (target_index != grt::BaseListRef::npos) { item_move = true; if (target_index < current_item_index) target_index++; } } if (sibling_move) { items.reorder(sibling_index, current_item_index); if (sibling_index > current_item_index) current_item_index++; } if (item_move) items.reorder(current_item_index, target_index); update_item_group<T>(object, group); } //-------------------------------------------------------------------------------------------------- void WBContextUI::show_about() { AboutBox::show_about(_wb->get_root()->info()->edition()); } //-------------------------------------------------------------------------------------------------- /** * Creates the main home screen (Workbench Central, Workspace) if not yet done and docks it to * the main application window. */ void WBContextUI::show_home_screen() { if (_home_screen != nullptr) { _home_screen->showSection(0); mforms::App::get()->select_view(_home_screen); return; } _initializing_home_screen = (_home_screen == NULL); if (_home_screen == NULL) { // The home screen and its content is freed in AppView::close() during undock_view(...). _home_screen = mforms::manage(new mforms::HomeScreen, false); _home_screen->set_menubar(mforms::setReleaseOnAdd(_command_ui->create_menubar_for_context(WB_CONTEXT_HOME_GLOBAL))); _home_screen->onHomeScreenAction = std::bind(&WBContextUI::handle_home_action, this, std::placeholders::_1, std::placeholders::_2); _home_screen->handleContextMenu = std::bind(&WBContextUI::handle_home_context_menu, this, std::placeholders::_1, std::placeholders::_2); // now we have to add sections _connectionsSection = mforms::manage(new mforms::ConnectionsSection(_home_screen)); _connectionsSection->set_name("Home Screen Connections Section"); _connectionsSection->setInternalName("homeScreenConnectionsSection"); _connectionsSection->showWelcomeHeading(bec::GRTManager::get()->get_app_option_int("HomeScreen:HeadingMessage", 1) == 1); _connectionsSection->getConnectionInfoCallback = std::bind([=] (const std::string &connectionId) -> mforms::anyMap { return connectionToMap(getConnectionById(connectionId)); }, std::placeholders::_1); _home_screen->addSection(_connectionsSection); _documentsSection = mforms::manage(new mforms::DocumentsSection(_home_screen)); _documentsSection->set_name("Home Screen Documents Section"); _documentsSection->setInternalName("homeScreenDocumentsSection"); _home_screen->addSection(_documentsSection); _home_screen->addSectionEntry("Migration Section", "sidebar_migration.png", [this]() { logInfo("Opening Migration Wizard...\n"); _wb->add_new_plugin_window("wb.migration.open", "Migration Wizard"); }, false); _home_screen->updateColors(); _home_screen->updateIcons(); // Setup context menus. mforms::Menu *menu; { std::list<std::string> groups; bec::ArgumentPool argument_pool; _wb->update_plugin_arguments_pool(argument_pool); groups.push_back("Menu/Home/Connections"); bec::MenuItemList pitems = bec::GRTManager::get()->get_plugin_context_menu_items(groups, argument_pool); if (!pitems.empty()) { menu = mforms::manage(new mforms::Menu()); menu->add_items_from_list(pitems); _home_screen->set_menu(menu, HomeMenuConnectionGeneric); } } menu = mforms::manage(new mforms::Menu()); menu->add_item(_("Open Connection"), "open_connection"); menu->add_item(_("Edit Connection..."), "edit_connection"); menu->add_separator(); menu->add_item(_("Move to Group..."), "move_connection_to_group"); { std::list<std::string> groups; bec::ArgumentPool argument_pool; _wb->update_plugin_arguments_pool(argument_pool); argument_pool.add_entries_for_object("selectedConnection", db_mgmt_ConnectionRef(grt::Initialized), "db.mgmt.Connection"); groups.push_back("Menu/Home/Connections"); bec::MenuItemList pitems = bec::GRTManager::get()->get_plugin_context_menu_items(groups, argument_pool); if (!pitems.empty()) { menu->add_separator(); menu->add_items_from_list(pitems); } } menu->add_separator(); menu->add_item(_("Move To Top"), "move_connection_to_top"); menu->add_item(_("Move Up"), "move_connection_up"); menu->add_item(_("Move Down"), "move_connection_down"); menu->add_item(_("Move To End"), "move_connection_to_end"); menu->add_separator(); menu->add_item(_("Delete Connection..."), "delete_connection"); menu->add_item(_("Delete All Connections..."), "delete_connection_all"); _home_screen->set_menu(menu, HomeMenuConnection); menu->release(); menu = mforms::manage(new mforms::Menu()); menu->add_item(_("Move To Top"), "move_connection_to_top"); menu->add_item(_("Move Up"), "move_connection_up"); menu->add_item(_("Move Down"), "move_connection_down"); menu->add_item(_("Move To End"), "move_connection_to_end"); menu->add_separator(); { std::list<std::string> groups; bec::ArgumentPool argument_pool; _wb->update_plugin_arguments_pool(argument_pool); argument_pool.add_simple_value("selectedGroupName", grt::StringRef("")); groups.push_back("Menu/Home/ConnectionGroup"); bec::MenuItemList pitems = bec::GRTManager::get()->get_plugin_context_menu_items(groups, argument_pool); if (!pitems.empty()) { menu->add_items_from_list(pitems); menu->add_separator(); } } menu->add_item(_("Delete Group..."), "delete_connection_group"); _home_screen->set_menu(menu, HomeMenuConnectionGroup); menu->release(); menu = mforms::manage(new mforms::Menu()); menu->add_item(_("Open Model"), "open_model_from_list"); { std::list<std::string> groups; bec::ArgumentPool argument_pool; _wb->update_plugin_arguments_pool(argument_pool); argument_pool.add_simple_value("selectedModelFile", grt::ValueRef()); groups.push_back("Menu/Home/ModelFiles"); bec::MenuItemList pitems = bec::GRTManager::get()->get_plugin_context_menu_items(groups, argument_pool); if (!pitems.empty()) { menu->add_separator(); for (bec::MenuItemList::const_iterator iterator = pitems.begin(); iterator != pitems.end(); iterator++) menu->add_items_from_list(pitems); } } menu->add_separator(); menu->add_item(_("Show Model File"), "show_model"); menu->add_item(_("Remove Model File from List"), "remove_model"); menu->add_item(_("Clear List"), "remove_model_all"); _home_screen->set_menu(menu, HomeMenuDocumentModel); menu->release(); menu = mforms::manage(new mforms::Menu()); menu->add_item(_("Create EER Model from Database"), "model_from_schema"); menu->add_item(_("Create EER Model from Script"), "model_from_script"); { std::list<std::string> groups; bec::ArgumentPool argument_pool; _wb->update_plugin_arguments_pool(argument_pool); argument_pool.add_simple_value("selectedModelFile", grt::ValueRef()); groups.push_back("Menu/Home/ModelFiles"); // TODO: do we need a different group for the action menu? bec::MenuItemList pitems = bec::GRTManager::get()->get_plugin_context_menu_items(groups, argument_pool); if (!pitems.empty()) { menu->add_separator(); for (bec::MenuItemList::const_iterator iterator = pitems.begin(); iterator != pitems.end(); iterator++) menu->add_items_from_list(pitems); } } _home_screen->set_menu(menu, HomeMenuDocumentModelAction); menu->release(); } mforms::App::get()->dock_view(_home_screen, "maintab"); std::string error; try { refresh_home_documents(); refresh_home_connections(); } catch (const std::exception *exc) { error = exc->what(); } catch (const std::exception &exc) { error = exc.what(); } catch (...) { error = "(unknown)"; } if (!error.empty()) { std::string message = base::strfmt(_("Error while setting up home screen. The error message is: %s"), error.c_str()); logError("%s\n", message.c_str()); mforms::Utilities::show_error(_("Home Screen Error"), message, _("Close")); } _home_screen->setup_done(); if (!_oldAuthList.empty()) { std::string tmp; std::vector<db_mgmt_ConnectionRef>::const_iterator it; for (it = _oldAuthList.begin(); it != _oldAuthList.end(); ++it) { tmp.append("\n"); tmp.append((*it)->name()); tmp.append(" user name:"); tmp.append((*it)->parameterValues().get_string("userName")); } int rc = mforms::Utilities::show_warning( "Connections using old authentication protocol found", "While loading the stored connections some were found to use the old authentication protocol. " "This is no longer supported by MySQL Workbench and the MySQL client library. Click on the \"More Info\" button " "for a more detailed explanation.\n\n" "With this change it is essential that user accounts are converted to the new password storage or you can no " "longer connect with MySQL Workbench using these accounts.\n\n" "The following connections are affected:\n" + tmp, "Change", "Ignore", "More Info"); if (rc == mforms::ResultOther) { mforms::Utilities::open_url( "http://mysqlworkbench.org/2014/03/" "mysql-workbench-6-1-updating-accounts-using-the-old-pre-4-1-1-authentication-protocol/"); } else if (rc == mforms::ResultOk) { std::vector<db_mgmt_ConnectionRef>::const_iterator it; for (it = _oldAuthList.begin(); it != _oldAuthList.end(); ++it) { if ((*it).is_valid()) { if ((*it)->parameterValues().has_key("useLegacyAuth")) (*it)->parameterValues().remove("useLegacyAuth"); } } _oldAuthList.clear(); } } _home_screen->showSection(0); _initializing_home_screen = false; } //-------------------------------------------------------------------------------------------------- db_mgmt_ConnectionRef WBContextUI::getConnectionById(const std::string &id) { grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); for (std::size_t i = 0; i < connections->count(); ++i) { if (connections[i].id() == id) return connections[i]; } return db_mgmt_ConnectionRef(); } //-------------------------------------------------------------------------------------------------- static bool isSSHConnection(const db_mgmt_ConnectionRef &connection) { if (connection.is_valid()) { std::string driver = connection->driver().is_valid() ? connection->driver()->name() : ""; return (driver == "MysqlNativeSSH"); } return false; } //-------------------------------------------------------------------------------------------------- /** * Determines if the given connection is a local connection (i.e. to the current box). */ static bool isLocalConnection(const db_mgmt_ConnectionRef &connection) { if (connection.is_valid()) { std::string hostname = connection->parameterValues().get_string("hostName"); if (!isSSHConnection(connection) && (hostname == "localhost" || hostname.empty() || hostname == "127.0.0.1")) return true; } return false; } anyMap WBContextUI::connectionToMap(db_mgmt_ConnectionRef connection) { anyMap output; if (!connection.is_valid()) return output; db_mgmt_ServerInstanceRef instance; grt::ListRef<db_mgmt_ServerInstance> instances = _wb->get_root()->rdbmsMgmt()->storedInstances(); for (grt::ListRef<db_mgmt_ServerInstance>::const_iterator iterator = instances.begin(); iterator != instances.end(); iterator++) { if ((*iterator)->connection() == connection) { instance = *iterator; break; } } output = grt::convert(connection->parameterValues()); if (instance.is_valid() && instance->serverInfo().is_valid()) output.insert({"serverInfo", grt::convert(instance->serverInfo())}); else output.insert({"serverInfo", base::any()}); if (instance.is_valid() && instance->loginInfo().is_valid()) output.insert({"loginInfo", grt::convert(instance->loginInfo())}); else output.insert({"loginInfo", base::any()}); output.insert({"isLocalConnection", isLocalConnection(connection)}); output.insert({"isSSHConnection", isSSHConnection(connection)}); output.insert({"hostIdentifier", std::string(connection->hostIdentifier())}); std::string name = connection->name(); output.insert({"name", name}); return output; } //-------------------------------------------------------------------------------------------------- /** * Removes a connection from the stored connections list along with all associated data * (including its server instance entry). */ void WBContextUI::remove_connection(const db_mgmt_ConnectionRef &connection) { grt::BaseListRef args(true); args->insert_unchecked(connection); grt::ValueRef result = grt::GRT::get()->call_module_function("Workbench", "deleteConnection", args); } //-------------------------------------------------------------------------------------------------- void WBContextUI::handle_home_context_menu(const base::any &object, const std::string &action) { if (action == "open_connection") { handle_home_action(HomeScreenAction::ActionOpenConnectionFromList, object); } else if (action == "delete_connection") { db_mgmt_ConnectionRef connection = getConnectionById(object.as<std::string>()); if (!connection.is_valid()) { logError("Invalid connection has been found during action: %s\n", action.c_str()); return; } std::string name = connection->name(); std::string title; std::string warning; title = _("Delete Connection"); warning = strfmt(_("Do you want to delete connection %s?"), name.c_str()); int answer = Utilities::show_warning(title, warning, _("Delete"), _("Cancel")); if (answer == mforms::ResultOk) { remove_connection(connection); refresh_home_connections(); } } else if (action == "manage_connections" || action == "edit_connection") { handle_home_action(HomeScreenAction::ActionManageConnections, object); } else if (action == "move_connection_to_top") { // We enter here for both: groups and connections. The object for groups must be a StringRef // with the name of the group. For connections it's the connection ref. // Similar for the other move_* actions. db_mgmt_ConnectionRef val = getConnectionById(object.as<std::string>()); if (!val.is_valid()) { logError("Invalid connection has been found during action: %s\n", action.c_str()); return; } grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); bec::move_list_ref_item<db_mgmt_Connection>(connections, val, MoveTop); refresh_home_connections(false); } else if (action == "move_connection_up") { grt::ValueRef val = getConnectionById(object.as<std::string>()); if (!val.is_valid()) { logError("Invalid connection has been found during action: %s\n", action.c_str()); return; } grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); bec::move_list_ref_item<db_mgmt_Connection>(connections, val, MoveUp); refresh_home_connections(false); } else if (action == "move_connection_down") { db_mgmt_ConnectionRef val = getConnectionById(object.as<std::string>()); if (!val.is_valid()) { logError("Invalid connection has been found during action: %s\n", action.c_str()); return; } grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); bec::move_list_ref_item<db_mgmt_Connection>(connections, val, MoveDown); refresh_home_connections(false); } else if (action == "move_connection_to_end") { db_mgmt_ConnectionRef val = getConnectionById(object.as<std::string>()); if (!val.is_valid()) { logError("Invalid connection has been found during action: %s\n", action.c_str()); return; } grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); bec::move_list_ref_item<db_mgmt_Connection>(connections, val, MoveBottom); refresh_home_connections(false); } else if (action == "move_connection_to_group") { grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); // Create the available groups for the movement... std::vector<std::string> groups; db_mgmt_ConnectionRef val = getConnectionById(object.as<std::string>()); if (!val.is_valid()) { logError("Invalid connection has been found during action: %s\n", action.c_str()); return; } get_groups_for_movement<db_mgmt_Connection>(connections, val, groups); SelectOptionDialog dialog(_("Move To Group"), _("Pick a group to move the selected connection " "to\nor type a name to move it to a new one."), groups); dialog.set_validation_function( std::bind(&validate_group_for_movement<db_mgmt_Connection>, connections, val, std::placeholders::_1)); std::string result = dialog.run(); // At this point the movement is considered valid so we just do it. if (result != "") { move_item_to_group<db_mgmt_Connection>(result, connections, val); refresh_home_connections(); } } else if (action == "delete_connection_group" || action == "internal_delete_connection_group") { std::string group = object; int answer = mforms::ResultOk; // Internal deletion does not require the prompt if (action == "delete_connection_group") { std::string text = strfmt(_("Do you really want to delete all the connections in group: %s?"), base::left(group, (unsigned int)group.length() - 1).c_str()); answer = Utilities::show_warning(_("Delete Connection Group"), text, _("Delete"), _("Cancel")); } if (answer == mforms::ResultOk) { grt::BaseListRef args(true); std::string val = object; args->insert_unchecked(grt::StringRef(val)); grt::ValueRef result = grt::GRT::get()->call_module_function("Workbench", "deleteConnectionGroup", args); // Internal deletion does not require the UI update if (action == "delete_connection_group") refresh_home_connections(); } } else if (action == "delete_connection_all") { std::string text = _("Do you really want to delete all defined connections?"); int answer = Utilities::show_warning(_("Delete All Connections"), text, _("Delete"), _("Cancel")); if (answer == mforms::ResultOk) { grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); while (connections->count() > 0) remove_connection(connections[0]); refresh_home_connections(); } } else if (action == "open_model_from_list") { handle_home_action(HomeScreenAction::ActionOpenEERModelFromList, object); } else if (action == "model_from_schema") { handle_home_action(HomeScreenAction::ActionNewModelFromDB, object); } else if (action == "model_from_script") { handle_home_action(HomeScreenAction::ActionNewModelFromScript, object); } else if (action == "show_model") { std::string file = object; mforms::Utilities::reveal_file(file); } else if (action == "remove_model") { std::string file = object; _wb->get_root()->options()->recentFiles()->remove(grt::StringRef(file)); bool remove_auto_save = false; if (file.size() > 5 && file.substr(file.size() - 5) == ".mwbd") remove_auto_save = true; else { std::map<std::string, std::string> auto_save_models(WBContextModel::auto_save_files()); if (auto_save_models.find(file) != auto_save_models.end()) { file = auto_save_models[file]; remove_auto_save = true; } else if (auto_save_models.find(base::basename(file)) != auto_save_models.end()) { file = auto_save_models[base::basename(file)]; remove_auto_save = true; } } if (remove_auto_save) { try { base::remove_recursive(file); // Refreshes the list of autosaved files... WBContextModel::detect_auto_save_files(_wb->get_auto_save_dir()); } catch (std::exception &exc) { logError("Error removing model file %s: %s\n", file.c_str(), exc.what()); } } refresh_home_documents(); } else if (action == "remove_model_all") { std::string text = _("Do you want to remove all entries from the model list?"); int answer = Utilities::show_warning(_("Clear Model Entry List"), text, _("Delete"), _("Cancel")); if (answer == mforms::ResultOk) { grt::StringListRef file_names(_wb->get_root()->options()->recentFiles()); for (ssize_t index = file_names->count() - 1; index >= 0; index--) { if (g_str_has_suffix(file_names[index].c_str(), ".mwb")) file_names->remove(index); } refresh_home_documents(); } } else { bec::ArgumentPool argument_pool; _wb->update_plugin_arguments_pool(argument_pool); if (object.is<std::string>()) { std::string val = object; db_mgmt_ConnectionRef connection = getConnectionById(val); if (connection.is_valid()) argument_pool.add_entries_for_object("selectedConnection", connection); else if (base::hasSuffix(val, ".mwb")) argument_pool.add_simple_value("selectedModelFile", grt::StringRef(val)); // assume a model file else argument_pool.add_simple_value("selectedGroupName", grt::StringRef(val)); // assume a connection group name } get_command_ui()->activate_command(action, argument_pool); } } //-------------------------------------------------------------------------------------------------- void WBContextUI::start_plugin(const std::string &title, const std::string &command, const bec::ArgumentPool &defaults, bool force_external) { try { std::string message_title = base::strfmt(_("Starting %s"), title.c_str()); GUILock lock(_wb, message_title, _("Please stand by while the plugin is started...")); if (base::hasPrefix(command, "plugin:")) { _wb->execute_plugin(command.substr(7, command.length()), defaults); } else if (base::hasPrefix(command, "browse:")) show_web_page(command.substr(7, command.length()), !force_external); else if (base::hasPrefix(command, "http://")) show_web_page(command, false); } catch (const std::exception &exc) { std::string message = base::strfmt(_("Could not open link or plugin. The error message is: %s"), exc.what()); logError("%s\n", message.c_str()); mforms::Utilities::show_error(_("Open Plugin Error"), message, _("Close")); } } //-------------------------------------------------------------------------------------------------- void WBContextUI::handle_home_action(mforms::HomeScreenAction action, const base::any &anyObject) { switch (action) { case HomeScreenAction::ActionNone: break; case HomeScreenAction::ActionOpenConnectionFromList: { if (_processing_action_open_connection) break; _processing_action_open_connection = true; db_mgmt_ConnectionRef object; if (!anyObject.isNull()) object = getConnectionById(anyObject.as<std::string>()); if (object.is_valid()) { db_mgmt_ConnectionRef connection(db_mgmt_ConnectionRef::cast_from(object)); _wb->_frontendCallbacks->show_status_text("Opening SQL Editor..."); _wb->add_new_query_window(connection); } _processing_action_open_connection = false; break; } case HomeScreenAction::ActionFilesWithConnection: { if (_processing_action_open_connection) break; _processing_action_open_connection = true; HomeScreenDropFilesInfo dInfo; if (!anyObject.isNull()) dInfo = anyObject; if (dInfo.files.size() != 0) { db_mgmt_ConnectionRef connection = getConnectionById(dInfo.connectionId); _wb->_frontendCallbacks->show_status_text("Opening files in new SQL Editor ..."); std::shared_ptr<SqlEditorForm> form = _wb->add_new_query_window(connection, false); if (form) { for (auto &it : dInfo.files) form->open_file(it, true); form->update_title(); } } _processing_action_open_connection = false; break; } case HomeScreenAction::ActionNewConnection: { NewConnectionWizard wizard(_wb, _wb->get_root()->rdbmsMgmt()); wizard.set_title("Setup New Connection"); wizard.run(); refresh_home_connections(); break; } case HomeScreenAction::ActionManageConnections: { db_mgmt_ConnectionRef object; if (!anyObject.isNull()) object = getConnectionById(anyObject.as<std::string>()); ServerInstanceEditor editor(_wb->get_root()->rdbmsMgmt()); _wb->_frontendCallbacks->show_status_text("Connection Manager Opened"); editor.run(db_mgmt_ConnectionRef::cast_from(object)); _wb->_frontendCallbacks->show_status_text(""); refresh_home_connections(); break; } case HomeScreenAction::ActionMoveConnection: { HomeScreenDropInfo dropInfo = anyObject; grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); if (dropInfo.valueIsConnectionId) { db_mgmt_ConnectionRef connection = getConnectionById(dropInfo.value); move_list_ref_item<db_mgmt_Connection>(connections, connection, dropInfo.to); refresh_home_connections(false); } else { std::string group = dropInfo.value; move_list_ref_item<db_mgmt_Connection>(connections, grt::StringRef(group), dropInfo.to); refresh_home_connections(false); } break; } case HomeScreenAction::ActionMoveConnectionToGroup: { HomeScreenDropInfo dropInfo = anyObject; grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); db_mgmt_ConnectionRef connection = getConnectionById(dropInfo.value); if (dropInfo.group != "" && connection.is_valid()) { move_item_to_group<db_mgmt_Connection>(dropInfo.group, connections, connection); refresh_home_connections(false); } break; } case HomeScreenAction::ActionSetupRemoteManagement: { db_mgmt_ConnectionRef object = getConnectionById(anyObject.as<std::string>()); NewServerInstanceWizard wizard(_wb, object); _wb->_frontendCallbacks->show_status_text("Started Management Setup Wizard"); wizard.run_modal(); _wb->_frontendCallbacks->show_status_text(""); _wb->save_instances(); refresh_home_connections(); break; } case HomeScreenAction::ActionEditSQLScript: { break; } case HomeScreenAction::ActionOpenEERModel: { // Note: wb->open_document has an own GUILock, so we must not set another one here. std::string filename = _wb->_frontendCallbacks->show_file_dialog("open", _("Open Workbench Model"), "mwb"); if (!filename.empty()) _wb->open_document(filename); else _wb->_frontendCallbacks->show_status_text("Cancelled"); break; } case HomeScreenAction::ActionOpenEERModelFromList: { // Note: wb->open_document has an own GUILock, so we must not set another one here. if (!anyObject.isNull()) { std::string path = anyObject; _wb->_frontendCallbacks->show_status_text(strfmt("Opening %s...", path.c_str())); _wb->open_document(path); } break; } case HomeScreenAction::ActionNewEERModel: _wb->new_document(); break; case HomeScreenAction::ActionNewModelFromDB: { _wb->new_document(); if (_wb->get_document().is_valid()) { ArgumentPool args; // delete the default schema if (_wb->get_document()->physicalModels()[0]->catalog()->schemata().count() > 0) _wb->get_document()->physicalModels()[0]->catalog()->schemata().remove(0); _wb->update_plugin_arguments_pool(args); args.add_entries_for_object("activeCatalog", _wb->get_document()->physicalModels()[0]->catalog(), "db.Catalog"); _wb->execute_plugin("db.plugin.database.rev_eng", args); // if the model is still empty, just close it if (_wb->get_document()->physicalModels()[0]->catalog()->schemata().count() == 0) _wb->close_document(); } else _wb->_frontendCallbacks->show_status_text("Error creating document"); break; } case HomeScreenAction::ActionNewModelFromScript: { _wb->new_document(); if (_wb->get_document().is_valid()) { ArgumentPool args; // delete the default schema if (_wb->get_document()->physicalModels()[0]->catalog()->schemata().count() > 0) _wb->get_document()->physicalModels()[0]->catalog()->schemata().remove(0); _wb->update_plugin_arguments_pool(args); args.add_entries_for_object("activeCatalog", _wb->get_document()->physicalModels()[0]->catalog(), "db.Catalog"); _wb->execute_plugin("db.mysql.plugin.import.sql", args); // if the model is still empty, just close it if (_wb->get_document()->physicalModels()[0]->catalog()->schemata().count() == 0) _wb->close_document(); } else _wb->_frontendCallbacks->show_status_text("Error creating document"); break; } case HomeScreenAction::ActionOpenBlog: _command_ui->activate_command("builtin:web_mysql_blog"); break; case HomeScreenAction::ActionOpenDocs: _command_ui->activate_command("builtin:web_mysql_docs"); break; case HomeScreenAction::ActionOpenForum: _command_ui->activate_command("builtin:web_mysql_forum"); break; case HomeScreenAction::CloseWelcomeMessage: _connectionsSection->showWelcomeHeading(false); bec::GRTManager::get()->set_app_option("HomeScreen:HeadingMessage", grt::IntegerRef(0)); break; case HomeScreenAction::RescanLocalServers: _wb->execute_plugin("wb.tools.createMissingLocalConnections", ArgumentPool()); break; default: logError("Unknown Action.\n"); } } //-------------------------------------------------------------------------------------------------- void WBContextUI::refresh_home_connections(bool clear_state) { if (!_home_screen) return; grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns()); std::map<std::string, std::string> auto_save_files = WBContextSQLIDE::auto_save_sessions(); _connectionsSection->clear_connections(clear_state); // If there are no connections defined yet then create entries for all currently installed // local servers (only if this is the first run, after application start). if (_initializing_home_screen && (connections->count() == 0)) { grt::Module *module = grt::GRT::get()->get_module("Workbench"); if (module == NULL) throw std::logic_error("Internal error: can't find Workbench module."); grt::StringListRef arguments(grt::Initialized); module->call_function("createInstancesFromLocalServers", arguments); } std::vector<db_mgmt_ConnectionRef> invalid_connections; std::map<std::string, std::string> invalid_connection_ids; std::vector<db_mgmt_ConnectionRef> oldAuthList; for (grt::ListRef<db_mgmt_Connection>::const_iterator end = connections.end(), inst = connections.begin(); inst != end; ++inst) { // Any connection with NULL driver will be considered invalid and so deleted if (!(*inst)->driver().is_valid()) { invalid_connections.push_back(*inst); invalid_connection_ids.insert(std::pair<std::string, std::string>((*inst)->id(), "")); } else { grt::DictRef dict((*inst)->parameterValues()); if (dict.has_key("useLegacyAuth")) { if ((size_t)dict.get_int("useLegacyAuth", 0) == 0) (*inst)->parameterValues().remove("useLegacyAuth"); // If it's not used (old val), we silently remove it. else oldAuthList.push_back(*inst); } { std::string host_entry; if ((*inst)->driver().is_valid() && (*inst)->driver()->name() == "MysqlNativeSSH") host_entry = dict.get_string("sshUserName") + "@" + dict.get_string("sshHost"); else { if ((*inst)->driver()->name() == "MySQLX") { std::string editor = ""; if (dict.get_int("editorLanguage") == 0) editor = "sql"; else if (dict.get_int("editorLanguage") == 1) editor = "js"; else if (dict.get_int("editorLanguage") == 2) editor = "py"; host_entry = strfmt("%s:///%s:%i", editor.c_str(), dict.get_string("hostName").c_str(), (int)dict.get_int("port", 3306)); } else host_entry = strfmt("%s:%i", dict.get_string("hostName").c_str(), (int)dict.get_int("port", 3306)); if ((*inst)->driver().is_valid() && (*inst)->driver()->name() == "MysqlNativeSocket") { // TODO: what about the default for sockets (only have a default for the pipe name)? std::string socket = dict.get_string("socket", "MySQL"); // socket or pipe host_entry = "Localhost via pipe " + socket; } } std::string title = *(*inst)->name(); if (auto_save_files.find((*inst)->id()) != auto_save_files.end()) title += " (auto saved)"; _connectionsSection->addConnection((*inst).id(), title, host_entry, dict.get_string("userName"), dict.get_string("schema")); } } } _connectionsSection->updateFocusableAreas(); _oldAuthList = oldAuthList; // Deletes invalid connections for (std::vector<db_mgmt_ConnectionRef>::const_iterator iterator = invalid_connections.begin(); iterator != invalid_connections.end(); ++iterator) { logWarning("Invalid connection detected %s, deleting it\n", (*iterator)->name().c_str()); remove_connection(*iterator); } // XXXX TEST PROBLEM // _wb->save_connections(); _wb->save_instances(); } //-------------------------------------------------------------------------------------------------- void WBContextUI::refresh_home_documents() { if (!_home_screen) return; _documentsSection->clear_documents(); std::map<std::string, std::string> auto_save_models(WBContextModel::auto_save_files()); // find list of auto-saved models that are not in the list of recent files for (std::map<std::string, std::string>::const_iterator iter = auto_save_models.begin(); iter != auto_save_models.end(); ++iter) { bool found = false; for (grt::StringListRef::const_iterator end = _wb->get_root()->options()->recentFiles().end(), f = _wb->get_root()->options()->recentFiles().begin(); f != end; ++f) { if (*f == iter->first || strcmp(base::basename((*f)).c_str(), iter->first.c_str()) == 0) { found = true; break; } } if (!found) _documentsSection->add_document(iter->second, 0, wb::ModelFile::read_comment(iter->first.c_str()), 0); } // Create list entries for each recently opened file. // First count how many there are so we can add examples if the list is empty. int model_count = 0; grt::StringListRef recentFiles(_wb->get_root()->options()->recentFiles()); for (grt::StringListRef::const_iterator end = recentFiles.end(), f = recentFiles.begin(); f != end; ++f) { if (g_str_has_suffix((*f).c_str(), ".mwb")) model_count++; } int sample_models_already_shown = _wb->read_state("WBSampleModelFilesShown", "home", 0); if (sample_models_already_shown == 0) { // If there is no entry in the MRU list then scan the examples folder for the Sakila model // file and add this as initial entry, so that a new user has an easy start. if (model_count == 0) { std::list<std::string> examples_paths; #ifdef _MSC_VER examples_paths.push_back(mforms::Utilities::get_special_folder(mforms::WinProgramFilesX86) + "\\MySQL\\Samples and Examples 5.5"); examples_paths.push_back(mforms::Utilities::get_special_folder(mforms::WinProgramFiles) + "\\MySQL\\Samples and Examples 5.5"); examples_paths.push_back(bec::GRTManager::get()->get_basedir() + "/extras"); #elif defined(__APPLE__) examples_paths.push_back(bec::GRTManager::get()->get_basedir() + "/../SharedSupport"); #else examples_paths.push_back(bec::GRTManager::get()->get_basedir() + "/extras"); #endif for (std::list<std::string>::const_iterator path_iterator = examples_paths.begin(); path_iterator != examples_paths.end(); path_iterator++) { if (g_file_test(path_iterator->c_str(), G_FILE_TEST_IS_DIR)) { std::string pattern = base::makePath(*path_iterator, "*.mwb"); std::list<std::string> sample_model_files = base::scan_for_files_matching(pattern, true); for (std::list<std::string>::const_iterator iterator = sample_model_files.begin(); iterator != sample_model_files.end(); iterator++) recentFiles->insert_checked(grt::StringRef(*iterator)); } } } _wb->save_state("WBSampleModelFilesShown", "home", 1); } for (grt::StringListRef::const_iterator end = recentFiles.end(), f = recentFiles.begin(); f != end; ++f) { #ifdef _MSC_VER struct _stat stbuf; #else struct stat stbuf; #endif if (base_stat((*f).c_str(), &stbuf) < 0) _documentsSection->add_document(*f, 0, "", 0); else _documentsSection->add_document(*f, stbuf.st_mtime, wb::ModelFile::read_comment(*f), stbuf.st_size); } } //--------------------------------------------------------------------------------------------------