# Copyright (c) 2007, 2021, Oracle and/or its affiliates. # # 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 also distributed 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 included with MySQL. # 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 import os import re import sys import subprocess import threading import _thread import time import tempfile import platform import io import grt import wb_admin_export_options import wb_common from wb_common import to_unicode from wb_server_management import local_run_cmd from workbench.db_utils import QueryError, ConnectionTunnel, escape_sql_identifier from collections import deque from workbench.utils import Version from workbench.log import log_warning, log_error, log_debug from mforms import newBox, newButton, newPanel, newTextBox, newRadioButton, newLabel, newTreeView, newProgressBar, newTextEntry, newCheckBox, newScrollPanel, newTabView, newSelector from mforms import Utilities, FileChooser import mforms from wb_admin_utils import weakcb, WbAdminTabBase, WbAdminValidationConnection def local_quote_shell_token(s): if sys.platform.lower() == "win32": t = '"%s"' % s.replace('\\', '\\\\').replace('"', '\\"') else: t = '"%s"' % s.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') if t[:5] == '"\\\\\\\\': t = t[3:] t = '\"' + t return t def normalize_filename(s): s = s.replace(":", "_").replace("/", "_").replace("\\", "_") return s def get_path_to_mysqldump(): """get path to mysqldump from options""" try: path = grt.root.wb.options.options["mysqldump"] if path: if os.path.exists(path): return path if any(os.path.exists(os.path.join(p, path)) for p in os.getenv("PATH").split(os.pathsep)): return path if path != "mysqldump": log_error("mysqldump path specified in configurations is invalid: %s" % path) return None except: return None if sys.platform == "darwin": # if path is not specified, use bundled one return mforms.App.get().get_executable_path("mysqldump") elif sys.platform == "win32": return mforms.App.get().get_executable_path("mysqldump.exe") else: # if path is not specified, use bundled one path = mforms.App.get().get_executable_path("mysqldump") if path: return path # just pick default if any(os.path.exists(os.path.join(p,"mysqldump")) for p in os.getenv("PATH").split(os.pathsep)): return "mysqldump" return None def get_mysqldump_version(): path = get_path_to_mysqldump() if not path: log_error("mysqldump command was not found, please install it or configure it in Edit -> Preferences -> Administration") return None output = io.StringIO() rc = local_run_cmd('"%s" --version' % path, output_handler=output.write) output = output.getvalue() if rc or not output: log_error("Error retrieving version from %s:\n%s (exit %s)"%(path, output, rc)) return None regexp = ".*Ver ([\d.a-z]+).*" if ("Distrib" in output): regexp = ".*Distrib ([\d.a-z]+).*" s = re.match(regexp, output) if not s: log_error("Could not parse version number from %s:\n%s"%(path, output)) return None version_group = s.groups()[0] major, minor, revision = [int(i) for i in version_group.split(".")[:3]] return Version(major, minor, revision) #################################################################################################### class DumpThread(threading.Thread): class TaskData: def __init__(self, title, table_count, extra_arguments, objec_names, tables_to_ignore, make_pipe = lambda:None): """description, object_count, pipe_factory, extra_args, objects operations.append((title, len(tables), lambda schema=schema:self.dump_to_file([schema]), params, objects))""" self.title = title self.table_count = table_count self.extra_arguments = extra_arguments self.objec_names = objec_names self.tables_to_ignore = tables_to_ignore self.make_pipe = make_pipe def __init__(self, command, operations, pwd, owner, log_queue): self.owner = owner self.pwd = pwd self.logging_lock, self.log = log_queue self.is_import = False self.command = command self.operations = operations self.done = False self.progress = 0 self.status_text = "Starting" self.error_count = 0 self.process_handle = None self.abort_requested = False self.e = None threading.Thread.__init__(self) def process_db(self, respipe, extra_arguments, object_names, tables_to_ignore): pwdfilename = None tmpdir = None try: if '<' in self.command: index = self.command.find('<') params = [self.command[:index] + ' '.join(extra_arguments) + ' ' + self.command[index:]] else: params = [self.command] + extra_arguments for arg in object_names: params.append(local_quote_shell_token(arg)) strcmd = " ".join(params) logstr = strcmd.partition("--password=")[0] if platform.system() == 'Windows': pwdfile = tempfile.NamedTemporaryFile(delete=False, suffix=".cnf") pwdfilename = pwdfile.name else: tmpdir = tempfile.mkdtemp() pwdfilename = os.path.join(tmpdir, 'extraparams.cnf') os.mkfifo(pwdfilename) logstr += "--defaults-file=\"" + pwdfilename + "\" " logstr += strcmd.partition("--password=")[2] log_debug("Executing command: %s\n" % logstr) self.print_log_message("Running: " + logstr) if platform.system() != 'Windows': try: p1 = subprocess.Popen(logstr,stdout=respipe,stdin=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) except OSError as exc: log_error("Error executing command %s\n%s\n" % (logstr, exc)) import traceback traceback.print_exc() self.print_log_message("Error executing task: %s" % exc) pwdfile = open(pwdfilename, 'w') else: pwdfile = open(pwdfilename, 'w') pwdfile.write('[client]\npassword="') pwdfile.write(self.pwd.replace("\\", "\\\\")) pwdfile.write('"') # When there are tables to ignore they are added as ignored entries # On the configuration file if tables_to_ignore: pwdfile.write('\n\n[mysqldump]\n') for s, t in tables_to_ignore: line = "ignore-table=%s.%s\n" % (s,t) pwdfile.write(line) pwdfile.close() if platform.system() == 'Windows': try: info = subprocess.STARTUPINFO() info.dwFlags |= subprocess.STARTF_USESHOWWINDOW info.wShowWindow = subprocess.SW_HIDE # Command line can contain object names in case of export and filename in case of import # Object names must be in utf-8 but filename must be encoded in the filesystem encoding, # which probably isn't utf-8 in windows. log_debug("Executing command: %s\n" % logstr) p1 = subprocess.Popen(logstr, stdout=respipe, stdin=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=info, shell=logstr[0] != '"', encoding='utf8') except OSError as exc: log_error("Error executing command %s\n%s\n" % (logstr, exc)) import traceback traceback.print_exc() self.print_log_message("Error executing task: %s" % exc) p1 = None # finally: # pass self.process_handle = p1 while p1 and p1.poll() == None and not self.abort_requested: err = to_unicode(p1.stderr.read()) if err != "": log_error("Error from task: %s\n" % err) self.print_log_message(err) if 'Access denied for user' in err: self.e = wb_common.InvalidPasswordError('Wrong username/password!') result = "" except Exception as exc: import traceback traceback.print_exc() log_error("Error executing task: %s\n" % exc) self.print_log_message("Error executing task: %s" % exc) finally: pass if pwdfilename: os.remove(pwdfilename) if platform.system() != 'Windows' and tmpdir: os.rmdir(tmpdir) err = to_unicode(p1.stderr.read()) if err != "": result += err exitcode = p1.poll() if exitcode != 0: log_warning("Task exited with code %s\n" % exitcode) self.print_log_message("Operation failed with exitcode " + str(exitcode)) else: log_debug("Task exited with code %s\n" % exitcode) self.print_log_message("") if result: log_debug("Task output: %s\n" % result) self.print_log_message(result) return p1.poll() def kill(self): self.abort_requested = True if self.process_handle: if platform.system() == 'Windows': cmd = "taskkill /F /T /PID %i" % self.process_handle.pid log_debug("Killing task: %s\n" % cmd) subprocess.Popen(cmd , shell=True) else: import signal try: log_debug("Sending SIGTERM to task %s\n" % self.process_handle.pid) os.kill(self.process_handle.pid, signal.SIGTERM) except OSError as exc: log_error("Exception sending SIGTERM to task: %s\n" % exc) self.print_log_message("kill task: %s" % str(exc)) def print_log_message(self,message): if message: self.logging_lock.acquire() self.log.append(message) self.logging_lock.release() def run(self): try: self.progress = 0 tables_processed = 0.0 tables_total = 0.0 # for title, count, make_pipe, args, objs in self.operations: for task in self.operations: tables_total += task.table_count or 1 # for title, table_count, make_pipe, arguments, objects in self.operations: for task in self.operations: self.print_log_message(time.strftime('%X ') + to_unicode(task.title)) tables_processed += task.table_count or 1 pipe = task.make_pipe() exitcode = self.process_db(pipe, task.extra_arguments, task.objec_names, task.tables_to_ignore) if exitcode == 0: if self.is_import: self.status_text = "%i of %i imported." % (tables_processed, tables_total) else: self.status_text = "%i of %i exported." % (tables_processed, tables_total) else: self.owner.fail_callback() self.error_count += 1 if self.abort_requested: break self.progress = float(tables_processed) / tables_total # print self.progress # Emulate slow dump # import time # time.sleep(1) except Exception as exc: import traceback traceback.print_exc() self.print_log_message("Error executing task %s" % exc ) # finally: if not self.abort_requested: self.progress = 1 self.done = True class TableListModel(object): def __init__(self): self.tables_by_schema = {} self.selected_schemas = set() self.routines_placeholder = None def get_full_selection(self): result = [] for schema, (tables, selection) in list(self.tables_by_schema.items()): for table in selection: result.append((schema,table)) result.sort() return result def get_schema_names(self): return list(self.tables_by_schema.keys()) def set_schema_selected(self, schema, flag): tables = self.get_tables(schema) selection = self.get_selection(schema) if flag: selection.update(set(tables)) self.selected_schemas.add(schema) else: selection.clear() if schema in self.selected_schemas: self.selected_schemas.remove(schema) def set_tables_by_schema(self, tables_by_schema): self.tables_by_schema = tables_by_schema def set_routines_placeholder(self, placeholder): self.routines_placeholder = placeholder def get_tables(self, schema): tables, selection = self.tables_by_schema[schema] return tables def is_view(self, schema, table): return False def list_icon_for_table(self, schema, table): if (schema, table) == self.routines_placeholder: return "db.RoutineGroup.16x16.png" else: return "db.Table.16x16.png" def get_selection(self, schema): selection = [] if schema in list(self.tables_by_schema.keys()): tables, selection = self.tables_by_schema[schema] return selection def count_selected_tables(self): count = 0 for tlist, selected in list(self.tables_by_schema.values()): count += len(selected) return count def get_count(self): return sum([len(item[1]) for item in list(self.tables_by_schema.values())]) def get_objects_to_dump(self, include_empty_schemas=False): schemas_to_dump = [] #names = self.tables_by_schema.keys() #names.sort() schemas_with_selection = set([key for key, value in list(self.tables_by_schema.items()) if value[1]]) for schema in self.selected_schemas | schemas_with_selection: #No tables selected for schema so skip it if schema not in self.tables_by_schema.keys(): continue tables, selection = self.tables_by_schema[schema] if not selection and not include_empty_schemas: continue schemas_to_dump.append((schema, list(selection))) return schemas_to_dump def get_tables_to_ignore(self): ignore_list = [] names = list(self.tables_by_schema.keys()) names.sort() for schema in names: #No tables selected for schema so skip it if schema not in self.tables_by_schema.keys(): continue tables, selection = self.tables_by_schema[schema] if not selection or len(selection) == len(tables): continue for t in tables: if not t in selection: ignore_list.append((schema, t)) return ignore_list #################################################################################################### class WbAdminSchemaListTab(mforms.Box): def __init__(self, owner, server_profile, progress_tab, is_importing = False): super(WbAdminSchemaListTab, self).__init__(False) self.skip_data_check = False self.suspend_layout() progress_tab.operation_tab = self self.owner = owner self.progress_tab = progress_tab self.is_importing = is_importing self.dump_thread = None self.bad_password_detected = False self.server_profile = server_profile self.out_pipe = None self.schema_list = newTreeView(mforms.TreeFlatList) self.schema_list.set_name("Schema List") self.schema_list.set_min_size(-1, 150) self.schema_list.add_column(mforms.CheckColumnType, is_importing and "Import" or "Export", 40, True) self.schema_list.add_column(mforms.IconColumnType, "Schema", 300, False) self.schema_list.set_cell_edited_callback(self.schema_list_edit) self.schema_list.end_columns() self.schema_list.set_allow_sorting(True) self.table_list = newTreeView(mforms.TreeFlatList) self.table_list.set_name("Table List") self.table_list.set_min_size(-1, 150) self.table_list.add_column(mforms.CheckColumnType, is_importing and "Import" or "Export", 40, True) self.table_list.add_column(mforms.IconColumnType, "Schema Objects", 300, False) self.table_list.end_columns() self.table_list.set_allow_sorting(True) self.table_list.set_cell_edited_callback(self.table_list_edit) self.schema_list.add_changed_callback(self.schema_selected) self.set_padding(8) self.set_spacing(10) box = newBox(True) box.set_spacing(12) optionspanel = newPanel(mforms.TitledBoxPanel) if is_importing: self.export_objects_panel = None optionspanel.set_title("Import Options") optionspanel.set_name("Import Options") else: self.export_objects_panel = newPanel(mforms.TitledBoxPanel) self.export_objects_panel.set_title("Objects to Export") self.export_objects_panel.set_name("Objects To Export") optionspanel.set_title("Export Options") optionspanel.set_name("Export Options") optionsbox = newBox(False) optionsbox.set_padding(8) optionsbox.set_spacing(6) self.file_btn = newButton() self.file_btn.set_text("...") self.file_btn.set_name("File Path Browse") self.file_btn.enable_internal_padding(False) self.file_btn.set_enabled(False) self._radio_group = mforms.RadioButton.new_id() if is_importing: self.folderlabel = newLabel("Select the Dump Project Folder to import. You can do a selective restore.") self.folderradio = newRadioButton(self._radio_group) self.folderradio.set_name("Import From Dump Project Folder") self.statlabel = newLabel("Press [Start Import] to start...") self.statlabel.set_name("Press [Start Import] To Start") self.statlabel.set_name("Press Start Info") self.filelabel = newLabel("Select the SQL/dump file to import. Please note that the whole file will be imported.") self.filelabel.set_name("Select File to Import Info") self.single_transaction_check = None self.include_schema_check = None self.dump_triggers_check = None #self.dump_view_check = None self.dump_routines_check = None self.dump_events_check = None else: self.filelabel = newLabel("All selected database objects will be exported into a single, self-contained file.") self.folderlabel = newLabel("Each table will be exported into a separate file. This allows a selective restore, but may be slower.") self.folderradio = newRadioButton(self._radio_group) self.folderradio.set_name("Export To Dump Project Folder") self.statlabel = newLabel("Press [Start Export] to start...") self.statlabel.set_name("Press [Start Export] To Start") self.single_transaction_check = newCheckBox() self.single_transaction_check.set_name("Create Dump In A Single Transaction") self.include_schema_check = newCheckBox() self.include_schema_check.set_name("Include Create Schema") self.dump_triggers_check = newCheckBox() self.dump_triggers_check.set_name("Dump Triggers") #self.dump_view_check = newCheckBox() self.dump_routines_check = newCheckBox() self.dump_routines_check.set_name("Dump Stored Procedures and Functions") self.dump_events_check = newCheckBox() self.dump_events_check.set_name("Dump Events") self.filelabel.set_enabled(False) self.filelabel.set_style(mforms.SmallStyle) if is_importing: self.fileradio = newRadioButton(self._radio_group) self.fileradio.set_text("Import from Self-Contained File") self.fileradio.set_name("Import From Self-Contained File") else: self.fileradio = newRadioButton(self._radio_group) self.fileradio.set_text("Export to Self-Contained File") self.fileradio.set_name("Export To Self-Contained File") self.fileradio.set_size(260,-1) self.fileradio.add_clicked_callback(self.set_save_option) file_path = newBox(True) file_path.set_spacing(4) self.file_te = newTextEntry() self.file_te.set_name("File Path Entry") file_path.add(self.fileradio,False,True) file_path.add(self.file_te, True, True) file_path.add(self.file_btn, False, True) self.folderradio.add_clicked_callback(self.set_save_option) self.folderradio.set_active(True) self.folderradio.set_size(260,-1) self.folderlabel.set_style(mforms.SmallStyle) folder_path = newBox(True) folder_path.set_spacing(4) self.folder_te = newTextEntry() self.folder_te.set_name("Folder Path Entry") self.folder_btn = newButton() self.folder_btn.set_text("...") self.folder_btn.set_name("Folder Path Browse") self.folder_btn.enable_internal_padding(False) self.folder_btn.add_clicked_callback(self.open_folder_chooser) folder_path.add(self.folderradio, False,True) folder_path.add(self.folder_te, True, True) folder_path.add(self.folder_btn, False, True) optionsbox.add(folder_path, False, True) optionsbox.add(self.folderlabel, False, True) if is_importing: self.folder_load_btn = newButton() self.folder_load_btn.set_text("Load Folder Contents") self.folder_load_btn.set_name("Load Folder Contents") self.folder_load_btn.add_clicked_callback(self.refresh_table_list) tbox = newBox(True) tbox.add(self.folder_load_btn, False, True) optionsbox.add(tbox, False, True) optionsbox.add(file_path, False, True) optionsbox.add(self.filelabel, False, True) if self.single_transaction_check or self.dump_routines_check: export_objects_opts = mforms.newTable() export_objects_opts.set_homogeneous(True) export_objects_opts.set_padding(4) export_objects_opts.set_row_count(1) export_objects_opts.set_column_count(3) export_objects_opts.set_row_spacing(2) export_objects_opts.set_column_spacing(2) self.export_objects_panel.add(export_objects_opts) export_options = mforms.newTable() export_options.set_name("Export Options") export_options.set_homogeneous(True) export_options.set_padding(4) export_options.set_row_count(1) export_options.set_column_count(2) export_options.set_row_spacing(2) export_options.set_column_spacing(2) optionsbox.add(export_options, False, True) if self.single_transaction_check: export_options.add(self.single_transaction_check,0,1,0,1) if self.include_schema_check: export_options.add(self.include_schema_check,1,2,0,1) if self.dump_routines_check: export_objects_opts.add(self.dump_routines_check,0,1,0,1) if self.dump_events_check: export_objects_opts.add(self.dump_events_check,1,2,0,1) if self.dump_triggers_check: export_objects_opts.add(self.dump_triggers_check,2,3,0,1) self.file_te.set_enabled(False) optionspanel.add(optionsbox) selectionpanel = newPanel(mforms.TitledBoxPanel) if is_importing: selectionpanel.set_title("Select Database Objects to Import (only available for Project Folders)") selectionpanel.set_name("Database Objects To Import") else: selectionpanel.set_title("Tables to Export") selectionpanel.set_name("Tables To Export") selectionvbox = newBox(False) selectionvbox.set_padding(8) selectionvbox.set_spacing(8) selectionbox = newBox(True) selectionvbox.add(selectionbox, True, True) selectionbox.set_spacing(12) selectionbox.add(self.schema_list, True, True) selectionbox.add(self.table_list, True, True) selectionbbox = newBox(True) selectionbbox.set_spacing(8) if not is_importing: self.refresh_button = newButton() self.refresh_button.set_text("Refresh") self.refresh_button.set_name("Refresh") selectionbbox.add(self.refresh_button, False, True) self.refresh_button.add_clicked_callback(self.refresh_table_list) self.select_summary_label = newLabel("") selectionbbox.add(self.select_summary_label, True, True) self.select_all_views_btn = newButton() self.select_all_views_btn.set_text("Select Views") self.select_all_views_btn.set_name("Select Views") self.select_all_views_btn.add_clicked_callback(self.select_all_views) self.select_all_views_btn.set_enabled(False) self.select_all_btn = newButton() self.select_all_btn.set_text("Select Tables") self.select_all_btn.set_name("Select Tables") self.select_all_btn.add_clicked_callback(self.select_all_tables) self.select_all_btn.set_enabled(False) self.unselect_all_btn = newButton() self.unselect_all_btn.set_text("Unselect All") self.unselect_all_btn.set_name("Unselect All") self.unselect_all_btn.add_clicked_callback(self.unselect_all_tables) self.unselect_all_btn.set_enabled(False) self.dump_type_selector = newSelector() self.dump_type_selector.add_items(["Dump Structure and Data", "Dump Data Only", "Dump Structure Only"]); self.dump_type_selector.set_name("Dump Selector") selectionbbox.add_end(self.unselect_all_btn, False, True) selectionbbox.add_end(self.select_all_btn, False, True) selectionbbox.add_end(self.select_all_views_btn, False, True) selectionbbox.add_end(self.dump_type_selector, False, True) selectionvbox.add(selectionbbox, False, True) selectionpanel.add(selectionvbox) if is_importing: self.add(optionspanel, False, True) self.import_target_schema_panel = targetpanel = newPanel(mforms.TitledBoxPanel) targetpanel.set_title("Default Schema to be Imported To") targetpanel.set_name("Default Schema To Be Imported To") hbox = newBox(True) hbox.set_spacing(8) hbox.add(newLabel("Default Target Schema:"), False, True) self.import_target_schema = newSelector() self.import_target_schema.set_name("Target Schema Selector") hbox.add(self.import_target_schema, True, True) b = newButton() b.set_name("New") b.set_text("New...") b.add_clicked_callback(self.new_target_schema) hbox.add(b, False, True) help = newLabel("The default schema to import the dump into.\nNOTE: this is only used if the dump file doesn't contain its schema,\notherwise it is ignored.") help.set_style(mforms.SmallHelpTextStyle) hbox.add(help, False, True) hbox.set_padding(12) targetpanel.add(hbox) self.add(targetpanel, False, True) self.add(selectionpanel, True, True) if not is_importing: self.add(self.export_objects_panel, False, True) self.add(optionspanel, False, True) box = newBox(True) self.add(box, False, True) box.add(self.statlabel, True, True) box.set_spacing(8) box.set_padding(0) self.export_button = newButton() if is_importing: self.export_button.set_enabled(False) box.add_end(self.export_button, False, True) self.export_button.add_clicked_callback(self.start) if is_importing: self.file_btn.add_clicked_callback(lambda: self.open_file_chooser(mforms.OpenFile)) self.folderradio.set_text("Import from Dump Project Folder") self.export_button.set_text("Start Import") self.export_button.set_name("Start Import") else: self.file_btn.add_clicked_callback(lambda: self.open_file_chooser(mforms.SaveFile)) self.single_transaction_check.set_text("Create Dump in a Single Transaction (self-contained file only)") self.single_transaction_check.set_enabled(False) self.single_transaction_check.add_clicked_callback(self.single_transaction_clicked) self.include_schema_check.set_text("Include Create Schema") self.dump_triggers_check.set_text("Dump Triggers") #self.dump_view_check.set_text("Dump Views") self.dump_routines_check.set_text("Dump Stored Procedures and Functions") self.dump_events_check.set_text("Dump Events") self.folderradio.set_text("Export to Dump Project Folder") self.export_button.set_text("Start Export") self.export_button.set_name("Start Export") self.resume_layout() def print_log_message(self, msg): self.progress_tab.print_log_message(msg) def get_default_dump_folder(self): try: path = grt.root.wb.options.options["dumpdirectory"] or os.path.join("~", "dumps") except: path = os.path.join("~", "dumps") path = os.path.expanduser(path) return os.path.normpath(path) def schema_list_edit(self, node, col, data): if col == 0: node.set_bool(0, int(data) != 0) schema = node.get_string(1) self.table_list_model.set_schema_selected(schema, int(data)) self.schema_selected() def table_list_edit(self, node, col, data): node.set_bool(col, int(data) != 0) self.update_table_selection() if int(data): # select the schema if its not sel = self.schema_list.get_selected_node() if sel and not sel.get_bool(0): sel.set_bool(col, True) self.schema_selected() def update_table_selection(self): if not self.get_selected_schema(): return schema = self.get_selected_schema() selection = self.table_list_model.get_selection(schema) for r in range(self.table_list.count()): node = self.table_list.node_at_row(r) table_name = node.get_tag() if node.get_bool(0): selection.add(table_name) else: selection.discard(table_name) self.select_summary_label.set_text("%i tables/views selected" % self.table_list_model.count_selected_tables()) def get_selected_schema(self): sel = self.schema_list.get_selected_node() if not sel: return None return sel.get_string(1) def schema_selected(self): sel = self.schema_list.get_selected_node() self.table_list.freeze_refresh() self.table_list.clear() if not sel: self.unselect_all_btn.set_enabled(False) self.select_all_btn.set_enabled(False) self.select_all_views_btn.set_enabled(False) self.table_list.thaw_refresh() return schema = self.get_selected_schema() tables = self.table_list_model.get_tables(schema) selection = self.table_list_model.get_selection(schema) for table in tables: r = self.table_list.add_node() r.set_bool(0, table in selection) r.set_icon_path(1, self.table_list_model.list_icon_for_table(schema, table)) r.set_string(1, table) r.set_tag(table) self.table_list.thaw_refresh() self.unselect_all_btn.set_enabled(True) self.select_all_btn.set_enabled(True) self.select_all_views_btn.set_enabled(True) self.select_summary_label.set_text("%i tables selected" % self.table_list_model.count_selected_tables()) def select_all_views(self): sel = self.schema_list.get_selected_node() if not sel: return sel.set_bool(0, True) schema = self.get_selected_schema() for row in range(self.table_list.count()): node = self.table_list.node_at_row(row) table = node.get_string(1) node.set_bool(0, self.table_list_model.is_view(schema, table)) self.update_table_selection() def select_all_tables(self, exclude_views=True): sel = self.schema_list.get_selected_node() if not sel: return sel.set_bool(0, True) schema = self.get_selected_schema() for row in range(self.table_list.count()): node = self.table_list.node_at_row(row) table = node.get_string(1) node.set_bool(0, not self.table_list_model.is_view(schema, table)) self.update_table_selection() def unselect_all_tables(self): for row in range(self.table_list.count()): self.table_list.node_at_row(row).set_bool(0, False) self.update_table_selection() def set_save_option(self): folder_selected = self.folderradio.get_active() self.folder_te.set_enabled(folder_selected) self.folder_btn.set_enabled(folder_selected) self.folderlabel.set_enabled(folder_selected) self.file_te.set_enabled(not folder_selected) self.file_btn.set_enabled(not folder_selected) self.filelabel.set_enabled(not folder_selected) if self.is_importing: if folder_selected: count = self.table_list_model.get_count() self.progress_tab.set_start_enabled(count > 0) self.import_target_schema_panel.set_enabled(False) else: self.progress_tab.set_start_enabled(True) self.import_target_schema_panel.set_enabled(True) self.refresh_schema_list() self.schema_list.set_enabled(folder_selected) self.table_list.set_enabled(folder_selected) else: if folder_selected: self.single_transaction_check.set_active(False) self.single_transaction_check.set_enabled(False) else: self.single_transaction_check.set_enabled(True) def refresh(self): pass def update_progress(self): completed = True progress = 0 progress_info = "" if self.dump_thread != None: if not self.dump_thread.done: completed = False progress = self.dump_thread.progress self.progress_tab.flush_queued_logs() progress_info = self.dump_thread.status_text if isinstance(self.dump_thread.e, wb_common.InvalidPasswordError): self.dump_thread = None self.bad_password_detected = True self.start() return False self.progress_tab.set_progress(progress, progress_info) # Python 2.6 needed # completed = self.dump_thread.is_alive() if completed: # print progress self.close_pipe() if self.dump_thread.abort_requested: self.tasks_aborted() else: self.tasks_completed() self.dump_thread = None return not completed def open_folder_chooser(self): filechooser = FileChooser(mforms.OpenDirectory) filechooser.set_directory(self.folder_te.get_string_value()) if filechooser.run_modal(): self.folder_te.set_value(filechooser.get_path()) if self.is_importing: self.refresh_table_list() def open_file_chooser(self, chooser_type=mforms.SaveFile): filechooser = FileChooser(chooser_type) filechooser.set_directory(os.path.dirname(self.file_te.get_string_value())) filechooser.set_extensions("SQL Files (*.sql)|*.sql","sql"); if filechooser.run_modal(): filepath = filechooser.get_path() self.file_te.set_value(filepath) if self.is_importing: self.refresh_table_list() else: if len(os.path.splitext(filepath)[1][1:]) == 0: self.file_te.set_value("%s.sql" % filepath) def get_mysql_password(self, reset_password=False): parameterValues = self.server_profile.db_connection_params.parameterValues username = parameterValues["userName"] host = self.server_profile.db_connection_params.hostIdentifier title = self.is_importing and "Import" or "Export" if self.bad_password_detected: title += ' (type the correct password)' self.bad_password_detected = False if not reset_password and not self.bad_password_detected: pwd = self.owner.ctrl_be.get_mysql_password() if pwd is None: accepted, pwd = mforms.Utilities.find_or_ask_for_password(title, host, username, reset_password) if not accepted: return None return pwd def stop(self): if self.dump_thread: self.dump_thread.kill() def failed(self, message): self.progress_tab.did_fail(message) def cancelled(self, message): self.progress_tab.did_cancel(message) #################################################################################################### ## Import #################################################################################################### class WbAdminImportTab(WbAdminSchemaListTab): def __init__(self, owner, server_profile, progress_tab): WbAdminSchemaListTab.__init__(self, owner, server_profile, progress_tab, True) self.table_list_model = TableListModel() self.export_button.set_text("Start Import") self.tables_paths = {} self.views_paths = {} self._update_schema_list_tm = None self._update_progress_tm = None self.folder_te.set_value(self.get_default_dump_folder()) self.file_te.set_value(os.path.join(self.get_default_dump_folder(), "export.sql")) def new_target_schema(self): ret, name = Utilities.request_input("Create Schema", "Name of schema to create:", "newschema") if ret: if not self.owner.ctrl_be.is_sql_connected(): Utilities.show_error("Create Schema", "Cannot create schema because there is no connection to the DB server.", "OK", "", "") return name = name.replace("`", "``") try: self.print_log_message("Creating schema %s\n" % name) self.owner.ctrl_be.exec_sql("CREATE DATABASE `%s`" % name, auto_reconnect=False) except QueryError as err: self.print_log_message("Error creating schema %s: %s\n" % (name, err)) Utilities.show_error("Create Schema", str(err), "OK", "", "") if err.is_connection_error(): self.owner.ctrl_be.handle_sql_disconnection(err) return self.refresh_schema_list() self.import_target_schema_selection = name def _refresh_schema_list_thread(self): self.schema_names = [] try: result = self.owner.ctrl_be.exec_query("SHOW DATABASES", auto_reconnect=False) while result.nextRow(): value = result.unicodeByName("Database") if value == "information_schema": continue self.schema_names.append(value) del result self.schema_refresh_done = True except QueryError as exc: self.print_log_message("Error fetching schema list: %s" % str(exc)) if exc.is_connection_error(): if self.owner.ctrl_be.handle_sql_disconnection(exc): self.schema_refresh_cancelled = "Error fetching schema list:\n%s\nReconnected successfully." % str(exc) else: self.schema_refresh_cancelled = "Error fetching schema list:\n%s\nCould not reconnect." % str(exc) else: self.schema_refresh_cancelled = "Error fetching schema list:\n%s" % str(exc) except Exception as exc: self.print_log_message("Error fetching schema list: %s" % str(exc) ) self.schema_refresh_cancelled = "Error fetching schema list:\n%s" % str(exc) def refresh_schema_list(self): if not self.owner.ctrl_be.is_sql_connected(): return class SchemaRefreshThread(threading.Thread): def __init__(self, owner): self.owner = owner threading.Thread.__init__(self) def run(self): self.owner._refresh_schema_list_thread() self.schema_refresh_thread = SchemaRefreshThread(self) self.schema_refresh_thread.start() self.schema_refresh_done = False self.schema_refresh_cancelled = None self.import_target_schema_selection = self.import_target_schema.get_string_value() self._update_schema_list_tm = Utilities.add_timeout(float(0.4), self._update_schema_list) def _update_schema_list(self): if self.schema_refresh_cancelled: Utilities.show_error("Refresh Schema List", self.schema_refresh_cancelled, "OK", "", "") self.schema_refresh_cancelled = None self._update_schema_list_tm = None return False if not self.schema_refresh_done: return True self.import_target_schema.clear() self.import_target_schema.add_items([""]+self.schema_names) if self.import_target_schema_selection: self.import_target_schema.set_value(self.import_target_schema_selection) self._update_schema_list_tm = None return False def close(self): if self._update_schema_list_tm: Utilities.cancel_timeout(self._update_schema_list_tm) if self._update_progress_tm: Utilities.cancel_timeout(self._update_progress_tm) def refresh_table_list(self): def parse_name_from_single_table_dump(path): import codecs f = codecs.open(path, encoding="utf-8") schema = None table = None is_view = False has_default_schema = False for line in f: if line.startswith("-- Host:"): schema = line.partition("Database: ")[-1].strip() if table: break elif line.startswith("USE "): has_default_schema = True elif not table and line.startswith("-- Table structure for table"): table = line.partition("-- Table structure for table")[-1].strip() if table[0] == '`': table = table[1:-1] if schema: break elif not table and line.startswith("-- Dumping data for table"): table = line.partition("-- Dumping data for table")[-1].strip() if table[0] == '`': table = table[1:-1] if schema: break elif line.startswith( ("/*!50001 VIEW", "/*!50003 CREATE*/ /*!50020 DEFINER=","/*!50106 CREATE*/ /*!50117 DEFINER=") ): is_view = True table = "Views, routines, events etc" if schema: break return schema, table, is_view, has_default_schema self.folder_load_btn.set_enabled(False) tables_by_schema = {} # (schema, table) -> path self.tables_paths = {} self.views_paths = {} self.needs_default_schema = {} self.schema_list.freeze_refresh() self.schema_list.clear() try: save_to_folder = not self.fileradio.get_active() if save_to_folder: self.progress_tab.set_start_enabled(False) path = self.folder_te.get_string_value() dirList=os.listdir(path) for fname in dirList: fullname = os.path.join(path, fname) if os.path.isfile(fullname) and os.path.splitext(fullname)[1] == ".sql": # open the backup file and look for schema and table name in it schema, table, is_view, has_default_schema = parse_name_from_single_table_dump(fullname) if not schema or not table: self.progress_tab.print_log_message("%s does not contain schema/table information" % fullname) continue if schema in tables_by_schema: tables, selection = tables_by_schema[schema] if table in tables: self.schema_list.thaw_refresh() message = "The selected folder doesn't appear to be valid. Multiple definitions of the same object (%s.%s) have been found." % (schema, table) Utilities.show_error("Open Dump Folder", message, "OK", "", "") return tables.append(table) tables.sort() selection.add(table) # select all by default else: tables_by_schema[schema] = ([table], set([table])) if is_view: self.views_paths[(schema, table)] = fullname self.table_list_model.set_routines_placeholder((schema, table)) else: self.tables_paths[(schema, table)] = fullname self.needs_default_schema[(schema, table)] = not has_default_schema if not tables_by_schema: Utilities.show_message("Open Dump Folder", "There were no dump files in the selected folder.", "OK", "", "") else: names = list(tables_by_schema.keys()) names.sort() for schema in names: row = self.schema_list.add_node() row.set_bool(0, True) row.set_icon_path(1, "db.Schema.16x16.png") row.set_string(1, schema) self.progress_tab.set_start_enabled(True) self.schema_list.thaw_refresh() except Exception as exc: import traceback self.schema_list.thaw_refresh() traceback.print_exc() Utilities.show_error("Error Opening Dump", str(exc), "OK", "", "") self.export_button.set_enabled(False) self.failed(str(exc)) self.table_list_model.set_tables_by_schema(tables_by_schema) self.folder_load_btn.set_enabled(True) for schema in list(tables_by_schema.keys()): self.table_list_model.set_schema_selected(schema, True) def get_path_to_mysql(self): # get path to mysql client from options try: path = grt.root.wb.options.options["mysqlclient"] if path: if os.path.exists(path): return path if any(os.path.exists(os.path.join(p,path)) for p in os.getenv("PATH").split(os.pathsep)): return path if path != "mysql": return None except: return None if sys.platform.lower() == "darwin": # if path is not specified, use bundled one return mforms.App.get().get_executable_path("mysql") elif sys.platform.lower() == "win32": return mforms.App.get().get_executable_path("mysql.exe") else: # if path is not specified, use bundled one path = mforms.App.get().get_executable_path("mysql") if path: return path # just pick default if any(os.path.exists(os.path.join(p,"mysql")) for p in os.getenv("PATH").split(os.pathsep)): return "mysql" return None def start(self): self.progress_tab.set_start_enabled(False) self.progress_tab.did_start() self.progress_tab.set_status("Import is running...") connection_params = self.server_profile.db_connection_params tunnel = ConnectionTunnel(connection_params) conn = connection_params.parameterValues from_folder = not self.fileradio.get_active() operations = [] extra_args = "" if from_folder: self.path = self.folder_te.get_string_value() else: self.path = self.file_te.get_string_value() #self.path = self.folder_te.get_string_value() if from_folder else self.file_te.get_string_value() if from_folder: selection = self.table_list_model.get_full_selection() # make sure routines, views and stuffs get imported last if self.table_list_model.routines_placeholder in selection: selection.remove(self.table_list_model.routines_placeholder) selection.append(self.table_list_model.routines_placeholder) for schema, table in selection: logmsg = "Restoring %s (%s)" % (schema, table) path = self.tables_paths.get((schema, table)) if self.needs_default_schema[(schema, table)]: extra_args = ["--database=%s" % schema] # description, object_count, extra_args, objects, pipe_factory if path != None: task = DumpThread.TaskData(logmsg, 1, extra_args, [path], None, lambda:None) #operations.insert(0,task) operations.append(task) else: path = self.views_paths.get((schema, table)) task = DumpThread.TaskData(logmsg, 1, extra_args, [path], None, lambda:None) if path != None: operations.append(task) else: if not os.path.exists(self.path): Utilities.show_message("Dump file not found", "File %s doesn't exist" % self.path, "OK", "", "") self.failed("Dump file not found: File %s doesn't exist" % self.path) return logmsg = "Restoring " + self.path # description, object_count, pipe_factory, extra_args, objects extra_args = [] task = DumpThread.TaskData(logmsg, 1, extra_args, [self.path], None, lambda:None) operations.append(task) # operations.append((logmsg, 1, lambda:None, [], [self.path])) if connection_params.driver.name == "MysqlNativeSocket": host_option = "--protocol="+("pipe" if sys.platform == "win32" else "socket") if conn["socket"]: port_option = "--socket=" + [conn["socket"]][0] else: port_option = "" else: if tunnel.port or conn["port"]: port_option = "--port=" + str(tunnel.port or conn["port"]) else: port_option = "" if (tunnel.port and ["localhost"] or [conn["hostName"]])[0]: host_option = "--host=" + (tunnel.port and ["localhost"] or [conn["hostName"]])[0] else: host_option = "" params = [ "--password=" ] if conn.get("useSSL", 0): if conn.get("sslCert", ""): params.append("--ssl-cert=%s" % conn["sslCert"]) if conn.get("sslCA", ""): params.append("--ssl-ca=%s" % conn["sslCA"]) if conn.get("sslKey", ""): params.append("--ssl-key=%s" % conn["sslKey"]) if conn.get("sslCipher", ""): params.append("--ssl-cipher=%s" % conn["sslCipher"]) params += [ host_option, "--user=" + conn["userName"], port_option, "--default-character-set=utf8", "--comments", "<" # the rest of the params will be redirected from stdin ] if not from_folder: target_db = self.import_target_schema.get_string_value() if target_db: params.insert(-1, "--database=%s" % target_db) params = [item for item in params if item] if connection_params.driver.name != "MysqlNativeSocket": params.insert(1, "--protocol=tcp") if conn.get("OPT_ENABLE_CLEARTEXT_PLUGIN", ""): params.insert(1, "--enable-cleartext-plugin") cmd = self.get_path_to_mysql() if cmd == None: self.failed("mysql command was not found, please install it or configure it in Preferences -> Administrator") return # if cmd[0] != '"': # cmd = '"' + cmd + '"' #cmd += " " + (" ".join(params)) cmd = subprocess.list2cmdline([cmd] + params) password = self.get_mysql_password(self.bad_password_detected) if password is None: self.cancelled("Password Input Cancelled") return self.dump_thread = DumpThread(cmd, operations, password, self, (self.progress_tab.logging_lock, self.progress_tab.log_queue)) self.dump_thread.is_import = True self.dump_thread.start() self._update_progress_tm = Utilities.add_timeout(float(0.4), self._update_progress) def _update_progress(self): r = self.update_progress() if not r: self._update_progress_tm = None return r def fail_callback(self): pass def close_pipe(self): pass def tasks_aborted(self): self.cancelled(time.strftime('%X ') + "Aborted by User") self.progress_tab.print_log_message("Restored database(s) maybe in an inconsistent state") def tasks_completed(self): logmsg = time.strftime('%X ') + "Import of %s has finished" % self.path if self.dump_thread.error_count > 0: self.progress_tab.set_status("Import Completed With %i Errors" % self.dump_thread.error_count) logmsg += " with %i errors" % self.dump_thread.error_count else: self.progress_tab.set_status("Import Completed") self.progress_tab.print_log_message(logmsg) self.progress_tab.did_complete() #################################################################################################### ## Export #################################################################################################### class WbAdminExportTab(WbAdminSchemaListTab): class ExportTableListModel(TableListModel): def __init__(self): TableListModel.__init__(self) self.views_by_schema = {} self.schemasqls = {} self.schemas_to_load = {} self.schemas = [] self.load_schema_data = lambda:None def reset(self): self.tables_by_schema = {} self.views_by_schema = {} self.schemasqls = {} def get_schema_names(self): return self.schemas def set_schema_data(self, schema, schematables_and_views, viewlist, dbsql): self.tables_by_schema[schema] = schematables_and_views, set() self.views_by_schema[schema] = set(viewlist) self.schemasqls[schema] = dbsql def get_schema_sql(self, schema): return self.schemasqls[schema] def is_view(self, schema, table): return table in self.views_by_schema[schema] def list_icon_for_table(self, schema, table): return "db.View.16x16.png" if self.is_view(schema, table) else "db.Table.16x16.png" def set_schema_list(self,schemas_to_load): self.schemas = schemas_to_load self.schemas_to_load = schemas_to_load def get_tables(self, schema): if schema in self.schemas_to_load: # print "Loading: ",schema,self.load_schema_data(schema) schema,schematables,viewlist,dbsql = self.load_schema_data(schema) self.set_schema_data(schema,schematables,viewlist,dbsql) self.schemas_to_load.remove(schema) tables = [] if schema in list(self.tables_by_schema.keys()): tables, selection = self.tables_by_schema[schema] return tables def validate_single_transaction(self, schemas_to_dump): return True class TableRefreshThread(threading.Thread): def __init__(self, owner): self.owner = owner threading.Thread.__init__(self) def run(self): self.owner.refresh_table_list_thread() def __init__(self, owner, server_profile, progress_tab): self.table_list_model = self.ExportTableListModel() WbAdminSchemaListTab.__init__(self, owner, server_profile, progress_tab, False) self.schemasqls = {} self._update_refresh_tm = None self._update_progress_tm = None defaultPath = os.path.join(self.get_default_dump_folder(), time.strftime("Dump%Y%m%d")) # updates self.savefolder_path self.folder_te.set_value(self.find_available_path(defaultPath)) # updates self.savefile_path self.file_te.set_value(self.find_available_path(defaultPath + '.sql')) self.table_list_model.load_schema_data = self.load_schema_tables # self.filefolder_label.set_text("Save to") # run mysqldump --help to get default option values self.mysqldump_defaults = self.check_mysqldump_defaults() self.ignore_internal_log_tables = True self.internal_log_tables = ["apply_status", "general_log", "slow_log", "schema"] self._compatibility_params = False self.show_internal_schemas = False def close(self): if self._update_refresh_tm: Utilities.cancel_timeout(self._update_refresh_tm) self._update_refresh_tm = None if self._update_progress_tm: Utilities.cancel_timeout(self._update_progress_tm) self._update_progress_tm = None def load_schema_tables(self, schema): schematables_and_views = [] viewlist = [] dbsql = "" try: self.refresh_state= "Retrieving tables data for schema " + schema dbcreate = self.owner.ctrl_be.exec_query("SHOW CREATE DATABASE `"+escape_sql_identifier(schema)+"`") tableset = self.owner.ctrl_be.exec_query("SHOW FULL TABLES FROM `"+escape_sql_identifier(schema)+"`") dbcreate.nextRow() dbsql = dbcreate.unicodeByName("Create Database") parts = dbsql.partition("CREATE DATABASE ") dbsql = "%s%s IF NOT EXISTS %s;\nUSE `%s`;\n" % (parts[0], parts[1], parts[2], escape_sql_identifier(schema)) while tableset.nextRow(): tabletype = tableset.unicodeByName("Table_type") tablename = tableset.unicodeByName("Tables_in_"+schema) if self.ignore_internal_log_tables and schema == "mysql" and tablename in self.internal_log_tables: continue if tabletype == "VIEW": viewlist.append(tablename) schematables_and_views.append(tablename) del tableset except Exception as exc: import traceback traceback.print_exc() print("Error retrieving table list form schema '",schema,"'") self.progress_tab.print_log_message("Error Fetching Table List From %s (%s)" % (schema, str(exc)) ) return schema,schematables_and_views,viewlist,dbsql def refresh_table_list_thread(self): self.table_list_model.reset() try: result = self.owner.ctrl_be.exec_query("SHOW DATABASES") schema_names = [] while result.nextRow(): value = result.unicodeByName("Database") if not self.show_internal_schemas and value in ["information_schema", "performance_schema", "mysql"]: continue schema_names.append(value) del result self.table_list_model.set_schema_list(schema_names) schema_cntr = 1 if schema_names: self.refresh_progress = float(schema_cntr) / len(schema_names) for schema in schema_names: # schematables,viewlist,dbsql = self.load_schema_tables(schema) # finally: schema_cntr += 1 self.refresh_progress = float(schema_cntr) / len(schema_names) # self.table_list_model.set_schema_data(schema,(schematables, set()),viewlist,dbsql) else: self.refresh_progress = 1 except Exception as exc: self.print_log_message("Error updating DB: %s" % str(exc) ) # finally: self.refresh_completed = True def refresh_table_list(self): self.table_list_model.reset() ####self.refresh_progressbar.set_value(0) if not self.owner.ctrl_be.is_sql_connected(): return #self.hintlabel.set_text("Schema list update...") self.refresh_state = "Retrieving schema list" self.refresh_progress = 0 self.schema_list.clear() self.refresh_button.set_enabled(False) self.refresh_thread = self.TableRefreshThread(self) # refresh_thread.run() self.refresh_completed = False self.refresh_thread.start() if not self._update_refresh_tm: self._update_refresh_tm = Utilities.add_timeout(float(0.4), self.update_refresh) def update_refresh(self): self.progress_tab.flush_queued_logs() if self.refresh_progress > 1: self.refresh_progress = float(1) ####self.refresh_progressbar.set_value(self.refresh_progress) self.select_summary_label.set_text(self.refresh_state) if not self.refresh_completed: return True ####self.refresh_progressbar.set_value(0) names = self.table_list_model.get_schema_names() names.sort() self.schema_list.freeze_refresh() for schema in names: r = self.schema_list.add_node() r.set_icon_path(1, "db.Schema.16x16.png") r.set_string(1, schema) r.set_bool(0, False) self.schema_list.thaw_refresh() self.refresh_button.set_enabled(True) self.select_summary_label.set_text("") ###self.hintlabel.set_text("Press [Start Export] to start...") self._update_refresh_tm = None return False def find_available_path(self, currentPath, ext = None): # remove the extension if exists ext = os.path.splitext(currentPath)[1] if len(ext) > 1: currentPath = currentPath[:-len(ext)] # remove old path resolution, if exists suffixBegin = currentPath.find(' (') if suffixBegin > 0: suffixEnd = currentPath.find(')', suffixBegin) if suffixEnd == -1: suffixEnd = len(currentPath) else: suffixEnd += 1 currentPath = currentPath[:suffixBegin] + currentPath[suffixEnd:] result = currentPath counter = 0 while os.path.exists(result + ext): counter += 1 result = "%s (%d)" % (currentPath, counter) return result + ext def check_mysqldump_version(self, about_to_run=False): mysqldump_version = get_mysqldump_version() if not mysqldump_version: if about_to_run: mforms.Utilities.show_error("Could not get mysqldump version", "Workbench was unable to get mysqldump version. Please verify the log for more information.", "OK", "", "") else: self.print_log_message("Workbench was unable to get mysqldump version") return False if str(mysqldump_version).split('.')[:2] != str(self.owner.ctrl_be.target_version).split('.')[:2]: msg = "%s is version %s, but the MySQL Server to be dumped has version %s.\nBecause the version of mysqldump is not the same as the server, some features may not be backed up properly.\nIt is recommended you upgrade or downgrade your local MySQL client programs, including mysqldump, to a version equal to or newer than that of the target server.\nThe path to the dump tool must then be set in Preferences -> Administrator -> Path to mysqldump Tool:" % (get_path_to_mysqldump(), mysqldump_version, self.owner.ctrl_be.target_version) if about_to_run: if not mforms.Utilities.show_warning("mysqldump Version Mismatch", msg, "Continue Anyway", "Cancel", ""): return False else: self.print_log_message(msg) # When using mysqldump >=5.6 and a server < 5.6, an additional parameter needs to be added # for backwards compatibility if (mysqldump_version >= Version(5, 6) and self.owner.ctrl_be.target_version < Version(5, 6)): self._compatibility_params = True return True def check_mysqldump_defaults(self): defaults = {} # check mysqldump default values path = get_path_to_mysqldump() if path: output = [] local_run_cmd('"%s" --help' % path, output_handler= lambda line,l=output: l.append(line if isinstance(line, str) else line.decode("utf-8"))) ok = False for line in ("\n".join(output)).split("\n"): line = line.strip() if line.startswith("-----------") and line.endswith("-----------"): ok = True continue if ok: t = line.split() if len(t) == 2: k, v = t if v in ("TRUE", "FALSE"): defaults[k] = v return defaults def validate_single_transaction(self, starting): if self.single_transaction_check.get_active() and self.owner.get_lock_tables() == {'lock-tables': 'TRUE'}: r = mforms.Utilities.show_warning("Export to Disk", "Single transaction with --lock-tables is not supported.\n" "Disable --lock-tables?", "Disable", "Cancel", "") if r == mforms.ResultOk: self.owner.set_lock_tables(False) return True else: return False return True def single_transaction_clicked(self): self.validate_single_transaction(False) def set_show_internal_schemas(self, show_internal_schemas): self.show_internal_schemas = show_internal_schemas class ViewDumpData(DumpThread.TaskData): def __init__(self,schema,views,make_pipe): title = "Dumping " + schema + " views" DumpThread.TaskData.__init__(self, title, len(views), [], [schema] + views, None, make_pipe) class TableDumpData(DumpThread.TaskData): def __init__(self,schema,table,args, make_pipe): title = "Dumping " + schema title += " (%s)" % table DumpThread.TaskData.__init__(self,title, 1, [] + args, [schema, table], None, make_pipe) class TableDumpNoData(DumpThread.TaskData): def __init__(self,schema,table,args, make_pipe): title = "Dumping " + schema title += " (%s)" % table DumpThread.TaskData.__init__(self,title, 1, ["--no-data"] + args, [schema, table], None, make_pipe) class ViewsRoutinesEventsDumpData(DumpThread.TaskData): def __init__(self, schema, views, args, make_pipe): title = "Dumping " + schema + " views and/or routines and/or events" if not views: extra_args = ["--no-create-info"] else: extra_args = [] DumpThread.TaskData.__init__(self,title, len(views), ["--skip-triggers", " --no-data" ," --no-create-db"] + extra_args + args, [schema] + views, None, make_pipe) def dump_to_folder(self, schemaname, tablename, include_schema): self.close_pipe() path = os.path.join(self.path, normalize_filename(schemaname) + "_" + normalize_filename(tablename) + '.sql') i = 0 # check if the path already exists (they could become duplicated because of normalization) while os.path.exists(path): path = os.path.join(self.path, normalize_filename(schemaname) + "_" + normalize_filename(tablename) + ('%i.sql'%i)) self.out_pipe = open(path,"w") if include_schema: data = self.table_list_model.get_schema_sql(schemaname) self.out_pipe.write(data) self.out_pipe.flush() return self.out_pipe def start(self): save_to_folder = not self.fileradio.get_active() if save_to_folder: self.path = self.folder_te.get_string_value() if os.path.exists(self.path): if not mforms.Utilities.show_warning("Folder already exists", "You are about to overwrite the specified folder. Do you want to continue overwrite?", "Overwrite", "Cancel", ""): return False else: self.path = self.file_te.get_string_value() if os.path.exists(self.path): if not mforms.Utilities.show_warning("File already exists", "You are about to overwrite the specified file. Do you want to continue overwrite?", "Overwrite", "Cancel", ""): return False self.progress_tab.set_start_enabled(False) if not self.check_mysqldump_version(True): self.progress_tab.set_start_enabled(True) return if not self.validate_single_transaction(True): self.progress_tab.set_start_enabled(True) return connection_params = self.server_profile.db_connection_params tunnel = ConnectionTunnel(connection_params) conn = connection_params.parameterValues single_transaction = self.single_transaction_check.get_active() #dump_views = self.dump_view_check.get_active() sel_index = self.dump_type_selector.get_selected_index() skip_data = True if sel_index == 2 else False skip_table_structure = True if sel_index == 1 else False # For mysqldump >= 8.0.2 there is column-statistic arg that cause issue while executing on MySQL Server version <= 5.7. # To avoid that we force add '--column-statistics=0' to mysqldump args. skip_column_statistics = True if get_mysqldump_version() > Version(8, 0, 2) and self.owner.ctrl_be.target_version < Version(8, 0, 0) else False dump_routines = self.dump_routines_check.get_active() dump_events = self.dump_events_check.get_active() dump_triggers = self.dump_triggers_check.get_active() save_to_folder = not self.fileradio.get_active() if save_to_folder: self.path = self.folder_te.get_string_value() else: self.path = self.file_te.get_string_value() # gather objects to dump schemas_to_dump = self.table_list_model.get_objects_to_dump(include_empty_schemas=True) tables_to_ignore = self.table_list_model.get_tables_to_ignore() if len(schemas_to_dump) == 0: self.progress_tab.print_log_message(time.strftime('%X ') + "Nothing to do, no schemas or tables selected." + "\n") self.progress_tab.set_start_enabled(True) return # assemble list of operations/command calls to be performed operations = [] if save_to_folder: if not os.path.exists(self.path): try: os.makedirs(self.path, mode=0o700) except: Utilities.show_error('Error', 'Access to "%s" failed' % self.path, "OK", "", "") self.export_button.set_enabled(True) return for schema, tables in schemas_to_dump: views = [] for table in tables: if self.table_list_model.is_view(schema, table): views.append(table) else: title = "Dumping " + schema title += " (%s)" % table # description, object_count, pipe_factory, extra_args, objects args = [] if not dump_triggers: args.append('--skip-triggers') if skip_table_structure: args.append('--no-create-info') if skip_column_statistics: args.append('--skip-column-statistics') include_schema = self.include_schema_check.get_active() if skip_data: task = self.TableDumpNoData(schema,table, args, lambda schema=schema,table=table:self.dump_to_folder(schema, table, include_schema)) else: task = self.TableDumpData(schema,table, args, lambda schema=schema,table=table:self.dump_to_folder(schema, table, include_schema)) operations.append(task) # dump everything non-tables to file for routines #if views: # task = self.ViewDumpData(schema, views, lambda schema=schema, table=table:self.dump_to_folder(schema, "routines")) # operations.append(task) #if dump_events: # task = self.EventDumpData(schema, lambda schema=schema, table=table:self.dump_to_folder(schema, "routines")) # operations.append(task) if views or dump_routines or dump_events: args = [] if dump_routines: args.append("--routines") if dump_events: args.append("--events") if skip_column_statistics: args.append('--skip-column-statistics') task = self.ViewsRoutinesEventsDumpData(schema, views, args, lambda schema=schema, table=None:self.dump_to_folder(schema, "routines", include_schema)) operations.append(task) else: # single file if not os.path.exists(os.path.dirname(self.path)): try: os.makedirs(os.path.dirname(self.path)) except: Utilities.show_error('Error', 'Access to "%s" failed' % self.path, "OK", "", "") self.export_button.set_enabled(True) return # if there is a single schema to dump or single-transaction is off we allow selecting the individual tables, otherwise we need to bulk dump the whole db #if (len(schemas_to_dump) != 1 and not single_transaction) or (len(schemas_to_dump) == 1 not self.table_list_model.validate_single_transaction(schemas_to_dump)): if len(schemas_to_dump) == 1 or (len(schemas_to_dump) > 1 and (not single_transaction or not self.table_list_model.validate_single_transaction(schemas_to_dump))): for schema, tables in schemas_to_dump: # don't list the tables explicitly if everything is dumped # this is to workaround a problem with mysqldump where functions are dumped after # tables if the table names are specified # see bug #14359349 title = "Dumping " + schema if set(tables) == set(self.table_list_model.get_tables(schema)): title += " (all tables)" else: title += " (%s)" % ", ".join(tables) objects = [schema] if single_transaction: params = ["--single-transaction=TRUE"] else: params = [] if dump_routines: params.append("--routines") if dump_events: params.append("--events") if skip_data or not tables: params.append("--no-data") if skip_column_statistics: params.append('--column-statistics=0') if not tables or skip_table_structure: params.append("--no-create-info=TRUE") if not tables or not dump_triggers: params.append("--skip-triggers") # description, object_count, pipe_factory, extra_args, objects include_schema = self.include_schema_check.get_active() task = DumpThread.TaskData(title, len(tables), params, objects, tables_to_ignore, lambda schema=schema:self.dump_to_file([schema], include_schema)) operations.append(task) # operations.append((title, len(tables), lambda schema=schema:self.dump_to_file([schema]), params, objects)) else: params = [] schema_names = [s[0] for s in schemas_to_dump] count = sum([len(s[1]) for s in schemas_to_dump]) title = "Dumping " + ", ".join(schema_names) if dump_routines: params.append("--routines") if dump_events: params.append("--events") if skip_data: params.append("--no-data") if skip_column_statistics: params.append('--column-statistics=0') if single_transaction: params += ["--single-transaction=TRUE", "--databases"] else: params += ["--databases"] # --databases includes CREATE DATABASE info, so it's not needed for dump_to_file() # description, object_count, pipe_factory, extra_args, objects include_schema = self.include_schema_check.get_active() task = DumpThread.TaskData(title, count, params, schema_names, tables_to_ignore, lambda:self.dump_to_file([], include_schema)) operations.append(task) # operations.append((title, count, lambda:self.dump_to_file([]), params, schema_names)) if connection_params.driver.name == "MysqlNativeSocket": params = { "protocol":"pipe" if sys.platform == "win32" else "socket", "socket":([conn["socket"]])[0], "default-character-set":"utf8", "user":conn["userName"] } if not params["socket"]: del params["socket"] else: params = { "host":(tunnel.port and ["localhost"] or [conn["hostName"]])[0], "port":(tunnel.port and [str(tunnel.port)] or [conn["port"]])[0], "default-character-set":"utf8", "user":conn["userName"] } params["protocol"] = "tcp" if not params["port"]: del params["port"] if not params["host"]: del params["host"] options = {} excludes = ['Show Internal Schemas'] for key, value in list(self.owner.get_export_options(self.mysqldump_defaults).items()): if not key in excludes: options[key] = value params.update(options) cmd = get_path_to_mysqldump() if cmd == None: self.failed("mysqldump command was not found, please install it or configure it in Edit -> Preferences -> Administration") return #if cmd[0] != '"': # cmd = '"' + cmd + '"' #cmd += " --password=" args = [cmd, "--password="] if conn.get("useSSL", 0): if conn.get("sslCert", ""): args.append("--ssl-cert=%s" % conn["sslCert"]) if conn.get("sslCA", ""): args.append("--ssl-ca=%s" % conn["sslCA"]) if conn.get("sslKey", ""): args.append("--ssl-key=%s" % conn["sslKey"]) if conn.get("sslCipher", ""): args.append("--ssl-cipher=%s" % conn["sslCipher"]) # Sets the compatibility parameters if needed if self._compatibility_params: args.append("--set-gtid-purged=OFF") if conn.get("OPT_ENABLE_CLEARTEXT_PLUGIN", ""): args.append("--enable-cleartext-plugin") for paramname, paramvalue in list(params.items()): args.append("--"+paramname+((paramvalue != None and ["="+str(paramvalue)] or [""])[0])) cmd = subprocess.list2cmdline(args) password = self.get_mysql_password(self.bad_password_detected) if password is None: self.cancelled("Password Input Cancelled") return self.progress_tab.did_start() self.progress_tab.set_status("Export is running...") self.dump_thread = DumpThread(cmd, operations, password, self, (self.progress_tab.logging_lock, self.progress_tab.log_queue)) self.dump_thread.is_import = False self.dump_thread.start() self._update_progress_tm = Utilities.add_timeout(float(0.4), self._update_progress) def _update_progress(self): r = self.update_progress() if not r: self._update_progress_tm = None return r def dump_to_file(self, schemanames, include_schema): if self.out_pipe == None: self.out_pipe = open(self.path, "w") if include_schema: for schema in schemanames: self.out_pipe.write(self.table_list_model.get_schema_sql(schema)) self.out_pipe.flush() return self.out_pipe def fail_callback(self): fname = self.out_pipe.name self.close_pipe() os.remove(fname) def close_pipe(self): if self.out_pipe != None: self.out_pipe.close() self.out_pipe = None def tasks_aborted(self): if self.path: try: os.rename(self.path, self.path+".cancelled") self.progress_tab.print_log_message("Partial backup file renamed to %s.cancelled" % self.path) except Exception as exc: self.progress_tab.print_log_message("Error renaming partial backup file %s: %s" % (self.path, exc)) self.cancelled(time.strftime('%X ') + "Aborted by User") def tasks_completed(self): self.folder_te.set_value(self.find_available_path(self.folder_te.get_string_value())) self.file_te.set_value(self.find_available_path(self.file_te.get_string_value())) logmsg = time.strftime('%X ') + "Export of %s has finished" % self.path if self.dump_thread.error_count > 0: self.progress_tab.set_status("Export Completed With %i Errors" % self.dump_thread.error_count) logmsg += " with %i errors" % self.dump_thread.error_count else: self.progress_tab.set_status("Export Completed") self.progress_tab.print_log_message(logmsg) self.progress_tab.did_complete() #################################################################################################### ## Options #################################################################################################### class WbAdminExportOptionsTab(mforms.Box): class Check_option_model: def __init__(self,optname,checkbox,default): self.optname = optname self.checkbox = checkbox self.default = default def get_option(self, defaults): is_bool_option = self.optname in defaults value = self.checkbox.get_active() and "TRUE" or "FALSE" if is_bool_option: if defaults[self.optname] != value: return {self.optname: value} return {} else: if self.default == "TRUE" and not self.checkbox.get_active(): return {"skip-"+self.optname:"TRUE"} else: return {self.optname:(self.checkbox.get_active() and ["TRUE"] or ["FALSE"])[0]} def set_option(self, value): if value in ("TRUE", "FALSE"): self.checkbox.set_active(value == "TRUE") else: self.checkbox.set_active(value) self.checkbox.call_clicked_callback() class Text_option_model: def __init__(self, optname, textentry, default): self.optname = optname self.entry = textentry self.default = default def get_option(self, defaults): if self.entry.get_string_value() == self.default: return {} return {self.optname:self.entry.get_string_value() or self.default} def set_option(self, value): self.entry.set_value(value) def __init__(self, target_version, defaults_from_mysqldump): mforms.Box.__init__(self, False) self.set_managed() self.set_release_on_add() mysqldump_version = get_mysqldump_version() self.options = {} button_box = newBox(True) button_box.set_padding(8) button_box.set_spacing(12) self.restore_defaults_button = newButton() self.restore_defaults_button.set_text('Restore Defaults') self.restore_defaults_button.add_clicked_callback(self.restore_default_options) button_box.add_end(self.restore_defaults_button, False) self.add_end(button_box, False) outerbox = newBox(False) outerbox.set_padding(8) outerbox.set_spacing(12) for groupname, options in reversed(list(wb_admin_export_options.export_options.items())): box = newBox(False) box.set_padding(8) box.set_spacing(8) panel = newPanel(mforms.TitledBoxPanel) panel.set_title(groupname) panel.set_name(groupname) # print groupname for optname, option_info in reversed(list(options.items())): option_type = "BOOL" if len(option_info) == 2: (option, default) = option_info elif len(option_info) == 4: # includes type and (min_version, max_version) tuple (option, default, option_type, (min_version, max_version)) = option_info if min_version and target_version: if not target_version.is_supported_mysql_version_at_least(Version.fromstr(min_version)): log_debug("Skip option %s because it's for version %s\n" % (optname, min_version)) continue if max_version and target_version: if target_version.is_supported_mysql_version_at_least(Version.fromstr(max_version)): log_debug("Skip option %s because it's deprecated in version %s\n" % (optname, max_version)) continue if min_version and mysqldump_version < min_version: log_debug("Skip option %s because it's for mysqldump %s\n" % (optname, min_version)) continue if max_version and mysqldump_version > max_version: log_debug("Skip option %s because it's deprecated in mysqldump %s\n" % (optname, max_version)) continue exlude = ['column-statistics'] # get the default value from mysqldump --help, if we don't have that data, use the stored default if optname not in exlude or get_mysqldump_version() == target_version: default = defaults_from_mysqldump.get(optname, default) if option_type == "BOOL": checkbox = newCheckBox() checkbox.set_text("%s - %s"% (optname, option)) checkbox.set_name(optname) checkbox.set_active(default == "TRUE") box.add(checkbox, False, True) self.options[optname] = self.Check_option_model(optname,checkbox,default) else: hbox = newBox(True) hbox.set_spacing(4) label = newLabel("%s - %s"% (optname, option)) hbox.add(label, False, True) entry = newTextEntry() entry.set_name(optname) hbox.add(entry, True, True) entry.set_value(default) box.add(hbox, False, True) self.options[optname] = self.Text_option_model(optname,entry,default) if groupname == "Other": max_allowed_packet_box = newBox(True) self.max_allowed_packet_te = newTextEntry() self.max_allowed_packet_te.set_value("1G") self.max_allowed_packet_te.set_size(40, -1) label = newLabel(" The maximum size of one packet or any generated/intermediate string. ") max_allowed_packet_box.add(label,False,False) max_allowed_packet_box.add(self.max_allowed_packet_te,False,True) max_allowed_packet_box.add(newBox(True),True,True) max_allowed_packet_box.add(newBox(True),True,True) max_allowed_packet_box.add(newBox(True),True,True) box.add(max_allowed_packet_box,False,True) self.options['max_allowed_packet'] = self.Text_option_model('max_allowed_packet', self.max_allowed_packet_te, "1G") panel.add(box) outerbox.add(panel, False, True) scrollpan = newScrollPanel(mforms.ScrollPanelNoFlags) scrollpan.add(outerbox) self.add(scrollpan, True, True) def get_lock_tables(self): return self.options["lock-tables"].get_option({'lock-tables': 'TRUE'}) def set_lock_tables(self, value): return self.options["lock-tables"].set_option(value) def get_options(self, defaults): options = {} for optname, getter in list(self.options.items()): result = getter.get_option(defaults) if result != None: options.update(result) #options.update({"max_allowed_packet":self.max_allowed_packet_te.get_string_value()}) return options def set_options(self, values): for k, v in list(values.items()): if k in self.options: self.options[k].set_option(v) def restore_default_options(self): for option in list(self.options.values()): option.set_option(option.default) def add_clicked_callback_to_checkbox(self, optname, callback_function): opt = self.options[optname] if opt: opt.checkbox.add_clicked_callback(callback_function) #################################################################################################### class WbAdminProgressTab(mforms.Box): def __init__(self, owner_tab, is_export): mforms.Box.__init__(self, False) self.set_managed() self.set_release_on_add() self.set_spacing(12) self.set_padding(8) self.owner_tab = owner_tab self.operation_tab = None self.is_export = is_export self.logging_lock = _thread.allocate_lock() self.log_queue = deque([]) statusbox = newBox(False) statusbox.set_spacing(2) self.dump_progressbar = newProgressBar() self.dump_progressbar.set_name("Progress") self.statlabel = newLabel("") statusbox.set_size(400, -1) if is_export: self.hintlabel = newLabel("Press [Start Export] to start...") else: self.hintlabel = newLabel("Press [Start Import] to start...") self.hintlabel.set_name("Press to Start") statusbox.add(self.hintlabel, False, True) statusbox.add(self.dump_progressbar, False, True) label = newLabel("Status:") label.set_name("Status") statusbox.add(label, False, True) statusbox.add(self.statlabel, False, True) self.progress_log = newTextBox(mforms.VerticalScrollBar) self.progress_log.set_name("Log Entry") self.progress_log.set_read_only(True) self.add(statusbox, False, True) label = newLabel("Log:") label.set_name("Log") self.add(label, False, True) self.add(self.progress_log, True, True) box = newBox(True) self.add(box, False, True) box.set_spacing(8) box.set_padding(0) self.export_button = newButton() if is_export: self.export_button.set_text("Start Export") self.export_button.set_name("Start Export") else: self.export_button.set_text("Start Import") self.export_button.set_name("Start Import") self.export_button.set_enabled(False) box.add_end(self.export_button, False, True) self.export_button.add_clicked_callback(self.start) self.stop_button = newButton() self.stop_button.set_text("Stop") self.stop_button.set_name("Stop") self.stop_button.set_enabled(False) self.stop_button.add_clicked_callback(self.stop) box.add_end(self.stop_button, False, True) def set_progress(self, progress, progress_text): self.statlabel.set_text(progress_text) self.dump_progressbar.set_value(progress) def set_start_enabled(self, flag): self.export_button.set_enabled(bool(flag)) self.operation_tab.export_button.set_enabled(bool(flag)) def set_status(self, text): self.hintlabel.set_text(text) self.operation_tab.statlabel.set_text(text) def flush_queued_logs(self): self.logging_lock.acquire() while len(self.log_queue) > 0: self.progress_log.append_text_and_scroll(self.log_queue.popleft()+"\n", True) self.logging_lock.release() def print_log_message(self, message): if mforms.Utilities.in_main_thread(): self.progress_log.append_text_and_scroll(message+"\n", True) else: self.logging_lock.acquire() self.log_queue.append(message+"\n") self.logging_lock.release() def start(self): self.operation_tab.start() def stop(self): self.operation_tab.stop() def did_start(self): self.owner_tab.switch_to_progress() self.set_start_enabled(False) self.stop_button.set_enabled(True) self.set_status("Export running...") def did_complete(self): self.set_start_enabled(True) self.stop_button.set_enabled(False) self.print_log_message("\n\n\n") if self.is_export: self.export_button.set_text("Export Again") else: self.export_button.set_text("Import Again") def did_fail(self, message): self.progress_log.append_text_and_scroll(message+"\n", True) self.set_status("Operation Failed") self.set_start_enabled(True) self.stop_button.set_enabled(False) def did_cancel(self, message): self.progress_log.append_text_and_scroll(message+"\n", True) self.set_status("Operation Cancelled") self.dump_progressbar.set_value(0) self.set_start_enabled(True) self.stop_button.set_enabled(False) def close(self): pass #################################################################################################### class WbAdminExport(WbAdminTabBase): @classmethod def wba_register(cls, admin_context): admin_context.register_page(cls, "Management", "Data Export", "Data Export") @classmethod def identifier(cls): return "admin_export" def __init__(self, ctrl_be, instance_info, main_view): WbAdminTabBase.__init__(self, ctrl_be, instance_info, main_view) self.set_name("Administration - Data Export/Import") self.showing_options = False self.add_validation(WbAdminValidationConnection(ctrl_be)) self.advanced_options_btn = mforms.newButton() self.advanced_options_btn.set_name("Toggle Advanced Options") self.advanced_options_btn.set_text("Advanced Options...") self.advanced_options_btn.add_clicked_callback(self.show_options) self.set_header(self.create_standard_header("title_export.png", self.instance_info.name, "Data Export", self.advanced_options_btn)) self.export_tab = None self.progress_tab = None def switch_to_progress(self): self.tabview.set_active_tab(1) def show_options(self): self.showing_options = not self.showing_options if self.showing_options: self.tabview.show(False) self.options_tab.show(True) self.advanced_options_btn.set_text("< Return") else: self.tabview.show(True) self.options_tab.show(False) self.advanced_options_btn.set_text("Advanced Options...") def validation_failed_notification(self, failed_validation): self.advanced_options_btn.show(False) def validation_successful_notification(self): self.advanced_options_btn.show(True) def create_ui(self): self.ui_box = mforms.newBox(False) self.tabview = newTabView(False) self.tabview.set_name("Import/Export Tab Panel") self.ui_box.add(self.tabview, True, True) self.tabview.show(False) self.progress_tab = WbAdminProgressTab(self, True) self.export_tab = WbAdminExportTab(self, self.instance_info, self.progress_tab) self.tabview.add_page(self.export_tab, "Object Selection") self.options_tab = WbAdminExportOptionsTab(self.ctrl_be.target_version, self.export_tab.mysqldump_defaults) self.ui_box.add(self.options_tab, True, True) self.options_tab.show(False) self.options_tab.add_clicked_callback_to_checkbox("Show Internal Schemas", self.show_internal_schemas_changed) self.tabview.add_page(self.progress_tab, "Export Progress") self.tabview.show(True) self.recall_options() self.export_tab.refresh_table_list() return self.ui_box def shutdown(self): # called when admin tab is closed self.remember_options() if self.ui_created(): if self.export_tab: self.export_tab.close() if self.progress_tab: self.progress_tab.close() def get_lock_tables(self): return self.options_tab.get_lock_tables() def set_lock_tables(self, value): return self.options_tab.set_lock_tables(value) def get_export_options(self, defaults): options = self.options_tab.get_options(defaults) return options def remember_options(self): if self.ui_created() and self.export_tab: dic = grt.root.wb.options.options dic["wb.admin.export:exportType"] = self.export_tab.folderradio.get_active() and "folder" or "file" dic["wb.admin.export:selectedFolder"] = self.export_tab.folder_te.get_string_value() dic["wb.admin.export:selectedFile"] = self.export_tab.file_te.get_string_value() dic["wb.admin.export:singleTransaction"] = self.export_tab.single_transaction_check.get_active() dic["wb.admin.export:dumpRoutines"] = self.export_tab.dump_routines_check.get_active() dic["wb.admin.export:dumpEvents"] = self.export_tab.dump_events_check.get_active() dic["wb.admin.export:dumpTriggers"] = self.export_tab.dump_triggers_check.get_active() dic["wb.admin.export:skipData"] = self.export_tab.dump_type_selector.get_selected_index() for key, value in list(self.get_export_options({}).items()): dic["wb.admin.export.option:"+key] = value def recall_options(self): dic = grt.root.wb.options.options if "wb.admin.export:exportType" in dic: if dic["wb.admin.export:exportType"] == "folder": self.export_tab.folderradio.set_active(True) else: self.export_tab.fileradio.set_active(True) self.export_tab.set_save_option() # if dic.has_key("wb.admin.export:selectedFolder"): # self.export_tab.folder_te.set_value(dic["wb.admin.export:selectedFolder"]) # if dic.has_key("wb.admin.export:selectedFile"): # self.export_tab.file_te.set_value(dic["wb.admin.export:selectedFile"]) if "wb.admin.export:singleTransaction" in dic: self.export_tab.single_transaction_check.set_active(dic["wb.admin.export:singleTransaction"] != 0) if "wb.admin.export:dumpRoutines" in dic: self.export_tab.dump_routines_check.set_active(dic["wb.admin.export:dumpRoutines"] != 0) if "wb.admin.export:dumpEvents" in dic: self.export_tab.dump_events_check.set_active(dic["wb.admin.export:dumpEvents"] != 0) if "wb.admin.export:dumpTriggers" in dic: self.export_tab.dump_triggers_check.set_active(dic["wb.admin.export:dumpTriggers"] != 0) if "wb.admin.export:skipData" in dic: self.export_tab.dump_type_selector.set_selected(dic["wb.admin.export:skipData"] != 0) values = {} for key in list(self.get_export_options({}).keys()): if "wb.admin.export.option:"+key in dic: values[key] = dic["wb.admin.export.option:"+key] self.options_tab.set_options(values) def show_internal_schemas_changed(self): self.export_tab.set_show_internal_schemas(self.get_export_options({})['Show Internal Schemas'] == 'TRUE') self.export_tab.refresh_table_list() #################################################################################################### class WbAdminImport(WbAdminTabBase): def __init__(self, ctrl_be, instance_info, main_view): WbAdminTabBase.__init__(self, ctrl_be, instance_info, main_view) self.set_name("Administration - Data Export/Import") self.add_validation(WbAdminValidationConnection(ctrl_be)) self.set_standard_header("title_import.png", self.instance_info.name, "Data Import") self.progress_tab = None self.import_tab = None self.tabview = None @classmethod def wba_register(cls, admin_context): admin_context.register_page(cls, "Management", "Data Import/Restore", "Data Import and Export") @classmethod def identifier(cls): return "admin_restore_data" def shutdown(self): if self.ui_created(): if self.import_tab: self.import_tab.close() if self.progress_tab: self.progress_tab.close() def switch_to_progress(self): self.tabview.set_active_tab(1) def create_ui(self): self.tabview = newTabView(False) self.tabview.show(False) self.progress_tab = WbAdminProgressTab(self, False) self.import_tab = WbAdminImportTab(self, self.instance_info, self.progress_tab) self.tabview.add_page(self.import_tab, "Import from Disk") self.tabview.add_page(self.progress_tab, "Import Progress") self.tabview.show(True) return self.tabview