# -*- coding: utf-8 -*- # emma # # Copyright (C) 2006 Florian Schmidt (flo@fastflo.de) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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 Library General Public License 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 sys import os from stat import * import time import re import gc import pickle import datetime import bz2 try: import gtk from gtk import keysyms import gobject import gtk.gdk import gtk.glade if __name__ != "__main__": from emmalib import __file__ as emmalib_file from emmalib.mysql_host import * from emmalib.mysql_query_tab import * else: emmalib_file = __file__ from mysql_host import * from mysql_query_tab import * except: print "no gtk. you will not be able to start emma." import pprint version = "0.6" new_instance = None our_module = None re_src_after_order_end = "(?:limit.*|procedure.*|for update.*|lock in share mode.*|[ \r\n\t]*$)" re_src_after_order = "(?:[ \r\n\t]" + re_src_after_order_end + ")" re_src_query_order = "(?is)(.*order[ \r\n\t]+by[ \r\n\t]+)(.*?)([ \r\n\t]*" + re_src_after_order_end + ")" emmalib_file = os.path.abspath(emmalib_file) if os.name in ["win32", "nt"]: print "Windows detected" emma_path = emmalib_file count = 5 dirs_checked = [] while not os.access(os.path.join(emma_path, "emma.glade"), os.R_OK): dirs_checked.append(emma_path) emma_path = os.path.dirname(emma_path) count -= 1 if not count: print "could not find glade file! checked these dirs:", dirs_checked sys.exit(0) else: emma_path = os.path.dirname(emmalib_file) emma_share_path = os.path.join(sys.prefix, "share/emma/") icons_path = os.path.join(emma_share_path, "icons") glade_path = os.path.join(emma_share_path, "glade") themes_path = os.path.join(sys.prefix, "share", "themes") last_update = 0 class Emma: def __init__(self): self.created_once = {} self.query_count = 0 self.glade_path = glade_path self.icons_path = icons_path self.glade_file = os.path.join(glade_path, "emma.glade") if not os.access(self.glade_file, os.R_OK): print self.glade_file, "not found!" sys.exit(-1) print "galde source file:", [self.glade_file] self.xml = gtk.glade.XML(self.glade_file) self.mainwindow = self.xml.get_widget("mainwindow") self.mainwindow.connect('destroy', lambda *args: gtk.main_quit()) self.xml.signal_autoconnect(self) self.load_icons() # setup sql_log self.sql_log_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) self.sql_log_tv = self.xml.get_widget("sql_log_tv") self.sql_log_tv.set_model(self.sql_log_model) self.sql_log_tv.append_column(gtk.TreeViewColumn("time", gtk.CellRendererText(), text=0)) self.sql_log_tv.append_column(gtk.TreeViewColumn("query", gtk.CellRendererText(), markup=1)) if hasattr(self, "state"): for log in self.state["sql_logs"]: self.sql_log_model.append(log) # setup msg self.msg_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.msg_tv = self.xml.get_widget("msg_tv") self.msg_tv.set_model(self.msg_model) self.msg_tv.append_column(gtk.TreeViewColumn("time", gtk.CellRendererText(), text=0)) self.msg_tv.append_column(gtk.TreeViewColumn("message", gtk.CellRendererText(), text=1)) self.blob_tv = self.xml.get_widget("blob_tv") self.blob_tv.set_sensitive(False) self.blob_buffer = self.blob_tv.get_buffer() self.blob_view_visible = False # setup connections self.connections_model = gtk.TreeStore(gobject.TYPE_PYOBJECT); self.connections_tv = self.xml.get_widget("connections_tv") self.connections_tv.set_model(self.connections_model) col = gtk.TreeViewColumn("MySQL-Hosts") pixbuf_renderer = gtk.CellRendererPixbuf() col.pack_start(pixbuf_renderer, False) #col.add_attribute(pixbuf_renderer, "pixbuf", 1) col.set_cell_data_func(pixbuf_renderer, self.render_connections_pixbuf) text_renderer = gtk.CellRendererText() col.pack_end(text_renderer) #col.add_attribute(text_renderer, "text", 2) col.set_cell_data_func(text_renderer, self.render_connections_text) self.connections_tv.append_column(col) self.connections_tv.connect("row-expanded", self.on_row_expanded) self.connections_tv.connect("row-collapsed", self.on_row_collapsed) #connections_tv.insert_column_with_data_func(-1, "MySQL-Hosts", col) # processlist self.processlist_tv = self.xml.get_widget("processlist_treeview") self.processlist_model = None self.local_search_window = self.xml.get_widget("localsearch_window") self.local_search_entry = self.xml.get_widget("local_search_entry") self.local_search_entry.connect("activate", lambda *a: self.local_search_window.response(gtk.RESPONSE_OK)); self.local_search_start_at_first_row = self.xml.get_widget("search_start_at_first_row") self.local_search_case_sensitive = self.xml.get_widget("search_case_sensitive") self.clipboard = gtk.Clipboard(gtk.gdk.display_get_default(), "CLIPBOARD") self.pri_clipboard = gtk.Clipboard(gtk.gdk.display_get_default(), "PRIMARY") self.field_edit = self.xml.get_widget("field_edit") self.field_edit_content = self.xml.get_widget("edit_field_content") self.table_property_labels = [] self.table_property_entries = [] self.table_description_size = (0, 0) self.table_description = self.xml.get_widget("table_description") self.query_notebook = self.xml.get_widget("query_notebook") self.tooltips = gtk.Tooltips() self.sort_timer_running = False self.execution_timer_running = False self.field_conditions_initialized = False self.current_host = None self.current_processlist_host = None self.processlist_timer_running = False self.init_config() if not hasattr(self, "state"): self.hosts = {} self.load_config() self.queries = [] self.add_query_tab(mysql_query_tab(self.xml, self.query_notebook)) else: self.hosts = self.state["hosts"] self.load_config(True) self.queries = [] first = True for q in self.state["queries"]: if first: xml = self.xml else: xml = gtk.glade.XML(self.glade_file, "first_query") new_page = xml.get_widget("first_query") q.__init__(xml, self.query_notebook) self.add_query_tab(q) if first: first = False self.query_notebook.set_tab_label_text(new_page, q.name) else: label = gtk.Label(q.name) label.show() self.query_notebook.append_page(new_page, label) if self.config["theme"]: self.select_theme(self.config["theme"]) if int(self.config["ping_connection_interval"]) > 0: gobject.timeout_add( int(self.config["ping_connection_interval"]) * 1000, self.on_connection_ping ) self.init_plugins() def select_theme(self, theme): theme_file = os.path.join(theme, "gtk-2.0", "gtkrc") if not os.access(theme_file, os.R_OK): theme_file = os.path.join(themes_path, theme, "gtk-2.0", "gtkrc") if not os.access(theme_file, os.R_OK): print "could not load theme file: %r" % theme_file return print "loading theme file %r" % theme_file gtk.rc_parse(theme_file) def on_reload_plugins_activate(self, *args): self.unload_plugins() self.load_plugins() def init_plugin(self, plugin): try: plugin_init = getattr(plugin, "plugin_init") except: return True plugin_init(self) def unload_plugin(self, plugin): try: plugin_unload = getattr(plugin, "plugin_unload") return plugin_unload() except: return True def on_tab_close_eventbox_button_press_event(self, eventbox, event): self.on_closequery_button_clicked(None) def load_plugins(self): for path in self.plugin_pathes: for plugin_name in os.listdir(path): plugin_dir = os.path.join(path, plugin_name) if not os.path.isdir(plugin_dir) or plugin_name[0] == ".": continue if plugin_name in self.plugins: #print "reloading plugin", plugin_name, "...", plugin = reload(self.plugins[plugin_name]) else: #print "loading plugin", plugin_name, "...", plugin = __import__(plugin_name) self.plugins[plugin_name] = plugin ret = self.init_plugin(plugin) #print "done", ret def unload_plugins(self): """ not really an unload - i just asks the module to cleanup """ for plugin_name, plugin in self.plugins.iteritems(): #print "unloading plugin", plugin_name, "...", self.unload_plugin(plugin) #print "done" def init_plugins(self): plugins_pathes = [ os.path.join(self.config_path, "plugins"), os.path.join(emma_path, "plugins") ] self.plugin_pathes = [] self.plugins = {} for path in plugins_pathes: if not os.path.isdir(path): print "plugins-dir", path, "does not exist" continue if not path in sys.path: sys.path.insert(0, path) self.plugin_pathes.append(path) self.load_plugins() def __getstate__(self): hosts = [] iter = self.connections_model.get_iter_root() while iter: host = self.connections_model.get_value(iter, 0) hosts.append(host) iter = self.connections_model.iter_next(iter) sql_logs = [] iter = self.sql_log_model.get_iter_root() while iter: log = self.sql_log_model.get(iter, 0, 1, 2) sql_logs.append(log) iter = self.sql_log_model.iter_next(iter) return {"hosts": hosts, "queries": self.queries, "sql_logs": sql_logs} def init_config(self): for i in ["HOME", "USERPROFILE"]: filename = os.getenv(i) if filename: break if not filename: filename = "." filename = filename + "/.emma" if os.path.isfile(filename): print "detected emma config file", filename, "converting to directory" temp_dir = filename + "_temp" os.mkdir(temp_dir) os.rename(filename, os.path.join(temp_dir, "emmarc")) os.rename(temp_dir, filename) self.config_path = filename self.config_file = "emmarc" def add_query_tab(self, qt): self.query_count += 1 self.current_query = qt self.queries.append(qt) qt.set_query_encoding(self.config["db_encoding"]) qt.set_query_font(self.config["query_text_font"]) qt.set_result_font(self.config["query_result_font"]) if self.config_get_bool("query_text_wrap"): qt.set_wrap_mode(gtk.WRAP_WORD) else: qt.set_wrap_mode(gtk.WRAP_NONE) qt.set_current_host(self.current_host) def del_query_tab(self, qt): if self.current_query == qt: self.current_query = None i = self.queries.index(qt) del self.queries[i] def on_connection_ping(self): iter = self.connections_model.get_iter_root() while iter: host = self.connections_model.get_value(iter, 0) if host.connected: print "pinging %s" % host.name, if not host.ping(): print "...error! reconnect seems to fail!" else: print "ok" iter = self.connections_model.iter_next(iter) return True def search_query_end(self, text, start): try: r = self.query_end_re except: r = self.query_end_re = re.compile(r'("(?:[^\\]|\\.)*?")|(\'(?:[^\\]|\\.)*?\')|(`(?:[^\\]|\\.)*?`)|(;)') while 1: result = re.search(r, text[start:]) if not result: return None start += result.end() if result.group(4): return start def is_query_editable(self, query, result = None): table, where, field, value, row_iter = self.get_unique_where(query) if not table or not where: return False return True def is_query_appendable(self, query): if not self.current_host: return False try: r = self.query_select_re except: r = self.query_select_re = re.compile(r'(?i)("(?:[^\\]|\\.)*?")|(\'(?:[^\\]|\\.)*?\')|(`(?:[^\\]|\\.)*?`)|(union)|(select[ \r\n\t]+(.*)[ \r\n\t]+from[ \r\n\t]+(.*))') start = 0 while 1: result = re.search(r, query[start:]) if not result: return False start += result.end() if result.group(4): return False # union if result.group(5) and result.group(6) and result.group(7): break # found select return result def read_expression(self, query, start=0, concat=True, update_function=None, update_offset=0, icount=0): # r'(?is)("(?:[^\\]|\\.)*?")|(\'(?:[^\\]|\\.)*?\')|(`(?:[^\\]|\\.)*?`)|([^ \r\n\t]*[ \r\n\t]*\()|(\))|([0-9]+(?:\\.[0-9]*)?)|([^ \r\n\t,()"\'`]+)|(,)') try: r = self.query_expr_re except: r = self.query_expr_re = re.compile(r""" (?is) ("(?:[^\\]|\\.)*?")| # double quoted strings ('(?:[^\\]|\\.)*?')| # single quoted strings (`(?:[^\\]|\\.)*?`)| # backtick quoted strings (/\*.*?\*/)| # c-style comments (\#.*$)| # shell-style comments (\))| # closing parenthesis ([0-9]+(?:\\.[0-9]*)?)| # numbers ([,;])| # comma or semicolon ([^ \r\n\t\(\)]*[ \r\n\t]*\()| # opening parenthesis with leading whitespace ([^ \r\n\t,;()"'`]+) # everything else... """, re.VERBOSE) # print "read expr in", query match = r.search(query, start) #if match: print match.groups() if not match: return (None, None) for i in range(1, match.lastindex + 1): if match.group(i): t = match.group(i) e = match.end(i) current_token = t if current_token[len(current_token) - 1] == "(": while 1: icount += 1 if update_function is not None and icount >= 10: icount = 0 update_function(False, update_offset + e) #print "at", [query[e:e+15]], "..." exp, end = self.read_expression(query, e, False, update_function, update_offset, icount) #print "got inner exp:", [exp] if not exp: break e = end if concat: t += " " + exp if exp == ")": break return (t, e) print "should not happen!" return (None, None) def get_order_from_query(self, query, return_before_and_after=False): current_order = [] try: r = self.query_order_re except: r = self.query_order_re = re.compile(re_src_query_order) # get current order by clause match = re.search(r, query) if not match: print "no order found in", [query] print "re:", [re_src_query_order] return current_order before, order, after = match.groups() order.lower() start = 0 while 1: item = [] while 1: ident, end = self.read_expression(order[start:]) if not ident: break if ident == ",": break if ident[0] == "`": ident = ident[1:-1] item.append(ident) start += end l = len(item) if l == 0: break elif l == 1: item.append(True) elif l == 2: if item[1].lower() == "asc": item[1] = True else: item[1] = False else: print "unknown order item:", item, "ignoring..." item = None if item: current_order.append(tuple(item)) if not ident: break start += 1 # comma return current_order def on_remember_order_clicked(self, button): query = self.current_query.last_source current_order = self.get_order_from_query(query) result = self.is_query_appendable(query) if not result: return (None, None, None, None, None) table_list = result.group(7) table_list = table_list.replace(" join ", ",") table_list = re.sub("(?i)(?:order[ \t\r\n]by.*|limit.*|group[ \r\n\t]by.*|order[ \r\n\t]by.*|where.*)", "", table_list) table_list = table_list.replace("`", "") tables = map(lambda s: s.strip(), table_list.split(",")) if len(tables) > 1: self.show_message("store table order", "can't store table order of multi-table queries!") return table = tables[0] print "table: %s order: %s" % (table, current_order) config_name = "stored_order_db_%s_table_%s" % (self.current_host.current_db.name, table) self.config[config_name] = str(current_order) if not self.current_host.current_db.name in self.stored_orders: self.stored_orders[self.current_host.current_db.name] = {} self.stored_orders[self.current_host.current_db.name][table] = current_order self.save_config() def get_field_list(self, s): # todo USE IT! fields = [] start = 0 while 1: item = [] while 1: ident, end = self.read_expression(s[start:]) if not ident: break if ident == ",": break if ident[0] == "`": ident = ident[1:-1] item.append(ident) start += end if len(item) == 1: fields.append(item[0]) else: fields.append(item) if not ident: break print "found fields:", fields return fields def escape_fieldname(self, field): if not re.search("[` ]", field): return field return "`%s`" % field.replace("`", r"\`") def get_unique_where(self, query, path=None, col_num=None, return_fields=False): # call is_query_appendable before! result = self.is_query_appendable(query) if not result: return (None, None, None, None, None) field_list = result.group(6) table_list = result.group(7) # check tables table_list = table_list.replace(" join ", ",") table_list = re.sub("(?i)(?:order[ \t\r\n]by.*|limit.*|group[ \r\n\t]by.*|order[ \r\n\t]by.*|where.*)", "", table_list) table_list = table_list.replace("`", "") tables = table_list.split(",") if len(tables) > 1: print "sorry, i can't edit queries with more than one than one source-table:", tables return (None, None, None, None, None) # get table_name table = tables[0].strip(" \r\n\t").strip("`'\"") print "table:", table # check for valid fields field_list = re.sub("[\r\n\t ]+", " ", field_list) field_list = re.sub("'.*?'", "__BAD__STRINGLITERAL", field_list) field_list = re.sub("\".*?\"", "__BAD__STRINGLITERAL", field_list) field_list = re.sub("\\(.*?\\)", "__BAD__FUNCTIONARGUMENTS", field_list) field_list = re.sub("\\|", "__PIPE__", field_list) temp_fields = field_list.split(",") fields = [] for f in temp_fields: fields.append(f.strip("` \r\n\t")) print "fields:", fields wildcard = False for field in fields: if field.find("*") != -1: wildcard = True break; # find table handle! tries = 0 new_tables = [] while 1: try: th = self.current_host.current_db.tables[table] break except: tries += 1 if tries > 1: print "query not editable, because table '%s' is not found in db %s" % (table, self.current_host.current_db) return (None, None, None, None, None) new_tables = self.current_host.current_db.refresh() continue # does this field really exist in this table? c = 0 possible_primary = possible_unique = "" unique = primary = ""; pri_okay = uni_okay = 0 for i in new_tables: self.current_host.current_db.tables[i].refresh(False) if not th.fields and not table in new_tables: th.refresh(False) row_iter = None if path: row_iter = self.current_query.model.get_iter(path) # get unique where_clause for field, field_pos in zip(th.field_order, range(len(th.field_order))): props = th.fields[field] if pri_okay >= 0 and props[3] == "PRI": if possible_primary: possible_primary += ", " possible_primary += field if wildcard: c = field_pos else: c = None try: c = fields.index(field) except: pass if not c is None: pri_okay = 1 if path: value = self.current_query.model.get_value(row_iter, c) if primary: primary += " and " primary += "`%s`='%s'" % (field, value) if uni_okay >= 0 and props[3] == "UNI": if possible_unique: possible_unique += ", " possible_unique += field if wildcard: c = field_pos else: c = None try: c = fields.index(field) except: pass if not c is None: uni_okay = 1 if path: value = self.current_query.model.get_value(row_iter, c) if unique: unique += " and " unique += "`%s`='%s'" % (field, value) if uni_okay < 1 and pri_okay < 1: possible_key = "(i can't see any key-fields in this table...)" if possible_primary: possible_key = "e.g.'%s' would be useful!" % possible_primary; elif possible_unique: possible_key = "e.g.'%s' would be useful!" % possible_unique; print "no edit-key found. try to name a key-field in your select-clause.", possible_key return (table, None, None, None, None) value = "" field = None if path: where = primary if not where: where = unique if not where: where = None if not col_num is None: value = self.current_query.model.get_value(row_iter, col_num) print col_num, fields if wildcard: field = th.field_order[col_num] else: field = fields[col_num] else: where = possible_primary + possible_unique # get current edited field and value by col_num #print "%s, %s, %s, %s, %s" % (table, where, field, value, row_iter) if return_fields: return table, where, field, value, row_iter, fields return table, where, field, value, row_iter def on_row_expanded(self, tv, iter, path): o = tv.get_model().get_value(iter, 0) if len(path) > 3: return o.expanded = True def on_row_collapsed(self, tv, iter, path): o = tv.get_model().get_value(iter, 0) if len(path) > 3: return o.expanded = False def on_remove_order_clicked(self, button): query = self.current_query.last_source try: r = self.query_order_re except: r = self.query_order_re = re.compile(re_src_query_order) match = re.search(r, query) if not match: return before, order, after = match.groups() new_query = re.sub("(?i)order[ \r\n\t]+by[ \r\n\t]+", "", before + after) self.current_query.set(new_query) self.sort_timer_running = False self.on_execute_query_clicked() def on_query_column_sort(self, column, col_num): query = self.current_query.last_source current_order = self.get_order_from_query(query) col = column.get_title().replace("__", "_") new_order = [] for c, o in current_order: if c == col: if o: new_order.append([col, False]) col = None else: new_order.append([c, o]) if col: new_order.append([self.escape_fieldname(col), True]) try: r = self.query_order_re except: r = self.query_order_re = re.compile(re_src_query_order) match = re.search(r, query) if match: before, order, after = match.groups() order = "" addition = "" else: match = re.search(re_src_after_order, query) if not match: before = query after = "" else: before = query[0:match.start()] after = match.group() addition = "\norder by\n\t" order = "" for col, o in new_order: if order: order += ",\n\t" order += col if not o: order += " desc" if order: new_query = ''.join([before, addition, order, after]) else: new_query = re.sub("(?i)order[ \r\n\t]+by[ \r\n\t]+", "", before + after) self.current_query.set(new_query) if self.config["result_view_column_sort_timeout"] <= 0: on_execute_query_clicked() new_order = dict(new_order) for col in self.current_query.treeview.get_columns(): field_name = col.get_title().replace("__", "_") try: sort_col = new_order[field_name] col.set_sort_indicator(True) if sort_col: col.set_sort_order(gtk.SORT_ASCENDING) else: col.set_sort_order(gtk.SORT_DESCENDING) except: col.set_sort_indicator(False) if not self.sort_timer_running: self.sort_timer_running = True gobject.timeout_add( 100 + int(self.config["result_view_column_sort_timeout"]), self.on_sort_timer ) self.sort_timer_execute = time.time() + int(self.config["result_view_column_sort_timeout"]) / 1000. def on_sort_timer(self): if not self.sort_timer_running: return False # aborted if self.sort_timer_execute > time.time(): return True # new order -> wait again self.sort_timer_running = False self.on_execute_query_clicked() return False # done def on_query_change_data(self, cellrenderer, path, new_value, col_num, force_update=False): q = self.current_query row_iter = q.model.get_iter(path) if q.append_iter and q.model.iter_is_valid(q.append_iter) and q.model.get_path(q.append_iter) == q.model.get_path(row_iter): q.filled_fields[q.treeview.get_column(col_num).get_title().replace("__", "_")] = new_value q.model.set_value(row_iter, col_num, new_value) return table, where, field, value, row_iter = self.get_unique_where(q.last_source, path, col_num) if force_update == False and new_value == value: return update_query = u"update `%s` set `%s`='%s' where %s limit 1" % ( table, field, self.current_host.escape(new_value), where ) if self.current_host.query(update_query, encoding=q.encoding): print "set new value:", [new_value] q.model.set_value(row_iter, col_num, new_value) return True return False def on_blob_wrap_check_clicked(self, button): if button.get_active(): self.blob_tv.set_wrap_mode(gtk.WRAP_WORD) else: self.blob_tv.set_wrap_mode(gtk.WRAP_NONE) def on_blob_load_clicked(self, button): d = self.assign_once("load dialog", gtk.FileChooserDialog, "load blob contents", self.mainwindow, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() try: fp = file(filename, "rb") query_text = fp.read().decode(self.current_query.encoding, "ignore") fp.close() except: self.show_message("load blob contents", "loading blob contents from file %s: %s" % (filename, sys.exc_value)) return self.blob_tv.get_buffer().set_text(query_text) def on_blob_save_clicked(self, button): d = self.assign_once("save dialog", gtk.FileChooserDialog, "save blob contents", self.mainwindow, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() if os.path.exists(filename): if not os.path.isfile(filename): self.show_message("save blob contents", "%s already exists and is not a file!" % filename) return if not self.confirm("overwrite file?", "%s already exists! do you want to overwrite it?" % filename): return b = self.blob_tv.get_buffer() new_value = b.get_text(b.get_start_iter(), b.get_end_iter()).encode(self.current_query.encoding, "ignore") try: fp = file(filename, "wb") fp.write(new_value) fp.close() except: self.show_message("save blob contents", "error writing query to file %s: %s" % (filename, sys.exc_value)) def on_delete_record_tool_clicked(self, button): q = self.current_query path, column = q.treeview.get_cursor() if not path: return row_iter = q.model.get_iter(path) if q.append_iter and q.model.iter_is_valid(q.append_iter) and q.model.get_path(q.append_iter) == q.model.get_path(row_iter): q.append_iter = None q.apply_record.set_sensitive(False) else: table, where, field, value, row_iter = self.get_unique_where(q.last_source, path) if not table or not where: show_message("delete record", "could not delete this record!?") return update_query = "delete from `%s` where %s limit 1" % (table, where) if not self.current_host.query(update_query, encoding=q.encoding): return if not q.model.remove(row_iter): row_iter = q.model.get_iter_first() while row_iter: new = q.model.iter_next(row_iter) if new is None: break row_iter = new if row_iter: q.treeview.set_cursor(q.model.get_path(row_iter)) def on_add_record_tool_clicked(self, button): q = self.current_query if not q.add_record.get_property("sensitive"): return path, column = q.treeview.get_cursor() if path: iter = q.model.insert_after(q.model.get_iter(path)) else: iter = q.model.append() q.treeview.grab_focus() q.treeview.set_cursor(q.model.get_path(iter)) q.filled_fields = dict() q.append_iter = iter q.apply_record.set_sensitive(True) def on_reload_self_activate(self, item): pass def on_apply_record_tool_clicked(self, button): q = self.current_query if not q.append_iter: return query = "" for field, value in q.filled_fields.iteritems(): if query: query += ", " if not value.isdigit(): value = "'%s'" % self.current_host.escape(value) query += "%s=%s" % (self.escape_fieldname(field), value) if query: table, where, field, value, row_iter, fields = self.get_unique_where(q.last_source, return_fields=True) update_query = "insert into `%s` set %s" % (table, query) if not self.current_host.query(update_query, encoding=q.encoding): return False insert_id = self.current_host.insert_id() print "insert id:", insert_id where_fields = map(lambda s: s.strip(), where.split(",")) print "where fields:", where_fields print "select fields:", fields print "from", [table, where, field, value, row_iter] if not where_fields: print "no possible key found to retrieve newly created record" else: th = self.current_host.current_db.tables[table] wc = [] for field in where_fields: props = th.fields[field] auto_increment = props[5].find("auto_increment") != -1 if auto_increment: value = insert_id else: if field in q.filled_fields: # use filled value value = "'%s'" % self.current_host.escape(q.filled_fields[field]) else: # use field default value (maybe none) value = props[4] if not value is None: value = "'%s'" % self.current_host.escape(value) wc.append("%s=%s" % (self.escape_fieldname(field), value)) where = " and ".join(wc) print "select where:", where if fields == ["*"]: field_selector = "*" else: field_selector = ", ".join(map(self.escape_fieldname, fields)) self.current_host.query("select %s from `%s` where %s limit 1" % (field_selector, table, where)) result = self.current_host.handle.store_result().fetch_row(0) if len(result) < 1: print "error: can't find modfied row!?" else: row = result[0] for index, value in enumerate(row): if not value is None: value = value.decode(q.encoding) q.model.set_value(q.append_iter, index, value) else: q.model.remove(q.append_iter) q.append_iter = None q.apply_record.set_sensitive(False) return True def on_message_notebook_switch_page(self, nb, pointer, page): self.blob_view_visible = (page == 2) if self.blob_view_visible: self.on_query_view_cursor_changed(self.current_query.treeview) def on_query_view_cursor_changed(self, tv): q = self.current_query path, column = q.treeview.get_cursor() if not path: return if self.blob_view_visible and column: iter = q.model.get_iter(path) col = q.treeview.get_columns().index(column) self.blob_encoding = q.encoding value = q.model.get_value(iter, col) if value is None: # todo signal null value self.blob_buffer.set_text("") else: self.blob_buffer.set_text(value) self.blob_tv.set_sensitive(True) else: self.blob_buffer.set_text("") self.blob_tv.set_sensitive(False) if q.append_iter: if path == q.model.get_path(q.append_iter): return self.on_apply_record_tool_clicked(None) def on_execute_query_from_disk_activate(self, button, filename=None): if not self.current_host: self.show_message("execute query from disk", "no host selected!") return d = self.get_widget("execute_query_from_disk") fc = self.assign_once("eqfd_file_chooser", self.xml.get_widget, "eqfd_file_chooser") if filename: fc.set_filename(filename) else: #fc.set_filename("/home/flo/fact24_data_small.sql") #fc.set_filename("/home/flo/very_small.sql") fc.set_filename("/home/flo/out.sql") d.show() def on_eqfd_limit_db_toggled(self, button): entry = self.get_widget("eqfd_db_entry") entry.set_sensitive(button.get_active()) def on_eqfd_exclude_toggled(self, button): entry = self.get_widget("eqfd_exclude_entry") entry.set_sensitive(button.get_active()) def on_abort_execute_from_disk_clicked(self, button): d = self.get_widget("execute_query_from_disk") d.hide() def get_widget(self, name): return self.assign_once("widget_%s" % name, self.xml.get_widget, name) def read_query(self, query, start=0): try: r = self.find_query_re rw = self.white_find_query_re except: r = self.find_query_re = re.compile(r""" (?s) ( ("(?:[^\\]|\\.)*?")| # double quoted strings ('(?:[^\\]|\\.)*?')| # single quoted strings (`(?:[^\\]|\\.)*?`)| # backtick quoted strings (/\*.*?\*/)| # c-style comments (\#.*$)| # shell-style comments ([^;]) # everything but a semicolon )+ """, re.VERBOSE) rw = self.white_find_query_re = re.compile("[ \r\n\t]+") m = rw.match(query, start) if m: start = m.end(0) match = r.match(query, start) if not match: return None, len(query) return (match.start(0), match.end(0)) def read_one_query(self, fp, start=None, count_lines=0, update_function=None, only_use_queries=False, start_line=1): current_query = [] self.read_one_query_started = True while self.read_one_query_started: gc.collect() if start is None: while 1: line = fp.readline() #print "line:", [line] if line == "": if len(current_query) > 0: return (' '.join(current_query), start, count_lines) return (None, start, count_lines) if count_lines is not None: count_lines += 1 if update_function is not None: lb = fp.tell() - len(line) update_function(False, lb) if count_lines is not None and count_lines <= start_line: #print count_lines continue first = line.lstrip("\r\n\t ")[0:15].lower() if only_use_queries and first[0:3] != "use" and first != "create database": continue if line.lstrip(" \t")[0:2] != "--": break #print "skipping line", [line] self.last_query_line = line start = 0 else: lb = fp.tell() - len(self.last_query_line) line = self.last_query_line start, end = self.read_query(line, start) next = line[end:end+1] #print "next: '%s'" % next if start is not None: #print "append query", [line[start:end]] current_query.append(line[start:end]) if next == ";": return (''.join(current_query), end + 1, count_lines) start = None return (None, None, None) def on_start_execute_from_disk_clicked(self, button): host = self.current_host d = self.get_widget("execute_query_from_disk") fc = self.get_widget("eqfd_file_chooser") exclude = self.get_widget("eqfd_exclude").get_active() exclude_regex = self.get_widget("eqfd_exclude_entry").get_text() exclude = exclude and exclude_regex if exclude: try: exclude_regex = re.compile(exclude_regex, re.DOTALL) except: self.show_message("execute query from disk", "error compiling your regular expression: %s" % (sys.exc_value)) return filename = fc.get_filename() try: sbuf = os.stat(filename) except: self.show_message("execute query from disk", "%s does not exists!" % filename) return if not S_ISREG(sbuf.st_mode): self.show_message("execute query from disk", "%s exists, but is not a regular file!" % filename) return size = sbuf.st_size try: fp = bz2.BZ2File(filename, "r", 1024 * 8) self.last_query_line = fp.readline() self.using_compression = True except: self.using_compression = False fp = None if fp is None: try: fp = file(filename, "rb") self.last_query_line = fp.readline() except: self.show_message("execute query from disk", "error opening query from file %s: %s" % (filename, sys.exc_value)) return d.hide() start_line = self.get_widget("eqfd_start_line").get_value() if start_line < 1: start_line = 1 ui = self.get_widget("eqfd_update_interval") update_interval = ui.get_value() if update_interval == 0: update_interval = 2 p = self.get_widget("execute_from_disk_progress") pb = self.get_widget("exec_progress") offset_entry = self.get_widget("edfq_offset") line_entry = self.get_widget("eqfd_line") query_entry = self.get_widget("eqfd_query") eta_label = self.get_widget("eqfd_eta") append_to_log = self.get_widget("eqfd_append_to_log").get_active() stop_on_error = self.get_widget("eqfd_stop_on_error").get_active() limit_dbname = self.get_widget("eqfd_db_entry").get_text() limit_db = self.get_widget("eqfd_limit_db").get_active() and limit_dbname != "" if limit_db: limit_re = re.compile("(?is)^use[ \r\n\t]+`?" + re.escape(limit_dbname) + "`?|^create database[^`]+`?" + re.escape(limit_dbname) + "`?") limit_end_re = re.compile("(?is)^use[ \r\n\t]+`?.*`?|^create database") last = 0 start = time.time() def update_ui(force=False, offset=0): global last_update now = time.time() if not force and now - last_update < update_interval: return last_update = now pos = offset f = float(pos) / float(size) expired = now - start if not self.using_compression and expired > 10: sr = float(expired) / float(pos) * float(size - pos) remaining = " (%.0fs remaining)" % sr eta_label.set_text("eta: %-19.19s" % datetime.datetime.fromtimestamp(now + sr)) else: remaining = "" query_entry.set_text(query[0:512]) offset_entry.set_text("%d" % pos) line_entry.set_text("%d" % current_line) if f > 1.0: f = 1.0 pb.set_fraction(f) pb_text = "%.2f%%%s" % (f * 100.0, remaining) pb.set_text(pb_text) self.process_events() new_line = 1 current_line = start query = "" p.show() while time.time() - start < 0.10: update_ui(True) self.query_from_disk = True line_offset = 0 found_db = False while self.query_from_disk: current_line = new_line query, line_offset, new_line = self.read_one_query(fp, line_offset, current_line, update_ui, limit_db and not found_db, start_line) if current_line < start_line: current_line = start_line if query is None: break if limit_db: if not found_db: first = query.lstrip("\r\n\t ")[0:15].lower() if (first[0:3] == "use" or first == "create database") and limit_re.search(query): found_db = True else: if limit_end_re.search(query) and not limit_re.search(query): found_db = False update_ui(False, fp.tell()) if not limit_db or found_db: if exclude and exclude_regex.match(query): print "skipping query", [query[0:80]] elif not host.query(query, True, append_to_log) and stop_on_error: self.show_message("execute query from disk", "an error occoured. maybe remind the line number and press cancel to close this dialog!") self.query_from_disk = False break #print "exec", [query] query = "" update_ui(True, fp.tell()) fp.close() if not self.query_from_disk: self.show_message("execute query from disk", "aborted by user whish - click cancel again to close window") return else: self.show_message("execute query from disk", "done!") p.hide() def on_cancel_execute_from_disk_clicked(self, button): if not self.query_from_disk: p = self.assign_once("execute_from_disk_progress", self.xml.get_widget, "execute_from_disk_progress") p.hide() return self.read_one_query_started = False self.query_from_disk = False def on_execute_query_clicked(self, button=None, query=None): if not self.current_query: return q = self.current_query if not query: b = q.textview.get_buffer() text = b.get_text(b.get_start_iter(), b.get_end_iter()) else: text = query self.current_host = host = q.current_host if not host: self.show_message( "error executing this query!", "could not execute query, because there is no selected host!" ) return self.current_db = q.current_db if q.current_db: host.select_database(q.current_db) elif host.current_db: if not self.confirm("query without selected db", """warning: this query tab has no database selected but the host-connection already has the database '%s' selected. the author knows no way to deselect this database. do you want to continue?""" % host.current_db.name): return update = False select = False q.editable = False # single popup q.add_record.set_sensitive(False) q.delete_record.set_sensitive(False) # per query buttons q.add_record.set_sensitive(False) q.delete_record.set_sensitive(False) q.apply_record.set_sensitive(False) q.local_search.set_sensitive(False) q.remove_order.set_sensitive(False) q.save_result.set_sensitive(False) q.save_result_sql.set_sensitive(False) affected_rows = 0 last_insert_id = 0 num_rows = 0 num_fields = 0 query_time = 0 download_time = 0 display_time = 0 query_count = 0 total_start = time.time(); # cleanup last query model and treeview for col in q.treeview.get_columns(): q.treeview.remove_column(col) if q.model: q.model.clear() start = 0 while start < len(text): query_start = start # search query end query_start, end = self.read_query(text, start) if query_start is None: break; thisquery = text[query_start:end] start = end + 1 thisquery.strip(" \r\n\t;") if not thisquery: continue # empty query query_count += 1 query_hint = re.sub("[\n\r\t ]+", " ", thisquery[:40]) q.label.set_text("executing query %d %s..." % (query_count, query_hint)) q.label.window.process_updates(False) appendable = False appendable_result = self.is_query_appendable(thisquery) if appendable_result: appendable = True q.editable = self.is_query_editable(thisquery, appendable_result) print "appendable: %s, editable: %s" % (appendable, q.editable) ret = host.query(thisquery, encoding=q.encoding) query_time += host.query_time # if stop on error is enabled if not ret: print [host.last_error] message = "error at: %s" % host.last_error.replace("You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near ", "") message = "error at: %s" % message.replace("You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ", "") line_pos = 0 pos = message.find("at line ") if pos != -1: line_no = int(message[pos + 8:]) while 1: line_no -= 1 if line_no < 1: break p = thisquery.find("\n", line_pos) if p == -1: break; line_pos = p + 1 i = q.textview.get_buffer().get_iter_at_offset(query_start + line_pos) match = re.search("error at: '(.*)'", message, re.DOTALL) if match and match.group(1): # set focus and cursor! #print "search for ->%s<-" % match.group(1) pos = text.find(match.group(1), query_start + line_pos, query_start + len(thisquery)) if not pos == -1: i.set_offset(pos); else: match = re.match("Unknown column '(.*?')", message) if match: # set focus and cursor! pos = thisquery.find(match.group(1)) if not pos == 1: i.set_offset(query_start + pos); q.textview.get_buffer().place_cursor(i) q.textview.scroll_to_iter(i, 0.0) q.textview.grab_focus() q.label.set_text(re.sub("[\r\n\t ]+", " ", message)) return field_count = host.handle.field_count() if field_count == 0: # query without result update = True; affected_rows += host.handle.affected_rows() last_insert_id = host.handle.insert_id() continue # query with result q.append_iter = None q.local_search.set_sensitive(True) q.add_record.set_sensitive(appendable) q.delete_record.set_sensitive(q.editable) select = True q.last_source = thisquery # get sort order! sortable = True # todo current_order = self.get_order_from_query(thisquery) sens = False if len(current_order) > 0: sens = True q.remove_order.set_sensitive(sens and sortable) sort_fields = dict() for c, o in current_order: sort_fields[c.lower()] = o q.label.set_text("downloading resultset...") q.label.window.process_updates(False) start_download = time.time() result = host.handle.store_result() download_time = time.time() - start_download if download_time < 0: download_time = 0 q.label.set_text("displaying resultset..."); q.label.window.process_updates(False) # store field info q.result_info = result.describe() num_rows = result.num_rows() for col in q.treeview.get_columns(): q.treeview.remove_column(col) columns = [gobject.TYPE_STRING] * field_count q.model = gtk.ListStore(*columns) q.treeview.set_model(q.model) q.treeview.set_rules_hint(True) q.treeview.set_headers_clickable(True) for i in range(field_count): title = q.result_info[i][0].replace("_", "__").replace("[\r\n\t ]+", " ") text_renderer = gtk.CellRendererText() if q.editable: text_renderer.set_property("editable", True) text_renderer.connect("edited", self.on_query_change_data, i) l = q.treeview.insert_column_with_data_func(-1, title, text_renderer, self.render_mysql_string, i) col = q.treeview.get_column(l - 1) if self.config_get_bool("result_view_column_resizable"): col.set_resizable(True) else: col.set_resizable(False); col.set_min_width(int(self.config["result_view_column_width_min"])) col.set_max_width(int(self.config["result_view_column_width_max"])) if sortable: col.set_clickable(True) col.connect("clicked", self.on_query_column_sort, i) # set sort indicator field_name = q.result_info[i][0].lower() try: sort_col = sort_fields[field_name] col.set_sort_indicator(True) if sort_col: col.set_sort_order(gtk.SORT_ASCENDING) else: col.set_sort_order(gtk.SORT_DESCENDING) except: col.set_sort_indicator(False) else: col.set_clickable(False) col.set_sort_indicator(False) cnt = 0 start_display = time.time() last_display = start_display for row in result.fetch_row(0): q.model.append(map(lambda f: f and f.decode(q.encoding, "replace"), row)) cnt += 1; if not cnt % 100 == 0: continue now = time.time() if (now - last_display) < 0.2: continue q.label.set_text("displayed %d rows..." % cnt) q.label.window.process_updates(False) last_display = now display_time = time.time() - start_display if display_time < 0: display_time = 0 result = [] if select: # there was a query with a result result.append("rows: %d" % num_rows) result.append("fields: %d" % field_count) q.save_result.set_sensitive(True) q.save_result_sql.set_sensitive(True) if update: # there was a query without a result result.append("affected rows: %d" % affected_rows) result.append("insert_id: %d" % last_insert_id) total_time = time.time() - total_start result.append("| total time: %.2fs (query: %.2fs" % (total_time, query_time)) if select: result.append("download: %.2fs display: %.2fs" % (download_time, display_time)) result.append(")") q.label.set_text(' '.join(result)) self.blob_tv.set_editable(q.editable) self.get_widget("blob_update").set_sensitive(q.editable) self.get_widget("blob_load").set_sensitive(q.editable) # todo update_buttons(); gc.collect() return True def on_save_result_clicked(self, button): if not self.current_query: return d = self.assign_once("save results dialog", gtk.FileChooserDialog, "save results", self.mainwindow, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() if os.path.exists(filename): if not os.path.isfile(filename): self.show_message("save results", "%s already exists and is not a file!" % filename) return if not self.confirm("overwrite file?", "%s already exists! do you want to overwrite it?" % filename): return q = self.current_query iter = q.model.get_iter_first() indices = range(q.model.get_n_columns()) field_delim = self.config["save_result_as_csv_delim"] line_delim = self.config["save_result_as_csv_line_delim"] try: fp = file(filename, "wb") for search, replace in {"\\n": "\n", "\\r": "\r", "\\t": "\t", "\\0": "\0"}.iteritems(): field_delim = field_delim.replace(search, replace) line_delim = line_delim.replace(search, replace) while iter: row = q.model.get(iter, *indices) for field in row: value = field if value is None: value = "" fp.write(value.replace(field_delim, "\\" + field_delim)) fp.write(field_delim) fp.write(line_delim) iter = q.model.iter_next(iter) fp.close() except: self.show_message("save results", "error writing query to file %s: %s" % (filename, sys.exc_value)) def on_save_result_sql_clicked(self, button): if not self.current_query: return title = "save results as sql insert script" d = self.assign_once("save results dialog", gtk.FileChooserDialog, title, self.mainwindow, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() if os.path.exists(filename): if not os.path.isfile(filename): self.show_message(title, "%s already exists and is not a file!" % filename) return if not self.confirm("overwrite file?", "%s already exists! do you want to overwrite it?" % filename): return q = self.current_query iter = q.model.get_iter_first() indices = range(q.model.get_n_columns()) # try to guess target table name from query table_name = "" query = self.current_query.last_source result = self.is_query_appendable(query) if result: table_list = result.group(7) table_list = table_list.replace(" join ", ",") table_list = re.sub("(?i)(?:order[ \t\r\n]by.*|limit.*|group[ \r\n\t]by.*|order[ \r\n\t]by.*|where.*)", "", table_list) table_list = table_list.replace("`", "") tables = map(lambda s: s.strip(), table_list.split(",")) table_name = "_".join(tables) table_name = self.input(title, "please enter the name of the target table:", table_name) if table_name is None: return table_name = self.escape_fieldname(table_name) output_row = None try: fp = file(filename, "wb") fp.write("insert into %s values" % table_name) row_delim = "\n\t" while iter: row = q.model.get(iter, *indices) if not output_row: output_row = range(len(row)) for i, field in enumerate(row): if field is None: field = "NULL" elif not field.isdigit(): field = "'%s'" % q.current_host.escape(field.encode(q.encoding)) output_row[i] = field fp.write("%s(%s)" % (row_delim, ",".join(output_row))) row_delim = ",\n\t" iter = q.model.iter_next(iter) fp.write("\n;\n") fp.close() except: self.show_message(title, "error writing to file %s: %s" % (filename, sys.exc_value)) def assign_once(self, name, creator, *args): try: return self.created_once[name] except: obj = creator(*args) self.created_once[name] = obj return obj def on_save_query_clicked(self, button): if not self.current_query: return d = self.assign_once("save dialog", gtk.FileChooserDialog, "save query", self.mainwindow, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() if os.path.exists(filename): if not os.path.isfile(filename): self.show_message("save query", "%s already exists and is not a file!" % filename) return if not self.confirm("overwrite file?", "%s already exists! do you want to overwrite it?" % filename): return b = self.current_query.textview.get_buffer() query_text = b.get_text(b.get_start_iter(), b.get_end_iter()) try: fp = file(filename, "wb") fp.write(query_text) fp.close() except: self.show_message("save query", "error writing query to file %s: %s" % (filename, sys.exc_value)) def on_load_query_clicked(self, button): if not self.current_query: return d = self.assign_once("load dialog", gtk.FileChooserDialog, "load query", self.mainwindow, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() try: sbuf = os.stat(filename) except: self.show_message("load query", "%s does not exists!" % filename) return if not S_ISREG(sbuf.st_mode): self.show_message("load query", "%s exists, but is not a file!" % filename) return size = sbuf.st_size max = int(self.config["ask_execute_query_from_disk_min_size"]) if size > max: if self.confirm("load query", """ %s is very big (%.2fMB)! opening it in the normal query-view may need a very long time! if you just want to execute this skript file without editing and syntax-highlighting, i can open this file using the execute file from disk function. shall i do this?""" % (filename, size / 1024.0 / 1000.0)): self.on_execute_query_from_disk_activate(None, filename) return try: fp = file(filename, "rb") query_text = fp.read() fp.close() except: self.show_message("save query", "error writing query to file %s: %s" % (filename, sys.exc_value)) return self.current_query.textview.get_buffer().set_text(query_text) def on_save_workspace_activate(self, button): d = self.assign_once("save workspace dialog", gtk.FileChooserDialog, "save workspace", self.mainwindow, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() if os.path.exists(filename): if not os.path.isfile(filename): self.show_message("save workspace", "%s already exists and is not a file!" % filename) return if not self.confirm("overwrite file?", "%s already exists! do you want to overwrite it?" % filename): return try: fp = file(filename, "wb") pickle.dump(self, fp) fp.close() except: self.show_message("save workspace", "error writing workspace to file %s: %s/%s" % (filename, sys.exc_type, sys.exc_value)) def on_restore_workspace_activate(self, button): global new_instance d = self.assign_once("restore workspace dialog", gtk.FileChooserDialog, "restore workspace", self.mainwindow, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)) d.set_default_response(gtk.RESPONSE_ACCEPT) answer = d.run() d.hide() if not answer == gtk.RESPONSE_ACCEPT: return filename = d.get_filename() if not os.path.exists(filename): self.show_message("restore workspace", "%s does not exists!" % filename) return if not os.path.isfile(filename): self.show_message("restore workspace", "%s exists, but is not a file!" % filename) return try: fp = file(filename, "rb") print "i am unpickling:", self new_instance = pickle.load(fp) print "got new instance:", new_instance fp.close() except: self.show_message("restore workspace", "error restoring workspace from file %s: %s/%s" % (filename, sys.exc_type, sys.exc_value)) self.mainwindow.destroy() def __setstate__(self, state): self.state = state def on_local_search_button_clicked(self, button, again = False): if not self.current_query.local_search.get_property("sensitive"): return query_view = self.current_query.treeview self.local_search_start_at_first_row.set_active(False) if not again or not self.local_search_entry.get_text(): self.local_search_entry.grab_focus() answer = self.local_search_window.run() self.local_search_window.hide() if not answer == gtk.RESPONSE_OK: return regex = self.local_search_entry.get_text() if self.local_search_case_sensitive.get_active(): regex = "(?i)" + regex; tm = self.current_query.model fields = tm.get_n_columns() start = tm.get_iter_root() start_column_index = -1 start_path = None if not self.local_search_start_at_first_row.get_active(): start_path, start_column = query_view.get_cursor() if start_path: start = tm.get_iter(start_path) for k in range(fields): if query_view.get_column(k) == start_column: start_column_index = k break else: start_path = None while start: for k in range(fields): v = tm.get_value(start, k) if v is None: continue if re.search(regex, v): path = tm.get_path(start); if start_path and start_path == path and k <= start_column_index: continue # skip! column = query_view.get_column(k) query_view.set_cursor(path, column) query_view.scroll_to_cell(path, column) query_view.grab_focus() return start = tm.iter_next(start) self.show_message("local regex search", "sorry, no match found!\ntry to search from the beginning or execute a less restrictive query...") def on_query_font_clicked(self, button): d = self.assign_once("query text font", gtk.FontSelectionDialog, "select query font") d.set_font_name(self.config["query_text_font"]) answer = d.run() d.hide() if not answer == gtk.RESPONSE_OK: return font_name = d.get_font_name() self.current_query.set_query_font(font_name) self.config["query_text_font"] = font_name self.save_config() def on_query_result_font_clicked(self, button): d = self.assign_once("query result font", gtk.FontSelectionDialog, "select result font") d.set_font_name(self.config["query_result_font"]) answer = d.run() d.hide() if not answer == gtk.RESPONSE_OK: return font_name = d.get_font_name() self.current_query.set_result_font(font_name) self.config["query_result_font"] = font_name self.save_config() def on_newquery_button_clicked(self, button): xml = gtk.glade.XML(self.glade_file, "first_query") tab_label_hbox = gtk.glade.XML(self.glade_file, "tab_label_hbox") new_page = xml.get_widget("first_query") self.add_query_tab(mysql_query_tab(xml, self.query_notebook)) label = tab_label_hbox.get_widget("tab_label_hbox") qtlabel = tab_label_hbox.get_widget("query_tab_label") #qtlabel.set_text("query%d" % self.query_count) self.query_notebook.append_page(new_page, label) self.query_notebook.set_current_page(len(self.queries) - 1) self.current_query.textview.grab_focus() xml.signal_autoconnect(self) tab_label_hbox.signal_autoconnect(self) def on_query_notebook_switch_page(self, nb, pointer, page): if page >= len(self.queries): page = len(self.queries) - 1 q = self.current_query = self.queries[page] self.on_query_db_eventbox_button_press_event(None, None) def on_closequery_button_clicked(self, button): if len(self.queries) == 1: return self.current_query.destroy() self.del_query_tab(self.current_query) self.query_notebook.remove_page(self.query_notebook.get_current_page()) gc.collect() def on_rename_query_tab_clicked(self, button): label = self.current_query.get_label() new_name = self.input("rename tab", "please enter the new name of this tab:", label.get_text() ) if new_name is None: return if new_name == "": self.current_query.last_auto_name = None self.current_query.update_db_label() return self.current_query.user_rename(new_name) def on_processlist_refresh_value_change(self, button): value = button.get_value() if self.processlist_timer_running: return self.processlist_timer_running = True self.processlist_timer_interval = value gobject.timeout_add(int(value * 1000), self.on_processlist_refresh_timeout, button) def on_fc_reset_clicked(self, button): for i in range(self.fc_count): self.fc_entry[i].set_text("") if i == 0: self.fc_combobox[i].set_active(0) self.fc_op_combobox[i].set_active(0) else: self.fc_combobox[i].set_active(-1) self.fc_op_combobox[i].set_active(-1) if i: self.fc_logic_combobox[i - 1].set_active(0) def on_quit_activate(self, item): gtk.main_quit() def on_about_activate(self, item): aboutdialog = self.xml.get_widget("aboutdialog") aboutdialog.set_version(version) aboutdialog.run() aboutdialog.hide() def on_changelog_activate(self, item): fp = file(os.path.join(emma_share_path, "changelog")) changelog = fp.read() fp.close() w = self.xml.get_widget("changelog_window") tv = self.xml.get_widget("changelog_text") tv.get_buffer().set_text(changelog) w.connect('delete-event', self.on_changelog_delete) w.show() def on_changelog_delete(self, window, event): window.hide() return True def on_kill_process(self, button): path, column = self.processlist_tv.get_cursor() if not path or not self.current_host: return iter = self.processlist_model.get_iter(path) process_id = self.processlist_model.get_value(iter, 0) if not self.current_host.query("kill %s" % process_id): self.show_message("sorry", "there was an error while trying to kill process_id %s!" % process_id) def on_sql_log_activate(self, *args): if len(args) == 1: menuitem = args[0] if menuitem.name == "clear_all_entries": self.sql_log_model.clear() path, column = self.sql_log_tv.get_cursor() row = self.sql_log_model[path] if menuitem.name == "copy_sql_log": self.clipboard.set_text(row[2]) self.pri_clipboard.set_text(row[2]) elif menuitem.name == "set_as_query_text": self.current_query.textview.get_buffer().set_text(row[2]) if menuitem.name == "delete_sql_log": iter = self.sql_log_model.get_iter(path) self.sql_log_model.remove(iter) return True tv, path, tvc = args query = tv.get_model()[path][2] self.current_query.textview.get_buffer().set_text(query) return True def on_sql_log_button_press(self, tv, event): if not event.button == 3: return False res = tv.get_path_at_pos(int(event.x), int(event.y)); if not res: return False self.xml.get_widget("sqllog_popup").popup(None, None, None, event.button, event.time); return True def on_connections_button_release(self, tv, event): if not event.button == 3: return False res = tv.get_path_at_pos(int(event.x), int(event.y)); menu = None if not res or len(res[0]) == 1: self.xml.get_widget("modify_connection").set_sensitive(not not res) self.xml.get_widget("delete_connection").set_sensitive(not not res) connected_host = False if res: model = self.connections_model iter = model.get_iter(res[0]) host = model.get_value(iter, 0) connected_host = host.connected self.xml.get_widget("new_database").set_sensitive(connected_host) self.xml.get_widget("refresh_host").set_sensitive(connected_host) menu = self.xml.get_widget("connection_menu") elif len(res[0]) == 2: menu = self.xml.get_widget("database_popup") elif len(res[0]) == 3: menu = self.xml.get_widget("table_popup") else: print "no popup at path depth %d\n" % res[0].size() if menu: menu.popup(None, None, None, event.button, event.time) return True def on_connections_tv_cursor_changed(self, tv): path, column = tv.get_cursor() nb = self.xml.get_widget("main_notebook") if path is None: print "get_cursor() returned none. don't know which datebase is selected." return if len(path) == 3 and nb.get_current_page() == 3: print "update table view..." self.update_table_view(path) q = self.current_query if not q: return q.last_path = path if len(path) == 1: # host i = self.connections_model.get_iter(path) o = self.connections_model[i][0] q.set_current_host(o) elif len(path) >= 2: # database or below i = self.connections_model.get_iter(path[0:2]) o = self.connections_model[i][0] q.set_current_db(o) def on_nb_change_page(self, np, pointer, page): if page == 2: self.redraw_tables() return path, column = self.connections_tv.get_cursor() if not path: return if len(path) == 3 and page == 3: self.update_table_view(path) def update_table_view(self, path = None): if not path: path, column = self.connections_tv.get_cursor() if len(path) != 3: return iter = self.connections_model.get_iter(path) th = self.connections_model.get_value(iter, 0) table = self.xml.get_widget("table_properties") prop_count = len(th.props) if len(self.table_property_labels) != prop_count: for c in self.table_property_labels: table.remove(c) for c in self.table_property_entries: table.remove(c) self.table_property_labels = [] self.table_property_entries = [] table.resize(prop_count, 2) r = 0 for h, p in zip(th.db.status_headers, th.props): l = gtk.Label(h) l.set_alignment(0, 0.5) e = gtk.Entry() e.set_editable(False) if p is None: p = "" e.set_text(p) table.attach(l, 0, 1, r, r + 1, gtk.FILL, 0) table.attach(e, 1, 2, r, r + 1, gtk.EXPAND|gtk.FILL|gtk.SHRINK, 0) l.show() e.show() self.table_property_labels.append(l) self.table_property_entries.append(e) r += 1 else: r = 0 for h, p in zip(th.db.status_headers, th.props): l = self.table_property_labels[r] e = self.table_property_entries[r] l.set_label(h) if p is None: p = "" e.set_text(p) r += 1 tv = self.xml.get_widget("table_textview") tv.get_buffer().set_text(th.get_create_table()) t = self.table_description for c in t.get_children(): self.table_description.remove(c) self.table_description.resize(len(th.describe_headers), len(th.fields) + 1) c = 0 for h in th.describe_headers: l = gtk.Label(h) t.attach(l, c, c + 1, 0, 1, gtk.FILL, 0) l.show() c += 1 r = 1 for fn in th.field_order: v = th.fields[fn] for c in range(len(th.describe_headers)): s = v[c] if s is None: s = "" l = gtk.Label(s) t.attach(l, c, c + 1, r, r + 1, gtk.FILL, 0) l.set_alignment(0, 0.5) l.set_selectable(True) l.show() r += 1 self.xml.get_widget("vbox14").check_resize() self.tables_count = 0 self.redraw_tables() def on_connections_row_activated(self, tv, path, col): depth = len(path) iter = self.connections_model.get_iter(path) o = self.connections_model.get_value(iter, 0) nb = self.xml.get_widget("main_notebook") if depth == 1: # host self.current_host = host = o if host.connected: self.current_host = None host.close() else: host.connect() if not host.connected: return self.refresh_processlist() nb.set_current_page(1) self.redraw_host(host, iter, True) if self.current_query: self.current_query.set_current_host(self.current_host) elif depth == 2: # database self.current_host = o.host new_tables = o.refresh() self.redraw_db(o, iter, new_tables, True) self.redraw_tables() o.host.select_database(o) if self.current_query: self.current_query.set_current_db(o) # self.connections_tv.expand_row(path, False) # todo update_query_db() elif depth == 3: # table self.current_host = host = o.db.host host.select_database(o.db) table = o if self.current_query: self.current_query.set_current_db(table.db) if not table.fields or (time.time() - table.last_field_read) > self.config["autorefresh_interval_table"]: table.refresh() self.redraw_table(o, iter) if self.first_template: nb.set_current_page(4) self.on_template(None, self.first_template) elif nb.get_current_page() < 3: nb.set_current_page(3) #self.connections_tv.expand_row(path, False) # todo update_query_db(); # todo if(!doubleclick) update_table(e, i) else: print "No Handler for tree-depth", depth return def on_mainwindow_key_release_event(self, window, event): #print "state: %d, keyval: 0x%04x, text: '%s'" % (event.state, event.keyval, event.string) #~ RefPtr xml = queries[current_query].xml; #~ xml_get_decl_widget_from(xml, local_search_button, Gtk::ToolButton); #~ if(event->keyval == GDK_Tab) { #~ return do_auto_completion(); #~ } else #if event.keyval == keysyms.F9 or (event.state == 4 and event.keyval == keysyms.Return): # self.on_execute_query_clicked(None) # return True #~ } else if(event->keyval == GDK_F6) { #~ query_notebook->set_current_page((current_query + 1) % queries.size()); #~ return true; #~ } else if(event->state & 4 && event->keyval == GDK_t) { #~ new_query_tab(); #~ return true; #~ } else if(event->state & 4 && event->keyval == GDK_w) { #~ close_query_tab(); #~ return true; #~ } else if(event->state & 4 && event->keyval == GDK_s) { #~ on_save_query(); #~ return true; #~ } else if(event->state & 4 && event->keyval == GDK_o) { #~ on_load_query(); #~ return true; #~ } else if(event->state & 4 && event->keyval == GDK_p) { #~ on_pretty_format(); #~ return true; if event.keyval == keysyms.F3: self.on_local_search_button_clicked(None, True) return True #~ } else if(event->state & 4 && event->keyval == GDK_u) { #~ xml_get_decl_widget_from(xml, query_text, Gtk::TextView); #~ TextBuffer::iterator start, end; #~ query_text->get_buffer()->get_selection_bounds(start, end); #~ string text = query_text->get_buffer()->get_text(start, end); #~ text = to_lower(text); #~ query_text->get_buffer()->erase_selection(); #~ query_text->get_buffer()->get_selection_bounds(start, end); #~ query_text->get_buffer()->insert(start, text); #~ query_text->get_buffer()->get_selection_bounds(start, end); #~ start = end; #~ end.backward_chars(text.size()); #~ query_text->get_buffer()->select_range(start, end); #~ return true; #~ } else if(event->state & 4 && event->keyval == GDK_U) { #~ xml_get_decl_widget_from(xml, query_text, Gtk::TextView); #~ TextBuffer::iterator start, end; #~ query_text->get_buffer()->get_selection_bounds(start, end); #~ string text = to_upper(query_text->get_buffer()->get_text(start, end)); #~ query_text->get_buffer()->erase_selection(); #~ query_text->get_buffer()->get_selection_bounds(start, end); #~ query_text->get_buffer()->insert(start, text); #~ query_text->get_buffer()->get_selection_bounds(start, end); #~ start = end; #~ end.backward_chars(text.size()); #~ query_text->get_buffer()->select_range(start, end); #~ return true; #~ } else if(event->state & 4 && event->keyval >= GDK_0 && event->keyval <= GDK_9) { #~ int page = event->keyval - GDK_0; #~ if(page == 0) page = 10; #~ page--; #~ if(page < query_notebook->get_n_pages()) #~ query_notebook->set_current_page(page); #~ // on_parse_query("vos2sql"); #~ } #~ return false; def on_query_view_key_press_event(self, tv, event): q = self.current_query path, column = q.treeview.get_cursor() if event.keyval == keysyms.F2: q.treeview.set_cursor(path, column, True) return True iter = q.model.get_iter(path) if event.keyval == keysyms.Down and not q.model.iter_next(iter): if q.append_iter and not self.on_apply_record_tool_clicked(None): return True self.on_add_record_tool_clicked(None) return True def on_query_view_button_release_event(self, tv, event): if not event.button == 3: return False res = tv.get_path_at_pos(int(event.x), int(event.y)); menu = self.xml.get_widget("result_popup") if res: sensitive = True else: sensitive = False for c in menu.get_children(): for s in ["edit", "set ", "delete"]: if c.name.find(s) != -1: c.set_sensitive(sensitive and self.current_query.editable) break else: if c.name not in ["add_record"]: c.set_sensitive(sensitive) else: c.set_sensitive(self.current_query.add_record.get_property("sensitive")) #menu.popup(None, None, None, event.button, event.time) menu.popup(None, None, None, 0, event.time) # strange! return True def get_current_table(self): path, column = self.connections_tv.get_cursor() iter = self.connections_model.get_iter(path) return path, column, iter, self.connections_model.get_value(iter, 0) def on_table_popup(self, item): path, column, iter, table = self.get_current_table() what = item.name if what == "refresh_table": table.refresh() self.redraw_table(table, iter) self.update_table_view() elif what == "truncate_table": if not self.confirm("truncate table", "do you really want to truncate the %s table in database %s on %s?" % (table.name, table.db.name, table.db.host.name)): return if table.db.query("truncate `%s`" % (table.name)): table.refresh() self.redraw_table(table, iter) self.update_table_view() elif what == "drop_table": if not self.confirm("drop table", "do you really want to DROP the %s table in database %s on %s?" % (table.name, table.db.name, table.db.host.name)): return db = table.db if db.query("drop table `%s`" % (table.name)): new_tables = db.refresh() self.redraw_db(db, self.get_db_iter(db), new_tables) self.redraw_tables() def on_db_popup(self, item): path, column = self.connections_tv.get_cursor() iter = self.connections_model.get_iter(path) what = item.name db = self.connections_model.get_value(iter, 0) if what == "refresh_database": new_tables = db.refresh() self.redraw_db(db, iter, new_tables) self.redraw_tables() elif what == "drop_database": if not self.confirm("drop database", "do you really want to drop the %s database on %s?" % (db.name, db.host.name)): return host = db.host if host.query("drop database`%s`" % (db.name)): host.refresh() self.redraw_host(host, self.get_host_iter(host)) elif what == "new_table": name = self.input("new table", "please enter the name of the new table:") if not name: return if db.query("create table `%s` (`%s_id` int primary key auto_increment)" % (name, name)): new_tables = db.refresh() self.redraw_db(db, self.get_db_iter(db), new_tables) self.redraw_tables() def on_host_popup(self, item): path, column = self.connections_tv.get_cursor() if path: iter = self.connections_model.get_iter(path) host = self.connections_model.get_value(iter, 0) else: iter = None host = None what = item.name if "connection_window" not in self.__dict__: self.connection_window = self.xml.get_widget("connection_window") self.xml.get_widget("cw_apply_button").connect("clicked", self.on_cw_apply) self.xml.get_widget("cw_test_button").connect("clicked", self.on_cw_test) self.xml.get_widget("cw_abort_button").connect("clicked", lambda *a: self.connection_window.hide()) self.cw_props = ["name", "host", "port", "user", "password", "database"] if what == "refresh_host": host.refresh() self.redraw_host(host, iter) elif what == "new_database": name = self.input("new database", "please enter the name of the new database:") if not name: return if host.query("create database `%s`" % name): host.refresh() self.redraw_host(host, iter) elif what == "modify_connection": for n in self.cw_props: self.xml.get_widget("cw_%s" % n).set_text(host.__dict__[n]) self.cw_mode = "edit" self.cw_host = host self.connection_window.show() elif what == "delete_connection": if not self.confirm("delete host", "do you really want to drop the host %s?" % (host.name)): return host.close() self.connections_model.remove(iter) if self.current_host == host: self.current_host = None del self.config["connection_%s" % host.name] host = None self.save_config() elif what == "new_connection": for n in self.cw_props: self.xml.get_widget("cw_%s" % n).set_text("") self.cw_mode = "new" self.connection_window.show() def on_cw_apply(self, *args): if self.cw_mode == "new": data = [] for n in self.cw_props: data.append(self.xml.get_widget("cw_%s" % n).get_text()) if not data[0]: self.connection_window.hide() return self.add_mysql_host(*data) else: for n in self.cw_props: self.cw_host.__dict__[n] = self.xml.get_widget("cw_%s" % n).get_text() self.connection_window.hide() self.save_config() def on_cw_test(self, *args): import _mysql; data = { "connect_timeout": 6 } widget_map = { "password": "passwd" } for n in ["host", "user", "password", "port:int"]: if ":" in n: n, typename = n.split(":", 1) data[widget_map.get(n, n)] = eval("%s(%r)" % (typename, self.xml.get_widget("cw_%s" % n).get_text())) else: data[widget_map.get(n, n)] = self.xml.get_widget("cw_%s" % n).get_text() try: handle = _mysql.connect(**data) except: self.show_message( "test connection", "could not connect to host %s with user %s and password %s:\n%s" % ( data["host"], data["user"], data["passwd"], sys.exc_value ), window=self.connection_window ) return self.show_message( "test connection", "successfully connected to host %s with user %s!" % ( data["host"], data["user"] ), window=self.connection_window) handle.close() def get_db_iter(self, db): return self.get_connections_object_at_depth(db, 1) def get_host_iter(self, host): return self.get_connections_object_at_depth(host, 0) def get_connections_object_at_depth(self, obj, depth): d = 0 model = self.connections_model iter = model.get_iter_first() while iter: if d == depth and model.get_value(iter, 0) == obj: return iter if d < depth and model.iter_has_child(iter): iter = model.iter_children(iter) d += 1 continue new_iter = model.iter_next(iter) if not new_iter: iter = model.iter_parent(iter) d -= 1 iter = model.iter_next(iter) else: iter = new_iter return None def on_execution_timeout(self, button): value = button.get_value() if value < 0.1: self.execution_timer_running = False return False if self.on_execute_query_clicked() != True: # stop on error button.set_value(0) value = 0 if value != self.execution_timer_interval: self.execution_timer_running = False self.on_reexecution_spin_changed(button) return False return True def on_reexecution_spin_changed(self, button): value = button.get_value() if self.execution_timer_running: return self.execution_timer_running = True self.execution_timer_interval = value gobject.timeout_add(int(value * 1000), self.on_execution_timeout, button) def on_blob_update_clicked(self, button): q = self.current_query path, column = q.treeview.get_cursor() iter = q.model.get_iter(path) b = self.blob_tv.get_buffer() new_value = b.get_text(b.get_start_iter(), b.get_end_iter()) col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return crs = column.get_cell_renderers() return self.on_query_change_data(crs[0], path, new_value, col_num, force_update=self.blob_encoding != q.encoding) def on_messages_popup(self, item): if item.name == "clear_messages": self.msg_model.clear() def on_msg_tv_button_press_event(self, tv, event): if not event.button == 3: return False res = tv.get_path_at_pos(int(event.x), int(event.y)); self.xml.get_widget("messages_popup").popup(None, None, None, event.button, event.time); return True def on_query_popup(self, item): q = self.current_query path, column = q.treeview.get_cursor() iter = q.model.get_iter(path) if item.name == "copy_field_value": col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return value = q.model.get_value(iter, col_num) self.clipboard.set_text(value) self.pri_clipboard.set_text(value) elif item.name == "copy_record_as_csv": col_max = q.model.get_n_columns() value = "" for col_num in range(col_max): if value: value += self.config["copy_record_as_csv_delim"] v = q.model.get_value(iter, col_num) if not v is None: value += v self.clipboard.set_text(value) self.pri_clipboard.set_text(value) elif item.name == "copy_column_as_csv": col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return value = "" iter = q.model.get_iter_first() while iter: if value: value += self.config["copy_record_as_csv_delim"] v = q.model.get_value(iter, col_num) if not v is None: value += v iter = q.model.iter_next(iter) self.clipboard.set_text(value) self.pri_clipboard.set_text(value) elif item.name == "copy_column_names": value = "" for col in q.treeview.get_columns(): if value: value += self.config["copy_record_as_csv_delim"] value += col.get_title().replace("__", "_") self.clipboard.set_text(value) self.pri_clipboard.set_text(value) elif item.name == "set_value_null": col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return table, where, field, value, row_iter = self.get_unique_where(q.last_source, path, col_num) update_query = "update `%s` set `%s`=NULL where %s limit 1" % (table, field, where) if self.current_host.query(update_query, encoding=q.encoding): q.model.set_value(row_iter, col_num, None) elif item.name == "set_value_now": col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return table, where, field, value, row_iter = self.get_unique_where(q.last_source, path, col_num) update_query = "update `%s` set `%s`=now() where %s limit 1" % (table, field, where) if not self.current_host.query(update_query, encoding=q.encoding): return self.current_host.query("select `%s` from `%s` where %s limit 1" % (field, table, where)) result = self.current_host.handle.store_result().fetch_row(0) if len(result) < 1: print "error: can't find modfied row!?" return q.model.set_value(row_iter, col_num, result[0][0]) elif item.name == "set_value_unix_timestamp": col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return table, where, field, value, row_iter = self.get_unique_where(q.last_source, path, col_num) update_query = "update `%s` set `%s`=unix_timestamp(now()) where %s limit 1" % (table, field, where) if not self.current_host.query(update_query, encoding=q.encoding): return self.current_host.query("select `%s` from `%s` where %s limit 1" % (field, table, where)) result = self.current_host.handle.store_result().fetch_row(0) if len(result) < 1: print "error: can't find modfied row!?" return q.model.set_value(row_iter, col_num, result[0][0]) elif item.name == "set_value_as_password": col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return table, where, field, value, row_iter = self.get_unique_where(q.last_source, path, col_num) update_query = "update `%s` set `%s`=password('%s') where %s limit 1" % (table, field, self.current_host.escape(value), where) if not self.current_host.query(update_query, encoding=q.encoding): return self.current_host.query("select `%s` from `%s` where %s limit 1" % (field, table, where)) result = self.current_host.handle.store_result().fetch_row(0) if len(result) < 1: print "error: can't find modfied row!?" return q.model.set_value(row_iter, col_num, result[0][0]) elif item.name == "set_value_to_sha": col_max = q.model.get_n_columns() for col_num in range(col_max): if column == q.treeview.get_column(col_num): break else: print "column not found!" return table, where, field, value, row_iter = self.get_unique_where(q.last_source, path, col_num) update_query = "update `%s` set `%s`=sha1('%s') where %s limit 1" % (table, field, self.current_host.escape(value), where) if not self.current_host.query(update_query, encoding=q.encoding): return self.current_host.query("select `%s` from `%s` where %s limit 1" % (field, table, where)) result = self.current_host.handle.store_result().fetch_row(0) if len(result) < 1: print "error: can't find modfied row!?" return q.model.set_value(row_iter, col_num, result[0][0]) def on_template(self, button, t): current_table = self.get_selected_table() current_fc_table = current_table; if t.find("$table$") != -1: if not current_table: show_message("info", "no table selected!\nyou can't execute a template with $table$ in it, if you have no table selected!") return t = t.replace("$table$", self.escape_fieldname(current_table.name)) pos = t.find("$primary_key$") if pos != -1: if not current_table: show_message("info", "no table selected!\nyou can't execute a template with $primary_key$ in it, if you have no table selected!") return if not current_table.fields: show_message("info", "sorry, can't execute this template, because table '%s' has no fields!" % current_table.name) return # is the next token desc or asc? result = re.search("(?i)[ \t\r\n]*(de|a)sc", t[pos:]) order_dir = "" if result: o = result.group(1).lower() if o == "a": order_dir = "asc" else: order_dir = "desc" replace = "" while 1: primary_key = "" for name in current_table.field_order: props = current_table.fields[name] if props[3] != "PRI": continue if primary_key: primary_key += " " + order_dir + ", " primary_key += "`%s`" % self.escape_fieldname(name) if primary_key: replace = primary_key break key = "" for name in current_table.field_order: props = current_table.fields[name] if props[3] != "UNI": continue if key: key += " " + order_dir + ", " key += "`%s`" % self.escape_fieldname(name) if key: replace = key break replace = "`%s`" % self.escape_fieldname(current_table.field_order[0]) break t = t.replace("$primary_key$", replace) if t.find("$field_conditions$") != -1: if not self.field_conditions_initialized: self.field_conditions_initialized = True self.fc_count = 4 self.fc_window = self.xml.get_widget("field_conditions") table = self.xml.get_widget("fc_table") table.resize(1 + self.fc_count, 4) self.fc_entry = [] self.fc_combobox = [] self.fc_op_combobox = [] self.fc_logic_combobox = [] for i in range(self.fc_count): self.fc_entry.append(gtk.Entry()) self.fc_entry[i].connect("activate", lambda *e: self.fc_window.response(gtk.RESPONSE_OK)) self.fc_combobox.append(gtk.combo_box_new_text()) self.fc_op_combobox.append(gtk.combo_box_new_text()) self.fc_op_combobox[i].append_text("=") self.fc_op_combobox[i].append_text("<") self.fc_op_combobox[i].append_text(">") self.fc_op_combobox[i].append_text("!=") self.fc_op_combobox[i].append_text("LIKE") self.fc_op_combobox[i].append_text("NOT LIKE") self.fc_op_combobox[i].append_text("ISNULL") self.fc_op_combobox[i].append_text("NOT ISNULL") if i: self.fc_logic_combobox.append(gtk.combo_box_new_text()) self.fc_logic_combobox[i - 1].append_text("disabled") self.fc_logic_combobox[i - 1].append_text("AND") self.fc_logic_combobox[i - 1].append_text("OR") table.attach(self.fc_logic_combobox[i - 1], 0, 1, i + 1, i + 2) self.fc_logic_combobox[i - 1].show() table.attach(self.fc_combobox[i], 1, 2, i + 1, i + 2); table.attach(self.fc_op_combobox[i], 2, 3, i + 1, i + 2) table.attach(self.fc_entry[i], 3, 4, i + 1, i + 2); self.fc_combobox[i].show(); self.fc_op_combobox[i].show(); self.fc_entry[i].show(); if not current_table: show_message("info", "no table selected!\nyou can't execute a template with $field_conditions$ in it, if you have no table selected!") return last_field = [] for i in range(self.fc_count): last_field.append(self.fc_combobox[i].get_active_text()) self.fc_combobox[i].get_model().clear() if i: self.fc_logic_combobox[i - 1].set_active(0) fc = 0 for field_name in current_table.field_order: for k in range(self.fc_count): self.fc_combobox[k].append_text(field_name) if last_field[k] == field_name: self.fc_combobox[k].set_active(fc) fc += 1 if not self.fc_op_combobox[0].get_active_text(): self.fc_op_combobox[0].set_active(0) if not self.fc_combobox[0].get_active_text(): self.fc_combobox[0].set_active(0) answer = self.fc_window.run() self.fc_window.hide() if answer != gtk.RESPONSE_OK: return def field_operator_value(field, op, value): if op == "ISNULL": return "isnull(`%s`)" % field if op == "NOT ISNULL": return "not isnull(`%s`)" % field eval_kw = "eval: " if value.startswith(eval_kw): return "`%s` %s %s" % (field, op, value[len(eval_kw):]) return "`%s` %s '%s'" % (field, op, self.current_host.escape(value)) conditions = "%s" % ( field_operator_value( self.fc_combobox[0].get_active_text(), self.fc_op_combobox[0].get_active_text(), self.fc_entry[0].get_text() ) ) for i in range(1, self.fc_count): if self.fc_logic_combobox[i - 1].get_active_text() == "disabled" or self.fc_combobox[i].get_active_text() == "" or self.fc_op_combobox[i].get_active_text() == "": continue conditions += " %s %s" % ( self.fc_logic_combobox[i - 1].get_active_text(), field_operator_value( self.fc_combobox[i].get_active_text(), self.fc_op_combobox[i].get_active_text(), self.fc_entry[i].get_text() ) ) t = t.replace("$field_conditions$", conditions) try: new_order = self.stored_orders[self.current_host.current_db.name][current_table.name] print "found stored order", new_order query = t try: r = self.query_order_re except: r = self.query_order_re = re.compile(re_src_query_order) match = re.search(r, query) if match: before, order, after = match.groups() order = "" addition = "" else: match = re.search(re_src_after_order, query) if not match: before = query after = "" else: before = query[0:match.start()] after = match.group() addition = "\norder by\n\t" order = "" for col, o in new_order: if order: order += ",\n\t" order += col if not o: order += " desc" if order: new_query = ''.join([before, addition, order, after]) else: new_query = re.sub("(?i)order[ \r\n\t]+by[ \r\n\t]+", "", before + after) t = new_query except: pass self.on_execute_query_clicked(None, t) def on_processlist_refresh_timeout(self, button): value = button.get_value() if value < 0.1: self.processlist_timer_running = False return False self.refresh_processlist() if value != self.processlist_timer_interval: self.processlist_timer_running = False self.on_processlist_refresh_value_change(button) return False return True def on_processlist_button_release(self, tv, event): if not event.button == 3: return False res = tv.get_path_at_pos(int(event.x), int(event.y)); if not res: return False self.xml.get_widget("processlist_popup").popup(None, None, None, event.button, event.time) def show_message(self, title, message, window=None): if window is None: window = self.mainwindow dialog = gtk.MessageDialog(window, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, message) dialog.label.set_property("use-markup", True) dialog.set_title(title) dialog.run() dialog.hide() def confirm(self, title, message, window=None): if window is None: window = self.mainwindow dialog = gtk.MessageDialog(window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, message) dialog.label.set_property("use-markup", True) dialog.set_title(title) answer = dialog.run() dialog.hide() return answer == gtk.RESPONSE_YES def input(self, title, message, default="", window=None): if window is None: window = self.mainwindow dialog = gtk.Dialog(title, window, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)) label = gtk.Label(message) label.set_property("use-markup", True) dialog.vbox.pack_start(label, True, True, 2) entry = gtk.Entry() entry.connect("activate", lambda *a: dialog.response(gtk.RESPONSE_ACCEPT)) dialog.vbox.pack_start(entry, False, True, 2) label.show() entry.show() entry.set_text(default) answer = dialog.run() dialog.hide() if answer != gtk.RESPONSE_ACCEPT: return None return entry.get_text() def render_connections_pixbuf(self, column, cell, model, iter): d = model.iter_depth(iter) o = model.get_value(iter, 0) if d == 0: if o.connected: cell.set_property("pixbuf", self.icons["host"]) else: cell.set_property("pixbuf", self.icons["offline_host"]) elif d == 1: cell.set_property("pixbuf", self.icons["db"]) elif d == 2: cell.set_property("pixbuf", self.icons["table"]) elif d == 3: cell.set_property("pixbuf", self.icons["field"]) else: print "unknown depth", d," for render_connections_pixbuf with object", o def on_new_file_activate(self, *args): print "new file", args def render_connections_text(self, column, cell, model, iter): d = model.iter_depth(iter) o = model.get_value(iter, 0) if d == 0: if o.connected: cell.set_property("text", o.name) else: cell.set_property("text", "(%s)" % o.name) elif d == 3: #fields are only strings cell.set_property("text", "%s %s" % (o[0], o[1])) else: # everything else has a name cell.set_property("text", o.name) #print "unknown depth", d," for render_connections_pixbuf with object", o def render_mysql_string(self, column, cell, model, iter, id): o = model.get_value(iter, id) if not o is None: cell.set_property("background", None) if len(o) < 256: cell.set_property("text", o) cell.set_property("editable", True) else: cell.set_property("text", o[0:256] + "...") cell.set_property("editable", False) else: cell.set_property("background", self.config["null_color"]) cell.set_property("text", "") cell.set_property("editable", True) def config_get_bool(self, name): value = self.config[name].lower() if value == "yes": return True if value == "y": return True if value == "1": return True if value == "true": return True if value == "t": return True return False def save_config(self): if not os.path.exists(self.config_path): print "try to create config path %r" % self.config_path try: os.mkdir(self.config_path) except: self.show_message("save config file", "could create config directory %r: %s" % (self.config_path, sys.exc_value)) return filename = os.path.join(self.config_path, self.config_file) try: fp = file(filename, "w") except: self.show_message("save config file", "could not open %s for writing: %s" % (filename, sys.exc_value)) return keys = self.config.keys() keys.sort() for name in keys: if name.startswith("connection_"): continue value = self.config[name] fp.write("%s=%s\n" % (name, value)) iter = self.connections_model.get_iter_root() while iter: host = self.connections_model.get_value(iter, 0) fp.write("connection_%s=%s\n" % (host.name, host.get_connection_string())) iter = self.connections_model.iter_next(iter) fp.close() def on_reread_config_activate(self, item): self.load_config() def load_config(self, unpickled=False): filename = os.path.join(self.config_path, self.config_file) # todo get_charset(self.config["db_codeset"]); # printf("system charset: '%s'\n", self.config["db_codeset"].c_str()); # syntax_highlight_functions: grep -E -e "^[ \\t]+]*>[^\(<90-9]+\(" mysql_fun.html fun*.html | sed -r -e "s/^[^<]*]+>//" -e "s/\(.*$/,/" | tr "[:upper:]" "[:lower:]" | sort | uniq | xargs echo self.config = { "null_color": "#00eeaa", "autorefresh_interval_table": "300", "column_sort_use_newline": "true", "query_text_font": "Monospace 8", "query_text_wrap": "false", "query_result_font": "Monospace 8", "query_log_max_entry_length": "1024", "result_view_column_width_min": "70", "result_view_column_width_max": "300", "result_view_column_resizable": "false", "result_view_column_sort_timeout": "750", "syntax_highlight_keywords": "lock, unlock, tables, kill, truncate table, alter table, host, database, field, comment, show table status, show index, add index, drop index, add primary key, add unique, drop primary key, show create table, values, insert into, into, select, show databases, show tables, show processlist, show tables, from, where, order by, group by, limit, left, join, right, inner, after, alter, as, asc, before, begin, case, column, change column, commit, create table, default, delete, desc, describe, distinct, drop, table, first, grant, having, insert, interval, insert into, limit, null, order, primary key, primary, auto_increment, rollback, set, start, temporary, union, unique, update, create database, use, key, type, uniqe key, on, type, not, unsigned", "syntax_highlight_functions": "date_format, now, floor, rand, hour, if, minute, month, right, year, isnull", "syntax_highlight_functions": "abs, acos, adddate, addtime, aes_decrypt, aes_encrypt, ascii, asin, atan, benchmark, bin, bit_length, ceil, ceiling, char, character_length, char_length, charset, coercibility, collation, compress, concat, concat_ws, connection_id, conv, convert_tz, cos, cot, crypt, curdate, current_date, current_time, current_timestamp, current_user, curtime, database, date, date_add, datediff, date_format, date_sub, day, dayname, dayofmonth, dayofweek, dayofyear, decode, default, degrees, des_decrypt, des_encrypt, elt, encode, encrypt, exp, export_set, extract, field, find_in_set, floor, format, found_rows, from_days, from_unixtime, get_format, get_lock, hex, hour, if, ifnull, inet_aton, inet_ntoa, insert, instr, is_free_lock, is_used_lock, last_day, last_insert_id, lcase, left, length, ln, load_file, localtime, localtimestamp, locate, log, lower, lpad, ltrim, makedate, make_set, maketime, master_pos_wait, microsecond, mid, minute, mod, month, monthname, mysql_insert_id, now, nullif, oct, octet_length, old_password, ord, order by rand, password, period_add, period_diff, pi, position, pow, power, quarter, quote, radians, rand, release_lock, repeat, replace, reverse, right, round, row_count, rpad, rtrim, schema, second, sec_to_time, session_user, sha, sign, sin, sleep, soundex, space, sqrt, str_to_date, subdate, substr, substring, substring_index, subtime, sysdate, system_user, tan, time, timediff, time_format, timestamp, timestampadd, timestampdiff, time_to_sec, to_days, trim, truncate, ucase, uncompress, uncompressed_length, unhex, unix_timestamp, upper, user, utc_date, utc_time, utc_timestamp, uuid, version, week, weekday, weekofyear, year, yearweek", "syntax_highlight_datatypes": "binary, bit, blob, boolean, char, character, dec, decimal, double, float, int, integer, numeric, smallint, timestamp, varchar, datetime, text, mediumint, bigint, tinyint, date", "syntax_highlight_operators": "not, and, or, like, \\<, \\>", "syntax_highlight_fg_keyword": "#00007F", "syntax_highlight_fg_function": "darkblue", "syntax_highlight_fg_datatype": "#AA00AA", "syntax_highlight_fg_operator": "#0000aa", "syntax_highlight_fg_double-quoted-string": "#7F007F", "syntax_highlight_fg_single-quoted-string": "#9F007F", "syntax_highlight_fg_backtick-quoted-string": "#BF007F", "syntax_highlight_fg_number": "#007F7F", "syntax_highlight_fg_comment": "#007F00", "syntax_highlight_fg_error": "red", "pretty_print_uppercase_keywords": "false", "pretty_print_uppercase_operators": "false", "template1_last 150 records": "select * from $table$ order by $primary_key$ desc limit 150", "template2_500 records in fs-order": "select * from $table$ limit 500", "template3_quick filter 500": "select * from $table$ where $field_conditions$ limit 500", "copy_record_as_csv_delim": ",", "save_result_as_csv_delim": ",", "save_result_as_csv_line_delim": "\\n", "ping_connection_interval": "300", "ask_execute_query_from_disk_min_size": "1024000", "connect_timeout": "7", "db_encoding": "latin1", "theme": os.path.join(emma_share_path, "theme"), "supported_db_encodings": "latin1 (iso8859-1, cp819); " "latin2 (iso8859-2); " "iso8859_15 (iso8859-15); " "utf8;" "utf7;" "utf16; " "ascii (646);" "cp437 (IBM437);" "cp500 (EBCDIC-CP-BE); " "cp850 (IBM850); " "cp1140 (ibm1140); " "cp1252 (windows-1252); " "mac_latin2; mac_roman" } first = False if not os.path.exists(filename): print "no config file %r found. using defaults." % filename self.config["connection_localhost"] = "localhost,root,," else: try: fp = file(filename, "r") line_no = 0 for line in fp: line_no += 1 line.lstrip(" \t\r\n") if not line: continue if line[0] == '#': continue varval = line.split("=", 1) name, value = map(lambda a: a.strip("\r\n \t"), varval) value = varval[1].strip("\r\n \t") self.config[name] = value #setattr(self, "cfg_%s" % name, value) fp.close() except: print "could not load config file %r: %s" % (filename, sys.exc_value) self.config["connection_localhost"] = "localhost,root,," # split supported encodings in list self.supported_db_encodings = map(lambda e: e.strip(), self.config["supported_db_encodings"].split(";")) menu = self.xml.get_widget("query_encoding_menu") for child in menu.get_children(): menu.remove(child) self.codings = {} for index, coding in enumerate(self.supported_db_encodings): try: c, description = coding.split(" ", 1) except: c = coding description = "" self.codings[c] = (index, description) item = gtk.MenuItem(coding, False) item.connect("activate", self.on_query_encoding_changed, (c, index)) menu.append(item) item.show() try: coding = self.config["db_encoding"] index = self.codings[coding][0] except: index = 0 coding, description = self.supported_db_encodings[index].split(" ", 1) self.config["db_encoding"] = coding # stored orders self.stored_orders = {} for name in self.config.keys(): if not name.startswith("stored_order_db_"): continue words = name.split("_") db = words[3] table = words[5] if not db in self.stored_orders: self.stored_orders[db] = {} self.stored_orders[db][table] = eval(self.config[name]) self.first_template = None keys = self.config.keys() keys.sort() toolbar = self.xml.get_widget("query_toolbar") for child in toolbar.get_children(): if not child.name.startswith("template_"): continue toolbar.remove(child) template_count = 0 for name in keys: value = self.config[name] if not unpickled: prefix = "connection_" if name.startswith(prefix): v = value.split(",") port = "" p = v[0].rsplit(":", 1) if len(p) == 2: port = p[1] v[0] = p[0] self.add_mysql_host(name[len(prefix):], v[0], port, v[1], v[2], v[3]) prefix = "template"; if name.startswith(prefix): value = value.replace("`$primary_key$`", "$primary_key$") value = value.replace("`$table$`", "$table$") value = value.replace("`$field_conditions$`", "$field_conditions$") self.config[name] = value if not self.first_template: self.first_template = value p = name.split("_", 1) template_count += 1 button = gtk.ToolButton(gtk.STOCK_EXECUTE) button.set_name("template_%d" % template_count) button.set_tooltip(self.tooltips, "%s\n%s" % (p[1], value)) button.connect("clicked", self.on_template, value) toolbar.insert(button, -1) button.show() if not unpickled: return for h in self.hosts: h.__init__(self.add_sql_log, self.add_msg_log) iter = self.connections_model.append(None, [h]) h.set_update_ui(self.redraw_host, iter) # call before init! self.redraw_host(h, iter) self.current_host = h def on_reload_theme_activate(self, *args): gtk.rc_reparse_all() def on_query_bottom_eventbox_button_press_event(self, ebox, event): self.xml.get_widget("query_encoding_menu").popup(None, None, None, event.button, event.time); def on_query_db_eventbox_button_press_event(self, ebox, event): q = self.current_query host = q.current_host db = q.current_db if q.last_path is not None: try: self.connections_model.get_iter(q.last_path) self.connections_tv.set_cursor(q.last_path) return except: # path was not valid pass i = self.connections_model.get_iter_root() while i and self.connections_model.iter_is_valid(i): if self.connections_model[i][0] == host: break i = self.connections_model.iter_next(i) else: print "host not found in connections list!" q.current_host = q.current_db = None q.update_db_label() return host_path = self.connections_model.get_path(i) self.connections_tv.scroll_to_cell(host_path, column=None, use_align=True, row_align=0.0, col_align=0.0) if db is None: self.connections_tv.set_cursor(host_path) return k = self.connections_model.iter_children(i) while k and self.connections_model.iter_is_valid(k): if self.connections_model[k][0] == db: break k = self.connections_model.iter_next(k) else: print "database not found in connections list!" q.current_db = None q.update_db_label() self.connections_tv.set_cursor(host_path) return path = self.connections_model.get_path(k) #self.connections_tv.scroll_to_cell(path, column=None, use_align=True, row_align=0.125, col_align=0.0) self.connections_tv.set_cursor(path) return def on_query_encoding_changed(self, menuitem, data): self.current_query.set_query_encoding(data[0]) def add_mysql_host(self, name, hostname, port, user, password, database): host = mysql_host(self.add_sql_log, self.add_msg_log, name, hostname, port, user, password, database, self.config["connect_timeout"]) iter = self.connections_model.append(None, [host]) host.set_update_ui(self.redraw_host, iter) def add_sql_log(self, log): olog = log max_len = int(self.config["query_log_max_entry_length"]) if len(log) > max_len: log = log[0:max_len] + "\n/* query with length of %d bytes truncated. */" % len(log); # query = db_to_utf8(log); # query = syntax_highlight_markup(query); # query = rxx.replace(query, "[\r\n\t ]+", " ", Regexx::global); if not log: return now = time.time() now = int((now - int(now)) * 100) timestamp = time.strftime("%Y-%m-%d %H:%M:%S") if now: timestamp = "%s.%02d" % (timestamp, now) log = log.replace("<", "<") log = log.replace(">", ">") iter = self.sql_log_model.append((timestamp, log, olog)) self.sql_log_tv.scroll_to_cell(self.sql_log_model.get_path(iter)) #self.xml.get_widget("message_notebook").set_current_page(0) self.process_events() def process_events(self): while gtk.events_pending(): gtk.main_iteration(False) def add_msg_log(self, log): if not log: return log.replace( "You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near", "syntax error at " ) now = time.time() now = int((now - int(now)) * 100) timestamp = time.strftime("%Y-%m-%d %H:%M:%S") if now: timestamp = "%s.%02d" % (timestamp, now) iter = self.msg_model.append((timestamp, log)) self.msg_tv.scroll_to_cell(self.msg_model.get_path(iter)) self.xml.get_widget("message_notebook").set_current_page(1) def get_selected_table(self): path, column = self.connections_tv.get_cursor() depth = len(path) iter = self.connections_model.get_iter(path) if depth == 3: return self.connections_model.get_value(iter, 0) return None def load_icons(self): self.icons = {} for icon in ["offline_host", "host", "db", "table", "field", "emma"]: filename = os.path.join(icons_path, icon + ".png") try: self.icons[icon] = gtk.gdk.pixbuf_new_from_file(filename) except: print "could not load", filename self.mainwindow.set_icon(self.icons["emma"]) def refresh_processlist(self, *args): if not self.current_host: return self.current_host.refresh_processlist() self.redraw_processlist(self.current_host) def redraw_processlist(self, host): if not host.processlist: return fields, rows = host.processlist if self.processlist_model: self.processlist_model.clear() if self.current_processlist_host != self.current_host: self.current_processlist_host = self.current_host self.xml.get_widget("version_label").set_text(" server version: %s" % self.current_host.handle.get_server_info()); for col in self.processlist_tv.get_columns(): self.processlist_tv.remove_column(col) columns = [gobject.TYPE_STRING] * len(fields) self.processlist_model = gtk.ListStore(*columns); self.processlist_tv.set_model(self.processlist_model); self.processlist_tv.set_headers_clickable(True); id = 0 for field in fields: title = field[0].replace("_", "__") self.processlist_tv.insert_column_with_data_func(-1, title, gtk.CellRendererText(), self.render_mysql_string, id) id += 1 for proc in rows: self.processlist_model.append(proc) return def redraw_tables(self): if not self.current_host: return db = self.current_host.current_db if not db: return if not "tables_tv" in self.__dict__: self.tables_tv = self.xml.get_widget("tables_treeview") self.tables_model = None self.tables_db = None if not self.tables_db == db: self.tables_db = db if self.tables_model: self.tables_model.clear() for col in self.tables_tv.get_columns(): self.tables_tv.remove_column(col) fields = db.status_headers columns = [gobject.TYPE_STRING] * len(fields) self.tables_model = gtk.ListStore(*columns); self.tables_tv.set_model(self.tables_model); id = 0 for field in fields: title = field.replace("_", "__") self.tables_tv.insert_column_with_data_func(-1, title, gtk.CellRendererText(), self.render_mysql_string, id) id += 1 self.tables_count = 0 keys = db.tables.keys() if self.tables_count == len(keys): return self.tables_count = len(keys) keys.sort() self.tables_model.clear() for name in keys: table = db.tables[name] self.tables_model.append(table.props) def redraw_entry(self, obj, iter): print "do redraw", obj, iter def redraw_host(self, host, iter, expand = False): #print "redraw host", host.name if host.expanded: expand = True # first remove exiting children of that node i = self.connections_model.iter_children(iter) while i and self.connections_model.iter_is_valid(i): self.connections_model.remove(i) # now add every database keys = host.databases.keys() keys.sort() for name in keys: db = host.databases[name] i = self.connections_model.append(iter, (db,)) if expand: self.connections_tv.expand_row(self.connections_model.get_path(iter), False) expand = False self.redraw_db(db, i) def redraw_db(self, db, iter, new_tables = None, force_expand = False): #print "redraw db", db.name if not iter: print "Error: invalid db-iterator:", iter return path = self.connections_model.get_path(iter) if db.expanded: force_expand = True i = self.connections_model.iter_children(iter) while i and self.connections_model.iter_is_valid(i): self.connections_model.remove(i) keys = db.tables.keys() keys.sort() iterators = {} for name in keys: table = db.tables[name] i = self.connections_model.append(iter, (table,)) if force_expand: self.connections_tv.expand_row(path, False) force_expand = False self.redraw_table(table, i) iterators[name] = i if not new_tables: return for name in new_tables: table = db.tables[name] table.refresh(False) self.redraw_table(table, iterators[name]) self.process_events() def redraw_table(self, table, iter): #print "redraw table", table.name if table.expanded: self.connections_tv.expand_row(self.connections_model.get_path(iter), False) i = self.connections_model.iter_children(iter) while i and self.connections_model.iter_is_valid(i): self.connections_model.remove(i) for field in table.field_order: i = self.connections_model.append(iter, (table.fields[field],)) class output_handler: def __init__(self, print_stdout=False, log_file=None, log_flush=False): self.stdout = sys.stdout self.print_stdout = print_stdout self.log_flush = log_flush sys.stdout = self if log_file: self.log_fp = file(log_file, "a+") else: self.log_fp = None self.debug = print_stdout or log_file def write(self, s): if self.print_stdout: self.stdout.write(s) if self.log_flush: self.stdout.flush() if self.log_fp: s = s.strip("\r\n") if not s: # do not write empty lines to logfile return timestamp = str(datetime.datetime.now())[0:22] self.log_fp.write("%s %s\n" % (timestamp, s.replace("\n", "\n " + (" " * len(timestamp))))) if self.log_flush: self.log_fp.flush() def usage(): print """usage: emma [-h|--help] [-d|--debug] [-l output_log [-f|--flush]] -h|--help show this help message -d|--debug output debug information on stdout -l|--log FILE append all output to a specified log file -f|--flush flush {stdout,log} after each write """ sys.exit(0) def start(args): global new_instance debug_output = False log_file = None log_flush = False skip = False for i, arg in enumerate(args): if skip: skip = False continue if arg == "-h" or arg == "--help": usage() elif arg == "-d" or arg == "--debug": debug_output = True elif arg == "-f" or arg == "--flush": log_flush = True elif arg == "-l" or arg == "--log": if i + 1 == len(args): usage() log_file = args[i + 1] skip = True else: usage() # this singleton will be accessible as sys.stdout! output_handler(debug_output, log_file, log_flush) e = Emma() while 1: gtk.main() del e if not new_instance: break e = new_instance new_instance = None e.__init__() return 0 if __name__ == "__main__": sys.exit(start(sys.argv[1:]))