emma-0.6/0000755000175000017500000000000010526165411010421 5ustar flofloemma-0.6/emmalib/0000755000175000017500000000000010526165411012027 5ustar flofloemma-0.6/emmalib/plugins/0000755000175000017500000000000010526165411013510 5ustar flofloemma-0.6/emmalib/plugins/pretty_format/0000755000175000017500000000000010526165411016407 5ustar flofloemma-0.6/emmalib/plugins/pretty_format/__init__.py0000644000175000017500000002566510516754317020546 0ustar floflo# -*- 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 os import sys import time import gtk.glade import gobject import pprint import re import cStringIO def test(c, t, f): if c: return t return f def strspn(text, accept, pos=0): tl = len(text) start = pos while pos < tl and text[pos] in accept: pos += 1 return pos - start def strcspn(text, reject, pos=0): tl = len(text) start = pos while pos < tl and text[pos] not in reject: pos += 1 return pos - start def search_string_end(text, delim, p): s = p while True: l = strcspn(text, delim, s) # check if there is an odd count of backslashes before r = s + l - 1 count = 0 while text[r] == "\\": r -= 1 count += 1 escaped = count % 2 == 1 if not escaped: break s = s + l return s + l def search_parenthesis_end(text, s): # search closing ) jumping over quoted strings opens = 1 s += 1 while True: #print "search closing: %r" % text[s:] el = strcspn(text, "()\"'", s) end = s + el print "ptoken: %r" % text[end] if text[end] == "(": opens += 1 #print "now open:", opens s = end + 1 continue if text[end] == ")": opens -= 1 #print "now open:", opens s = end + 1 if opens == 0: break continue # must be a string delim = text[end] strend = search_string_end(text, delim, end + 1) quoted_string = text[end:strend + 1] #print "quoted string: %r" % quoted_string s = strend + 1 return s def get_token(text, p, allow_functions=False): ttype = "token" o = strspn(text, " \r\n\t", p) p = p + o print "\nget token from %r" % text[p:p + 25] if not text[p:]: return ttype, "", len(text) if text[p] in "\"'": delim = text[p] end = search_string_end(text, delim, p + 1) quoted_string = text[p:end + 1] #print "quoted string: %r" % quoted_string ttype = "quoted string" return ttype, quoted_string, end + 1 l = strcspn(text, " \r\n\t(,;=", p) s = p + l if s >= len(text): print "********last token? : %r" % text[p:] # todo? return ttype, text[p:], len(text) print "found first %r" % text[s] if text[s] == "(": s = search_parenthesis_end(text, s) if l == 0: ttype = "parenthesis block" else: ttype = "function call" l = s - p elif text[s] in "=,;" and l == 0: l = 1 else: pp = p + l wl = strspn(text, " \r\n\t", pp) pp += wl print "first char after: %r" % text[pp] if text[pp] == "(": # found function call # read function arguments to this token s = search_parenthesis_end(text, pp) l = s - p ttype = "function call" token = text[p:p + l] return ttype, token, p + l def pretty_print_function_call(function, compressed=False): l = strcspn(function, "(\n\r\t ") function_name = function[:l] ll = strspn(function, "\n\r\t ", l) function_args = function[l + ll + 1:-1] print "function name: %r args: %r" % (function_name, function_args) out = function_name + "(" p = 0 tl = len(function_args) while p < tl: tt, token, e = get_token(function_args, p) if not token: break print "fcn pf: token: %r type: %s" % (token, tt) if tt == "function call": # uniform pretty print a function call token = pretty_print_function_call(token) if not compressed and token == ",": out += token + " " else: out += token p = e return out + ")" class pretty_format: def __init__(self, emma_instance): self.emma = emma_instance self.toolbar_items = [] self.plugin_dir = os.path.dirname(os.path.abspath(__file__)) self.install_toolbar_item("query_toolbar", gtk.STOCK_INDENT, "pretty format query", self.on_pretty_format) self.install_toolbar_item("query_toolbar", gtk.STOCK_UNINDENT, "compress query", self.on_compress) q = self.emma.current_query if sys.stdout.debug: # check if we are running with debug output - enable example text print "\n\n\n" self.set_query_text(q, """# this is the pretty format test query. click the "pretty format" or "compress query" button in the query-toolbar. \n \t# comment before\n\n/* also before... */\n \tselect date_format \n\t (\nnow( \n"lalala" ) , "%Y-%m-%d" \n), ("%Y,((%m"), \', from ),here\', * from record_job where some_field = "a very interesting 'text'" order by job_id desc, \tvalid_from,\n\t\t\n\tmode,\n\tquery,\n\tpriority desc limit 150; select * from user; """) def cleanup(self): for item, toolbar in self.toolbar_items: toolbar.remove(item) del item def install_toolbar_item(self, toolbar_name, stock, item_catpion, callback): toolbar = self.emma.xml.get_widget(toolbar_name) button = gtk.ToolButton(stock) button.set_label(item_catpion) button.connect("clicked", callback) button.set_tooltip(self.emma.tooltips, item_catpion) toolbar.insert(button, -1) button.show() self.toolbar_items.append((button, toolbar)) def get_query_text(self, q): buffer = q.textview.get_buffer() return buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter()) def set_query_text(self, q, text): buffer = q.textview.get_buffer() buffer.set_text(text) def on_pretty_format(self, button): q = self.emma.current_query text = self.get_query_text(q) print "input: %r" % text output = cStringIO.StringIO() keyword_normalisation = "u" def starts_with(t, s, p): print repr(t[p:p + len(s)].lower()) if t[p:p + len(s)].lower() == s: return t[p:p + len(s)] return None def kw(s): if keyword_normalisation == "uppercase": return s.upper() if keyword_normalisation == "lowercase": return s.lower() return s p = 0 current_statement = None current_state = None tl = len(text) token = None while p < tl: tt, token, e = get_token(text, p) if not token: break print "got token %-60.60r of type %r at %s %s" % (token, tt, current_statement, current_state) if tt == "function call": # uniform pretty print a function call token = pretty_print_function_call(token) if token.startswith("#"): # comment line. skipping to eol e = text.find("\n", e) if e == -1: # last line break p = e continue if token.startswith("/*"): # comment line. skipping to eol e = text.find("*/", p) if e == -1: break p = e + 2 continue if token == ";": # new query current_statement = None current_state = None output.write(token) output.write("\n") p = e continue; if token.lower() == "select": # start of select statement! output.write(kw(token)) output.write("\n\t") p = e current_statement = "select" current_state = "fields" continue if token.lower() == "limit": output.write("\n") output.write(kw(token)) output.write("\n\t") current_statement = "limit" p = e continue if current_statement == "limit": output.write(token) if token == ",": output.write(" ") p = e continue if token.lower() == "order": output.write("\n") output.write(kw(token)) output.write(" ") # wait for by :) p = e current_statement = "order" continue if token.lower() == "by": output.write(kw(token)) output.write("\n\t") current_state = "order_fields" order_dir = None p = e continue if token.lower() == "where": output.write("\n") output.write(kw(token)) output.write("\n\t") current_statement = "where" p = e continue if current_statement == "where": if token.lower() in ("and", "or"): output.write("\n\t") output.write(kw(token)) else: output.write(token) p = e continue if current_statement == "select" and current_state == "fields": if token.lower() == "from": output.write("\n") output.write(kw(token)) output.write("\n\t") p = e current_state = "tables" continue output.write(token) if token == ",": output.write("\n\t") p = e continue if current_statement == "select" and current_state == "tables": output.write(token) if token == ",": output.write("\n\t") p = e continue if current_statement == "order": if token.lower() == "desc" or token.lower() == "asc": output.write(" ") output.write(kw(token)) else: output.write(token) if token == ",": output.write("\n\t") p = e continue break while True: break o = strspn(text, " \r\n\t", p) print "span : ", o s = p + o print "start: %r" % text[s:] break self.set_query_text(q, output.getvalue()) def on_compress(self, button): q = self.emma.current_query text = self.get_query_text(q) print "input: %r" % text output = cStringIO.StringIO() keyword_normalisation = "u" def kw(s): if keyword_normalisation == "uppercase": return s.upper() if keyword_normalisation == "lowercase": return s.lower() return s p = 0 tl = len(text) token = None keywords = "select,from,left,join,right,inner,where,and,or,order,by,having,group,limit,union,distinct" keywords = keywords.split(",") token = None last_token = token while p < tl: tt, token, e = get_token(text, p) if not token: break print "token : %r last_token: %r" % (token, last_token) if tt == "function call": # uniform pretty print a function call token = pretty_print_function_call(token, compressed=True) if token.startswith("#"): # comment line. skipping to eol e = text.find("\n", e) if e == -1: # last line break p = e continue if token.startswith("/*"): # comment line. skipping to eol e = text.find("*/", p) if e == -1: break p = e + 2 continue if token == ";": # new query current_statement = None current_state = None output.write(token) output.write("\n") last_token = token p = e continue; if token not in ",=" and last_token != None and last_token not in ",=;": output.write(" ") if token.lower() in keywords: output.write(kw(token)) p = e last_token = token continue output.write(token) p = e last_token = token self.set_query_text(q, output.getvalue()) plugin_instance = None def plugin_init(emma_instance): global plugin_instance plugin_instance = pretty_format(emma_instance) return plugin_instance def plugin_unload(): global plugin_instance plugin_instance.cleanup() del plugin_instance plugin_instance = None gc.collect() emma-0.6/emmalib/plugins/table_editor/0000755000175000017500000000000010526165411016145 5ustar flofloemma-0.6/emmalib/plugins/table_editor/__init__.py0000644000175000017500000003421010517473655020272 0ustar floflo# -*- 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 os import sys import time import gtk.glade import gobject import pprint import re pp = pprint.PrettyPrinter() LEN = 1 UNSIGNED = 2 BINARY = 4 ZERO = 8 data_type_capabilities = { "bigint": LEN | UNSIGNED | ZERO, "blob": LEN, "char": LEN | BINARY, "char unicode": LEN, "date": 0, "datetime": 0, "decimal": LEN | UNSIGNED | ZERO, "double": LEN | UNSIGNED | ZERO, "enum": LEN, "float": LEN | UNSIGNED | ZERO, "int": LEN | UNSIGNED | ZERO, "longblob": 0, "longtext": 0, "mediumblob": 0, "mediumint": LEN | UNSIGNED | ZERO, "mediumtext": 0, "set": LEN, "smallint": LEN | UNSIGNED | ZERO, "text": LEN, "time": 0, "year": LEN, "timestamp": LEN, "tinyblob": 0, "tinyint": LEN | UNSIGNED | ZERO, "tinytext": LEN | UNSIGNED | ZERO, "varchar": LEN | BINARY } def split_strings(string): return re.findall("(?:(?:'.*?')|[^ ])+", string) def test(c, t, f): if c: return t return f class table_editor: def __init__(self, emma_instance): self.emma = emma_instance self.popup_items = [] self.plugin_dir = os.path.dirname(os.path.abspath(__file__)) self.glade_file = os.path.join(self.plugin_dir, "table_editor.glade") if not os.access(self.glade_file, os.R_OK): self.glade_file = os.path.join(self.emma.glade_path, "table_editor.glade") if not os.access(self.glade_file, os.R_OK): raise ValueError("glade file %s not found!" % self.glade_file) else: print "galde file:", self.glade_file self.xml = gtk.glade.XML(self.glade_file) self.window = self.xml.get_widget("table_editor") self.xml.signal_autoconnect(self) self.treeview = self.xml.get_widget("table_columns") self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT) self.treeview.set_model(self.model) self.treeview.set_headers_clickable(False) self.treeview.set_reorderable(True) self.treeview.insert_column_with_attributes(-1, "name", gtk.CellRendererText(), text=0) self.treeview.insert_column_with_attributes(-1, "type", gtk.CellRendererText(), text=1) self.treeview.insert_column_with_attributes(-1, "comment", gtk.CellRendererText(), text=2) self.install_popup_item("table_popup", "edit table", self.edit_table) self.ignore_changes = True self.treeview.connect("drag-begin", self.drag_begin) self.treeview.connect("drag-end", self.drag_end) def row_changed(self, model, path, iter): if self.changed_handler: self.model.disconnect(self.changed_handler) self.changed_handler = None l = list(model[path][3]) l.append("reordered") model[path][3] = l def drag_begin(self, *args): self.changed_handler = self.model.connect("row_changed", self.row_changed) def drag_end(self, *args): if self.changed_handler: self.model.disconnect(self.changed_handler) self.changed_handler = None def cleanup(self): for item, menu in self.popup_items: menu.remove(item) del item self.popup_items = [] def install_popup_item(self, popup_name, item_catpion, callback): popup = self.emma.xml.get_widget(popup_name) for child in popup.get_children(): if child.get_child().get_text() == item_catpion: print "%s: warning: there already is a menu item called '%s' in '%s'" % (__name__, item_caption, popup_name) item = gtk.MenuItem(item_catpion) item.connect("activate", callback) item.show() popup.append(item) self.popup_items.append((item, popup)) def edit_table(self, menuitem): path, column, iter, table = self.emma.get_current_table() self.table = table tname = self.xml.get_widget("table_name") if not tname: print "error: table_name-field not found in xml", self.xml e = tname.set_text(table.name) e = tname.grab_focus() e = self.xml.get_widget("table_comment").set_text(table.props[14]) self.model.clear() for name in table.field_order: field = table.fields[name] comment = self.extract_comment(field[5]) # todo self.model.append(row=(field[0], field[1], comment, list(field), field)) self.xml.get_widget("table_deletefield").set_sensitive(False) self.xml.get_widget("table_field_properties").set_sensitive(False) self.deleted = set() self.window.show_all() def on_table_addfield_clicked(self, button): unnamed_count = 0 while True: field_name = "unnamed_%d" % unnamed_count for row in self.model: if row[0] == field_name: break else: break unnamed_count += 1 row = [field_name, "int", "", "", "", ""] iter = self.model.append(row=(row[0], row[1], "", list(row), None)) self.treeview.set_cursor(self.model.get_path(iter)) self.xml.get_widget("table_field_name").grab_focus() def on_table_columns_row_activated(self, *args): self.xml.get_widget("table_field_name").grab_focus() def on_table_deletefield_clicked(self, button): print "delete field!" path, column = self.treeview.get_cursor() row = self.model[path] if row[4]: self.deleted.add(row[3][0]) del self.model[path] self.xml.get_widget("table_deletefield").set_sensitive(False) self.xml.get_widget("table_field_properties").set_sensitive(False) def parse_type(self, string): match = re.search("\(([0-9,]+)\)$", string) if not match: return string, None return string[0:match.start(0)], match.group(1) def extract_comment(self, string): p = string.find("COMMENT") current = split_strings(string) for i, c in enumerate(current): if c.startswith("comment="): return c[len("comment=")+1:-1] return "" def get_field_capabilities(self, ftype): global data_type_capabilities try: return data_type_capabilities[ftype] except: print "unknwon type", ftype, "guessing all capabilities!" return LEN | UNSIGNED | ZERO | BINARY def set_type_restrictions(self, ftype, capabilities=None): if capabilities is None: capabilities = self.get_field_capabilities(ftype) set = self.set_field set("table_field_length_sensitive", capabilities & LEN) set("table_field_unsigned_sensitive", capabilities & UNSIGNED) set("table_field_binary_sensitive", capabilities & BINARY) def set_field(self, xml_name, value): if xml_name.endswith("_sensitive"): xml_name = xml_name[:-len("_sensitive")] widget = self.xml.get_widget(xml_name) if type(widget) != gtk.CheckButton: widget.set_editable(test(value, True, False)) widget.set_sensitive(test(value, True, False)) return widget = self.xml.get_widget(xml_name) if type(widget) == gtk.Entry: widget.set_text(str(value)) elif type(widget) == gtk.CheckButton: widget.set_active(value) elif type(widget) == gtk.ComboBox: model = widget.get_model() for i, v in enumerate(model): if value in v[0].split(", "): widget.set_active(i) break else: print "value", value, "not found in model", model else: print "unknown type", type(widget) def set_current_field(self, field): self.ignore_changes = True set = self.set_field set("table_field_name", field[0]) ftype, length = self.parse_type(field[1]) set("table_field_type", ftype) if length is None: set("table_field_length", "") else: set("table_field_length", length) default = not field[4] is None or field[2] == "YES" set("table_field_hasdefault", default) set("table_field_default_sensitive", default) if default: set("table_field_default", test(field[4] is None, "NULL", field[4])) else: set("table_field_default", "") set("table_field_notnull", field[2] != "YES") set("table_field_comment", self.extract_comment(field[5])) set("table_field_isautoincrement", field[5].find("auto_increment") != -1) set("table_field_unsigned", field[5].find("unsigned") != -1) set("table_field_binary", field[5].find("binary") != -1) self.xml.get_widget("table_field_properties").set_sensitive(True) self.set_type_restrictions(ftype) self.ignore_changes = False def on_cursor_changed(self, treeview): path, column = treeview.get_cursor() row = self.model[path] self.set_current_field(row[3]) self.xml.get_widget("table_deletefield").set_sensitive(True) def on_table_field_changed(self, widget): if self.ignore_changes: return def propagate_back(field, row): row[0] = field[0] # name row[1] = field[1] # type def set_extra(name, active): current = set(split_strings(row[3][5])) if active: current.add(name) else: current.discard(name) row[3][5] = " ".join(current) fname = widget.name if fname.startswith("table_"): fname = fname[len("table_"):] path, column = self.treeview.get_cursor() row = self.model[path] if fname == "field_name": row[3][0] = widget.get_text() elif fname == "field_type" or fname == "field_length": widget = self.xml.get_widget("table_field_type") ftype = widget.get_active_text().split(", ")[0] capabilities = self.get_field_capabilities(ftype) length = self.xml.get_widget("table_field_length").get_text() self.set_type_restrictions(ftype, capabilities) if capabilities & LEN and length != "": ftype += "(%s)" % length row[3][1] = ftype elif fname == "field_default": row[3][4] = widget.get_text() elif fname == "field_hasdefault": default = widget.get_active() self.set_field("table_field_default_sensitive", default) if not default: self.set_field("table_field_default", "") row[3][4] = test(default, self.xml.get_widget("table_field_default").get_text(), None) elif fname == "field_comment": current = split_strings(row[3][5]) for i, c in enumerate(current): if c.startswith("comment="): del current[i] break current.append("comment='%s'" % self.table.host.escape(widget.get_text())) row[3][5] = " ".join(current) elif fname == "field_notnull": row[3][2] = test(widget.get_active(), "", "YES") elif fname == "field_isautoincrement": set_extra("auto_increment", widget.get_active()) elif fname == "field_unsigned": set_extra("unsigned", widget.get_active()) elif fname == "field_binary": set_extra("binary", widget.get_active()) else: print "unknown change in field", fname propagate_back(row[3], row) def on_table_abort(self, *args): self.window.hide() def on_table_apply(self, *args): button = args[0] if len(args) > 1: mode = args[1] else: mode = "close" """ render alter table sql """ esc = self.table.host.escape query = "alter table `%s` " % esc(self.table.name) no_changes = query add = "" """ deleted columns """ for f in self.deleted: query += "%sdrop column `%s`" % (add, esc(f)) add = ","; """ modified or new columns """ last_field = None for f in self.model: if tuple(f[3]) == f[4]: # no changes on this field last_field = f[0]; continue; #print "this field changed from\n%s to\n%s" % (f[4], f[3]) if not f[4] is None: # modified existing field if f[4][0] != f[3][0]: # name changed query += "%schange column `%s` `%s` %s " % ( add, esc(f[4][0]), esc(f[3][0]), f[3][1] ) else: query += "%smodify column `%s` %s " % ( add, esc(f[3][0]), f[3][1] ) else: # new field query += "%sadd column `%s` %s " % ( add, esc(f[3][0]), f[3][1] ) if f[3][2] == "YES": # NULL allowed? query += "null " else: query += "not null " if not f[3][4] is None: # default value? query += "default '%s' " % esc(f[3][4]) if f[3][5]: # extra? query += f[3][5] + " "; if last_field is None: query += "FIRST "; else: query += "AFTER `%s` " % esc(last_field) add = "," last_field = f[3][0] # rename table table_changed = False new_name = self.xml.get_widget("table_name").get_text() if new_name != self.table.name: old_name = self.table.name query += "%srename to `%s` " % (add, new_name) add = "," table_changed = True # table comment # todo comment = self.xml.get_widget("table_comment").get_text() if comment != self.table["Comment"]: query += "%scomment = '%s' " % (add, esc(comment)) add = "" table_changed = True """ ask user """ if query != no_changes: if not self.emma.confirm( "edit table", "do you really want to edit the %s table in database %s on %s with this sql:\n%s" % ( self.table.name, self.table.db.name, self.table.db.host.name, query ), window=self.window): return else: self.window.hide() return """ execute sql """ print "executing" if self.table.db.query(query): # success """ close dialog """ self.table.create_table = None self.table.refresh() new_tables = self.table.db.refresh() self.emma.redraw_db(self.table.db, self.emma.get_db_iter(self.table.db), new_tables) self.emma.redraw_tables() self.window.hide() return self.emma.show_message("edit table", "sorry, can't change table - sql error") """ table_editor->hide(); if(old_name != new_name) { TreePath p = TreePath(table_iter); p.up(); table_iter = connections_model->get_iter(p); mysql_database_entry* db = ((mysql_table_entry*)edit_table->db)->db; db->refresh(); update_database(db, table_iter); } else { ((mysql_table_entry*)edit_table->db)->refresh(); on_connections_cursor_changed(); } """ def on_table_apply_and_open(self, button): self.on_table_apply(button, "key_editor") plugin_instance = None def plugin_init(emma_instance): global plugin_instance plugin_instance = table_editor(emma_instance) return plugin_instance def plugin_unload(): global plugin_instance plugin_instance.cleanup() del plugin_instance plugin_instance = None gc.collect() emma-0.6/emmalib/plugins/table_editor/table_editor.glade0000644000175000017500000007171010441064622021604 0ustar floflo 2 edit table GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT True True False gtk-edit True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False True False 2 True 2 2 False 0 0 True name: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 0 1 fill True comment: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 1 2 fill True True True True 0 True * False 1 2 0 1 True True True True 0 True * False 1 2 1 2 0 False False True False 0 235 True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True True True True False False False 0 True True True False 0 True GTK_ORIENTATION_HORIZONTAL GTK_TOOLBAR_ICONS True True True True gtk-add True True False False True True False True gtk-delete True True False False True 0 True False 2 True False False 0 5 True 5 2 False 0 0 True name: False False GTK_JUSTIFY_RIGHT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 0 1 fill True type: False False GTK_JUSTIFY_RIGHT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 1 2 fill True True default: True GTK_RELIEF_NORMAL True False False True 0 1 2 3 fill True comment: False False GTK_JUSTIFY_RIGHT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 3 4 fill True length/value: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 4 5 fill 104 True True True True 0 True * False 1 2 0 1 70 True True True True 0 True * False 1 2 2 3 70 True True True True 0 True * False 1 2 4 5 70 True True True True 0 True * False 1 2 3 4 70 True bigint blob char char unicode date datetime decimal, dec, numeric, fixed double, real enum float int, integer longblob longtext mediumblob mediumint mediumtext set smallint text time timestamp tinyblob tinyint, bool, boolean tinytext varchar year False True 1 2 1 2 fill fill 0 False False True True auto increment True GTK_RELIEF_NORMAL True False False True 0 False False True True NOT NULL True GTK_RELIEF_NORMAL True False False True 0 False False True True unsigned True GTK_RELIEF_NORMAL True False False True 0 False False True True binary True GTK_RELIEF_NORMAL True False False True 0 False False 0 True True 0 False False 0 True True True GTK_BUTTONBOX_END 10 True True True gtk-cancel True GTK_RELIEF_NORMAL True True True True GTK_RELIEF_NORMAL True True 0.5 0.5 0 0 0 0 0 0 True False 2 True gtk-ok 4 0.5 0.5 0 0 0 False False True OK / edit keys True False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True True True gtk-ok True GTK_RELIEF_NORMAL True 5 False False emma-0.6/emmalib/emma.glade0000644000175000017500000054105010526157200013746 0ustar floflo True emma GTK_WINDOW_TOPLEVEL GTK_WIN_POS_NONE False 1024 768 True False gtk-network True False False GDK_WINDOW_TYPE_HINT_NORMAL GDK_GRAVITY_NORTH_WEST True False True True 608 True False 0 True False 0 True 0.5 0.5 1 1 0 0 0 0 True GTK_PACK_DIRECTION_LTR GTK_PACK_DIRECTION_LTR True _File True True execute query from disk True True reload plugins True True gtk-refresh 1 0.5 0.5 0 0 True reload config True True reload self True True gtk-refresh 1 0.5 0.5 0 0 True reload theme True True True save workspace True True gtk-save-as 1 0.5 0.5 0 0 True restore workspace True True gtk-open 1 0.5 0.5 0 0 True True gtk-quit True True _Help True True Changelog True True gtk-about 1 0.5 0.5 0 0 True gtk-about True 0 True True True der hat nen tooltip, verdammt! True GTK_RELIEF_NORMAL True True gtk-dialog-info 4 0.5 0.5 0 0 2 False False 0 False False True True 224 True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True True False False True False False False True False True True True False GTK_POS_TOP False False True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True False True GTK_JUSTIFY_LEFT GTK_WRAP_NONE True 0 0 0 0 0 0 False True True info False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True False 0 True GTK_ORIENTATION_HORIZONTAL GTK_TOOLBAR_ICONS True True True True gtk-refresh True True False False True True True True False True False 0 True auto refresh every False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True True 1 1 False GTK_UPDATE_ALWAYS False False 0 0 100 0.5 10 10 0 True True True seconds False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False False False True True True True False False True True True False True True server version: False False GTK_JUSTIFY_LEFT False True 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 False False 0 False False True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True False False True False False False 0 True True False True True process list False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True False False True False False False False True True tables False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True True 266 True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True GTK_SHADOW_IN True False 0 3 True 1 2 False 0 0 0 False True True 2 False True 3 True 3 3 False 0 6 0 False True True False True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True False False True GTK_JUSTIFY_LEFT GTK_WRAP_NONE True 0 0 0 0 0 0 True True False True True table False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True False 0 True GTK_ORIENTATION_HORIZONTAL GTK_TOOLBAR_ICONS True True True execute query (F9, Ctrl-Enter) True gtk-execute True True False False True True True True False 41 True automatic reexecution True 0.5 1 False GTK_UPDATE_ALWAYS False False 0 0 99 0.5 10 10 False False True save query to file (ctrl-s) save query True gtk-save True True False False True True load query from file (ctrl-o) load query True gtk-open True True False False True True True True True False False True True gtk-select-font True True False False True True True True True False False True new query tab (ctrl-t) True gtk-new True True False False True True close query tab (ctrl-w) True gtk-close True True False False True True rename query tab gtk-bold True True False False True True True True True False False 0 False False True True True False GTK_POS_TOP True False True True 130 True False 0 True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN GTK_CORNER_TOP_LEFT True True True False True GTK_JUSTIFY_LEFT GTK_WRAP_NONE True 0 0 0 0 0 0 0 True True True False True False 0 True GTK_ORIENTATION_VERTICAL GTK_TOOLBAR_ICONS True True True False add new record gtk-add True True False False True True False delete record (ctrl+del) gtk-delete True True False False True True False store appended row True gtk-apply True True False False True True False save result as csv save result as csv True gtk-save-as True True False False True True save result as sql insert script True gtk-save-as True True False False True True False search for regular expression in this result (Ctrl+f / F3) gtk-find True True False False True True set font of result view True gtk-select-font True True False False True True False remove order clause True gtk-cancel True True False False True True remember order for this table! True gtk-dialog-warning True True False False True 0 False False True False 0 True False False GTK_JUSTIFY_CENTER False False 0 1 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True False False True False False False 1 True True True False 0 True True False True label False False GTK_JUSTIFY_LEFT False False 0 0.5 5 1 PANGO_ELLIPSIZE_NONE -1 True 0 0 False False True click here to jump to the selected host/database True False True False False GTK_JUSTIFY_LEFT False False 0 0.5 5 1 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False 1 False True 0 True True True True False True True False 0 True query1 False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True True False True gtk-close 1 0.5 0.5 0 0 0 False False tab 0 True True False True True query False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True True 0 True True True False True True True False GTK_POS_TOP False False True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True False False True False False False False True True sql log False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True True GTK_POLICY_ALWAYS GTK_POLICY_ALWAYS GTK_SHADOW_IN GTK_CORNER_TOP_LEFT True True True False False True False False False False True True messages False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True False 0 True False 5 True True wrap text True GTK_RELIEF_NORMAL True False False False 0 False False True click here to store your changes back to the database True update True GTK_RELIEF_NORMAL True 0 False False True 0 False True True save contents to file True GTK_RELIEF_NONE True True gtk-save-as 4 0.5 0.5 0 0 0 False False True read contents from file True GTK_RELIEF_NONE True True gtk-open 4 0.5 0.5 0 0 0 False False 0 False True True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN GTK_CORNER_TOP_LEFT True GTK_SHADOW_IN True True False False True GTK_JUSTIFY_LEFT GTK_WRAP_NONE True 0 0 0 0 0 0 0 True True False True True blobview False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True False 4 new connection GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT True False False gtk-new True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False True False 2 True 6 2 False 2 0 True connection name: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 0 1 fill True hostname: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 1 2 fill True user: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 3 4 fill 60 True password: False False GTK_JUSTIFY_RIGHT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 4 5 fill True True True True 0 True * False 1 2 0 1 True True True True 0 True * False 1 2 3 4 True True True False 0 True * False 1 2 4 5 True database: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 5 6 fill True True True True 0 True * False 1 2 5 6 True port: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 2 3 fill True True True True 0 True * False 1 2 1 2 True True True True 0 True * False 1 2 2 3 0 False False True True 11 True True apply True GTK_RELIEF_NORMAL True 0 True True True True test True GTK_RELIEF_NORMAL True 0 True True True True abort True GTK_RELIEF_NORMAL True 0 True True 4 False False False emma by Florian Schmidt 2006 Extendable Mysql Managing Assistant successor of yamysqlfront False Florian Schmidt <schmidt_florian at gmx.de> translator-credits True refresh True True gtk-refresh 1 0.5 0.5 0 0 True modify connection True True gtk-edit 1 0.5 0.5 0 0 True delete connection True True gtk-delete 1 0.5 0.5 0 0 True new connection True True gtk-new 1 0.5 0.5 0 0 True True new database True True gtk-new 1 0.5 0.5 0 0 field conditions GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT True False False True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False True True False 0 True GTK_BUTTONBOX_END True True True gtk-cancel True GTK_RELIEF_NORMAL True -6 True True True gtk-ok True GTK_RELIEF_NORMAL True -5 0 False True GTK_PACK_END 5 True 2 4 False 4 4 True operator False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 2 3 0 1 fill True field False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 1 2 0 1 fill True logic False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 0 1 fill True False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 1 2 fill True False 3 True value False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 True True True True reset True GTK_RELIEF_NORMAL True 0 False False 3 4 0 1 fill fill 0 False False local regex search GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT True False False True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False True True False 0 True GTK_BUTTONBOX_END True True True gtk-cancel True GTK_RELIEF_NORMAL True -6 True True True gtk-find True GTK_RELIEF_NORMAL True -5 0 False True GTK_PACK_END True False 0 True regular expression: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True True True True 0 True * True 0 True True 0 False False True 1 2 True 0 0 True True case insensitive True GTK_RELIEF_NORMAL True False False True 0 1 0 1 True True start at first row True GTK_RELIEF_NORMAL True False False True 1 2 0 1 0 True True True refresh True True gtk-refresh 1 0.5 0.5 0 0 True drop database True True gtk-delete 1 0.5 0.5 0 0 True True new table True True gtk-new 1 0.5 0.5 0 0 True refresh True True gtk-refresh 1 0.5 0.5 0 0 True truncate table True True gtk-cut 1 0.5 0.5 0 0 True drop table True True gtk-delete 1 0.5 0.5 0 0 True copy field value True True gtk-copy 1 0.5 0.5 0 0 True copy record as csv True True gtk-copy 1 0.5 0.5 0 0 True copy column as csv True True gtk-copy 1 0.5 0.5 0 0 True copy column names True True gtk-copy 1 0.5 0.5 0 0 True set field value to... True True NULL True True gtk-edit 1 0.5 0.5 0 0 True now() True True gtk-edit 1 0.5 0.5 0 0 True unix_timestmap(now()) True True gtk-edit 1 0.5 0.5 0 0 True password(X) True True gtk-edit 1 0.5 0.5 0 0 True sha1(X) True True gtk-edit 1 0.5 0.5 0 0 True True add record True True gtk-add 1 0.5 0.5 0 0 True delete record True True gtk-delete 1 0.5 0.5 0 0 True copy True True gtk-copy 1 0.5 0.5 0 0 True set as query text True True gtk-justify-fill 1 0.5 0.5 0 0 True delete True True gtk-delete 1 0.5 0.5 0 0 True clear all entries True 450 300 index-/key editor GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT True True False True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False True True False 0 True GTK_BUTTONBOX_END True True True gtk-cancel True GTK_RELIEF_NORMAL True -6 True True True gtk-ok True GTK_RELIEF_NORMAL True -5 0 False True GTK_PACK_END True False 0 True True 0 True index-/key name: False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 True True True False True True 0 True True 2 False False True GTK_BUTTONBOX_DEFAULT_STYLE 0 True there can only be one primary key for a table True True GTK_RELIEF_NORMAL True True 0.5 0.5 0 0 0 0 0 0 True False 2 True gtk-add 4 0.5 0.5 0 0 0 False False True create primary key True False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True create new index True True GTK_RELIEF_NORMAL True True 0.5 0.5 0 0 0 0 0 0 True False 2 True gtk-add 4 0.5 0.5 0 0 0 False False True create index True False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True drop the selected index/key True True GTK_RELIEF_NORMAL True True 0.5 0.5 0 0 0 0 0 0 True False 2 True gtk-remove 4 0.5 0.5 0 0 0 False False True remove True False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False 2 False False True wheter this index is a unique index True unique True GTK_RELIEF_NORMAL True False False True 2 False True True True 200 True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True True False True False False False True True True False 0 True GTK_ORIENTATION_VERTICAL GTK_TOOLBAR_ICONS True True True add selected field to this index True gtk-add True True False False True True remove selected field from this index True gtk-remove True True False False True 0 False False True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_NONE GTK_CORNER_TOP_LEFT True True True True False True False False False 0 True True True True 0 True True 0 True True 500 400 changelog GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT False True False True False False GDK_WINDOW_TYPE_HINT_NORMAL GDK_GRAVITY_NORTH_WEST True False True True GTK_POLICY_ALWAYS GTK_POLICY_ALWAYS GTK_SHADOW_IN GTK_CORNER_TOP_LEFT True True False False True GTK_JUSTIFY_LEFT GTK_WRAP_NONE False 0 0 0 0 0 0 True kill process True True gtk-stop 1 0.5 0.5 0 0 execute query from disk GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT True False False gtk-execute True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False 4 True 6 4 False 2 2 True GTK_BUTTONBOX_END 5 True True True gtk-cancel True GTK_RELIEF_NORMAL True True True True gtk-ok True GTK_RELIEF_NORMAL True 0 4 5 6 fill fill True False 0 True Filename: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 False False True select sql skript GTK_FILE_CHOOSER_ACTION_OPEN True False False -1 0 True True 0 4 0 1 fill True start from line: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 1 2 fill True True 1 0 True GTK_UPDATE_ALWAYS False False 1 1 1.00000001505e+29 1 100 100 1 2 1 2 True UI update interval in s: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 2 3 1 2 fill True True 1 2 True GTK_UPDATE_ALWAYS False False 0.5 0 100 0.25 1 1 3 4 1 2 True True append to query log True GTK_RELIEF_NORMAL True False False True 0 2 4 5 fill True True stop on error True GTK_RELIEF_NORMAL True False False True 2 4 4 5 fill True True limit to database: True GTK_RELIEF_NORMAL True False False True 0 2 2 3 fill True False True True True 0 True * False 2 4 2 3 True True exclude queries matching: True GTK_RELIEF_NORMAL True False False True 0 2 3 4 fill True False True True True 0 True * False 2 4 3 4 308 110 execute query from disk progress GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT True False False True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False 4 True 4 4 False 2 5 True line: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 0 1 fill True current line number False True 0 True * False 6 1 2 0 1 True offset: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 2 3 0 1 fill True current byte offset from file start False True 0 True * False 6 3 4 0 1 True query: False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 1 1 2 fill True False True 0 True * False 1 4 1 2 True GTK_PROGRESS_LEFT_TO_RIGHT 0 0.10000000149 PANGO_ELLIPSIZE_NONE 0 4 2 3 fill True False 0 True False False GTK_JUSTIFY_LEFT False False 0 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 0 True True True GTK_BUTTONBOX_END 5 True True True True gtk-cancel True GTK_RELIEF_NORMAL True 0 False True 0 4 3 4 fill True latin1 True True clear messages True emma-0.6/emmalib/__init__.py0000644000175000017500000034256310526161115014152 0ustar floflo# -*- 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:])) emma-0.6/emmalib/mysql_db.py0000644000175000017500000000426210516735662014231 0ustar floflo# -*- 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 traceback from mysql_table import * class mysql_db: def __init__(self, host, name = None): self.handle = host.handle self.host = host if name != None: self.name = name self.expanded = False self.status_headers = [] self.tables = {} else: print "unpickling tables!", self.handle for name, table in self.tables.iteritems(): table.handle = self.handle #self.id = id def __getstate__(self): d = dict(self.__dict__) for i in ["handle"]: del d[i] #print "db will pickle:", d return d def refresh(self): self.host.select_database(self) if not self.host.query("show table status"): return new_tables = [] result = self.handle.store_result() self.status_headers = [] for h in result.describe(): self.status_headers.append(h[0]) old = dict(zip(self.tables.keys(), range(len(self.tables)))) for row in result.fetch_row(0): if not row[0] in old: #print "new table", row[0] self.tables[row[0]] = mysql_table(self, row, result.describe()) new_tables.append(row[0]) else: #print "known table", row[0] # todo update self.tables[row[0]] with row! del old[row[0]] for table in old: print "destroy table", table del self.tables[table] return new_tables def query(self, query, check_use=True, append_to_log=True): self.host.select_database(self) return self.host.query(query, check_use, append_to_log) emma-0.6/emmalib/mysql_host.py0000644000175000017500000001272010516746305014613 0ustar floflo# -*- 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 _mysql import _mysql_exceptions import time import re import traceback from mysql_db import * class mysql_host: def __init__(self, *args): if len(args) == 2: # unpickle self.sql_log, self.msg_log = args print "unpickle host!" if self.connected: db_name = self.current_db.name self.current_db = None print "try to reconnect after unpickling!" self.connect() print "resulting handle:", self.handle if self.connected: print "unpickling databases!", self.handle for name, db in self.databases.iteritems(): db.__init__(self) self._use_db(db_name, True) else: self.sql_log, self.msg_log, self.name, self.host, self.port, self.user, self.password, self.database, self.connect_timeout = args self.connected = False self.databases = {} # name -> db_object self.current_db = None self.expanded = False self.handle = None self.processlist = None self.update_ui = None self.last_error = "" def __getstate__(self): d = dict(self.__dict__) for i in ["sql_log", "msg_log", "handle", "processlist", "update_ui", "update_ui_args"]: del d[i] #print "host will pickle:", d return d def get_connection_string(self): if self.port != "": output = "%s:%s" % (self.host, self.port) else: output = "%s" % self.host output += ",%s,%s,%s" % (self.user, self.password, self.database) return output def set_update_ui(self, update_ui, *args): self.update_ui = update_ui self.update_ui_args = args def connect(self): c = { "host": self.host, "user": self.user, "passwd": self.password, "connect_timeout": int(self.connect_timeout) } if self.port: c["port"] = int(self.port) if self.database: c["db"] = self.database try: self.handle = _mysql.connect(**c) except _mysql_exceptions.OperationalError: self.connected = False self.msg_log("%s: %s" % (sys.exc_type, sys.exc_value[1])) return self.connected = True self.refresh() if self.database: self._use_db(self.database) def ping(self): try: self.handle.ping() return True except: self.connected = False self.msg_log(sys.exc_value[1]) return False def close(self): self.databases = {} self.processlist = None if self.handle: self.handle.close() self.handle = None self.current_db = None self.connected = False if self.update_ui: self.update_ui(self, *self.update_ui_args) def query(self, query, check_use=True, append_to_log=True, encoding=None): if not self.handle: self.msg_log("not connected! can't execute %s, %s, %s" % (query, str(self.handle), str(self))) return if append_to_log: self.sql_log(query) try: self.query_time = 0 start = time.time() if encoding: query = query.encode(encoding, "ignore") self.handle.query(query) self.query_time = time.time() - start except: #print "error code:", sys.exc_value[0] try: self.last_error = sys.exc_value[1] except: self.last_error = str(sys.exc_value) s = sys.exc_value[1] #print "error:", [s] s = s.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 ", "MySQL syntax error at ") self.msg_log(s) if sys.exc_value[0] == 2013: # lost connection self.close() return False if not check_use: return True match = re.match("(?is)^([ \r\n\t]*|#[^\n]*)*(use[ \r\n\t]*).*", query) if match: dbname = query[match.end(2):].strip("`; \t\r\n") print "use db: '%s'" % dbname self._use_db(dbname, False) # reexecute to reset field_count and so on... self.handle.query(query) return True def _use_db(self, name, do_query=True): if self.current_db and name == self.current_db.name: return if do_query: self.query("use `%s`" % name, False) try: self.current_db = self.databases[name] except KeyError: print "Warning: used an unknown database %r! please refresh host!\n%s" % (name, "".join(traceback.format_stack())) def select_database(self, db): self._use_db(db.name) def refresh(self): self.query("show databases") result = self.handle.store_result() old = dict(self.databases) db_id = len(old) for row in result.fetch_row(0): if not row[0] in old: self.databases[row[0]] = mysql_db(self, row[0]) else: del old[row[0]] for db in old.keys(): print "remove database", db del self.databases[db] def refresh_processlist(self): if not self.query("show processlist"): return result = self.handle.store_result() self.processlist = (result.describe(), result.fetch_row(0)) def insert_id(self): return self.handle.insert_id() def escape(self, s): if s is None: return s return self.handle.escape_string(s) emma-0.6/emmalib/mysql_query_tab.py0000644000175000017500000001146410526154200015621 0ustar floflo# -*- 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 pango import gtk import traceback class mysql_query_tab: def __init__(self, xml, nb): self.xml = xml self.nb = nb renameload = { "textview": "query_text", "treeview": "query_view", "save_result": "save_result", "save_result_sql": "save_result_sql", "add_record": "add_record_tool", "delete_record": "delete_record_tool", "apply_record": "apply_record_tool", "local_search": "local_search_button", "remove_order": "remove_order", "label": "query_label", "page": "first_query", "query_bottom_label": "query_bottom_label", "query_db_label": "query_db_label", } for attribute, xmlname in renameload.iteritems(): self.__dict__[attribute] = xml.get_widget(xmlname) self.current_host = None self.current_db = None self.model = None self.last_source = None self.result_info = None self.append_iter = None self.save_result_sql.set_sensitive(False) self.last_path = None if hasattr(self, "query"): self.textview.get_buffer().set_text(self.query) self.last_auto_name = None def __getstate__(self): b = self.textview.get_buffer() d = { "name": self.nb.get_tab_label_text(self.page), "query": b.get_text(b.get_start_iter(), b.get_end_iter()) } print "query will pickle:", d return d def auto_rename(self, new_auto_name): label = self.get_label() if label is None: return if self.last_auto_name is None: print "no last_auto_name" label.set_text(new_auto_name) self.last_auto_name = new_auto_name return current_name = label.get_text() if self.last_auto_name in current_name: print "setting new %r from old %r" % (new_auto_name, current_name) label.set_text(current_name.replace(self.last_auto_name, new_auto_name)) self.last_auto_name = new_auto_name else: print "last auto name %r not in %r!" % (self.last_auto_name, current_name) return def get_label(self): tab_widget = self.nb.get_tab_label(self.page) if not tab_widget: print "no tab widget" return labels = filter(lambda w: type(w) == gtk.Label, tab_widget.get_children()) if not labels: print "no label found!" return return labels[0] def user_rename(self, new_name): tab_widget = self.nb.get_tab_label(self.page) label = self.get_label() label.set_text(new_name) def destroy(self): # try to free some memory if self.model: self.model.clear() self.textview.get_buffer().set_text("") del self.treeview del self.model del self.textview self.treeview = None self.model = None self.textview = None self.update_db_label() def set(self, text): self.last_source = text self.textview.get_buffer().set_text(text) def update_db_label(self): h = self.current_host d = self.current_db if not h: self.query_db_label.set_label("no host/database selected") return title = "selected host" if d: dname = "/" + d.name title = "selected database" else: dname = "" if h.name == h.host: hname = h.name else: hname = "%s(%s)" % (h.name, h.host) self.query_db_label.set_label("%s: %s@%s%s" % ( title, h.user, hname, dname )) self.auto_rename("%s%s" % (h.name, dname)) def set_current_host(self, host): if self.current_host == host and host is not None and self.current_db == host.current_db: return self.current_host = host if host: self.current_db = host.current_db else: self.current_db = None self.update_db_label() def set_current_db(self, db): self.current_host = db.host self.current_db = db self.update_db_label() def update_bottom_label(self): self.query_bottom_label.set_label("encoding: %s" % self.encoding) def set_query_encoding(self, encoding): self.encoding = encoding self.update_bottom_label() def set_query_font(self, font_name): self.textview.get_pango_context() fd = pango.FontDescription(font_name) self.textview.modify_font(fd) def set_result_font(self, font_name): self.treeview.get_pango_context() fd = pango.FontDescription(font_name) self.treeview.modify_font(fd) def set_wrap_mode(self, wrap): self.textview.set_wrap_mode(wrap) emma-0.6/emmalib/mysql_table.py0000644000175000017500000000541610516735704014732 0ustar floflo# -*- 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, time class mysql_table: def __init__(self, db, props, props_description): self.handle = db.handle self.host = db.host self.db = db self.props = props self.props_dict = dict(zip(props_description, props)) self.name = props[0] self.fields = {} self.field_order = [] self.expanded = False self.last_field_read = 0 self.create_table = "" self.describe_headers = [] def __getstate__(self): d = dict(self.__dict__) for i in ["handle"]: del d[i] #print "table will pickle:", d return d def __getitem__(self, what): try: return self.props_dict[what] except: pass print "property", what, "not found in table props:", self.props_dict def refresh(self, refresh_props=True): self.db.host.select_database(self.db) if refresh_props: self.host.query("show table status like '%s'" % self.name) result = self.handle.store_result() rows = result.fetch_row(0) self.props = rows[0] self.props_dict = dict(zip(map(lambda v: v[0], result.describe()), rows[0])) self.name = self.props[0] self.host.query("describe `%s`" % self.name) result = self.handle.store_result() self.describe_headers = [] for h in result.describe(): self.describe_headers.append(h[0]) self.fields = {} self.field_order = [] for row in result.fetch_row(0): self.field_order.append(row[0]) self.fields[row[0]] = row self.last_field_read = time.time() return def __str__(self): output = "" for h, p in zip(self.db.status_headers, self.props): output += "\t%-25.25s: %s\n" % (h, p) return output def get_create_table(self): if not self.create_table: self.db.host.select_database(self.db) self.host.query("show create table `%s`" % self.name) print "create with:", self.handle result = self.handle.store_result() if not result: print "can't get create table for %s at %s and %s" % (self.name, self, self.handle) return "" result = result.fetch_row(0) self.create_table = result[0][1] return self.create_table emma-0.6/icons/0000755000175000017500000000000010526165411011534 5ustar flofloemma-0.6/icons/db.png0000644000175000017500000000043510517472061012633 0ustar flofloPNG  IHDR(-SgAMA7tEXtSoftwareAdobe ImageReadyqe<PLTE*?tRNSSIDATxb`AČ0  01CH  EU .bA, 1*X,b@u' @ н`m_aIENDB`emma-0.6/icons/emma.png0000644000175000017500000000265710517472061013175 0ustar flofloPNG  IHDR szzbKGD pHYs  tIME W]O-#z(fL&JbHRT*L2suf3 O>}t:bH___ܵZ 0###T*޼yvvnqvvlZh4R(x$v1 rXYYayyJOO::QA4,nS`00::ʓ'O0LB!Zx<֚p &'' Np8L&AӴ <[[[( boo/>h4277G>? \rH"Z>uiSTGUճL&I&zd2޽{׶+ȵkp\zO2I" 2H߿wN, SSSlmmʊcy^/-100j%H4 ۙ(\|-S:FGGY__Ǐg|fkε#IhF*\. T*i(i~JҒH$ Al6SQUFSLNNih%\.E!˱jK}%|>߿gqqEQN(qvvϓlj^'088&Ɠ˫(6hl6.vc@ۉbETU% gTU֬ƫW266 R)$F~|>T!vx<餻FD CŽ7"Ȳ^![(~؃E LV#N嘞buNT*VVVXXXrq]V~54Mccc8UGUU<(BP Jq`{BQTUEe*sss}jBel6>YinH+)eE<333 l♻VaNnbccP(D:>r@ B^wbW8 ?˜195۷y&J E/|rM鯿#ÇD"8i'Z}~XL&WԧѴa  cvdlR}IENDB`emma-0.6/icons/field.png0000644000175000017500000000025610517472061013332 0ustar flofloPNG  IHDRb PLTEatRNS@fbKGDH pHYs  tIME 2IDATc`@?0Bj L!%dw~pIENDB`emma-0.6/icons/host.png0000644000175000017500000000047410517472061013226 0ustar flofloPNG  IHDR(-SgAMA7tEXtSoftwareAdobe ImageReadyqe<PLTEƄvJ8tRNSKFIDATxb`Cg1@11#0\ bB\X*$@@VT @ki -Bd(+02ll`@ +܏Ā}+9eIENDB`emma-0.6/icons/index.png0000644000175000017500000000040510517472061013352 0ustar flofloPNG  IHDR(-SKPLTEfffnӉtRNS@fhIDATWe 7^ ̡i&Ch$޳,7eP.!  \# ǤV RhF"VQuWEE"4oν;IENDB`emma-0.6/icons/index_primary.png0000644000175000017500000000040510517472061015115 0ustar flofloPNG  IHDR(-SKPLTEfffY{tRNS@fhIDATWe 7^ ̡i&Ch$޳,7eP.!  \# ǤV RhF"VQuWEE"4oν;IENDB`emma-0.6/icons/index_unique.png0000644000175000017500000000040510517472061014740 0ustar flofloPNG  IHDR(-SKPLTEfff_tRNS@fhIDATWe 7^ ̡i&Ch$޳,7eP.!  \# ǤV RhF"VQuWEE"4oν;IENDB`emma-0.6/icons/offline_host.png0000644000175000017500000000041310517472061014721 0ustar flofloPNG  IHDR7bKGD̿ pHYs  tIME,IDAT(} ?y6-]Z )I,L- :G<~{Q`_Z,OPQI(>^8Ay Žo<k0dA%HD^/'}\*vullDA" mb ]2nST%2tIENDB`emma-0.6/icons/table.png0000644000175000017500000000037410517472061013337 0ustar flofloPNG  IHDR(-SgAMA7tEXtSoftwareAdobe ImageReadyqe< PLTETtRNS@*fIDATxb`FĀ.@@$ @DLp @`F8bf  0T P@*C@33@ax 0 F n.IENDB`emma-0.6/theme/0000755000175000017500000000000010526165411011523 5ustar flofloemma-0.6/theme/gtk-2.0/0000755000175000017500000000000010526165411012605 5ustar flofloemma-0.6/theme/gtk-2.0/gtkrc0000644000175000017500000001325010526160413013640 0ustar floflogtk-can-change-accels = 1 gtk-menu-drop-shadow = 1 gtk-menu-shadow-delay = 100 style "default" { GtkButton::default_border = {0, 0, 0, 0} GtkButton::default_outside_border = {0, 0, 0, 0} GtkButton::default_spacing = 10 GtkButton::focus-line-width = 1 GtkButton::focus-padding = 0 GtkCheckButton::indicator_size = 15 GtkMenuBar::shadow_type = out GtkMenuItem::selected_shadow_type = out GtkPaned::handle_full_size = 1 GtkPaned::handle_size = 8 GtkRadioButton::indicator_size = 15 GtkRange::slider_width = 15 GtkRange::stepper_size = 15 GtkRange::stepper_spacing = 0 GtkRange::trough_border = 0 GtkScrollbar::min_slider_length = 20 GtkToolbar::shadow_type = none GtkWidget::focus-line-width = 1 GtkWidget::focus_padding = 2 GtkWidget::interior_focus = 5 GtkWidget::internal_padding = 0 xthickness = 2 ythickness = 2 fg[NORMAL] = "#000000" fg[ACTIVE] = "#000000" fg[PRELIGHT] = "#000000" fg[SELECTED] = "#224466" fg[INSENSITIVE] = "#808080" bg[NORMAL] = "#e8e8e6" bg[ACTIVE] = "#d8d8d6" bg[PRELIGHT] = "#e8e8e6" bg[SELECTED] = "#b3c8dd" bg[INSENSITIVE] = "#e8e8e6" text[NORMAL] = "#000000" text[ACTIVE] = "#000000" text[PRELIGHT] = "#000000" text[SELECTED] = "#224466" text[INSENSITIVE] = "#808080" base[NORMAL] = "#f0f0f0" base[ACTIVE] = "#d8d8d6" base[PRELIGHT] = "#e8e8e6" base[SELECTED] = "#b3c8dd" base[INSENSITIVE] = "#e8e8e6" engine "xfce" { grip_style = slide smooth_edge = true } } widget_class "*" style "default" style "colored" = "default" { xthickness = 3 ythickness = 3 bg[ACTIVE] = "#A4B8CB" bg[PRELIGHT] = "#B3C8DD" fg[ACTIVE] = "#224466" fg[PRELIGHT] = "#224466" text[ACTIVE] = "#224466" text[PRELIGHT] = "#224466" engine "xfce" { smooth_edge = true grip_style = slide boxfill { fill_style = plain } } } widget_class "*List*" style "colored" class "*List*" style "colored" widget_class "*Text*" style "colored" class "*Text*" style "colored" widget_class "*Entry*" style "colored" class "*Entry*" style "colored" style "menubar" = "colored" { xthickness = 1 ythickness = 2 engine "xfce" { smooth_edge = true grip_style = slide boxfill { fill_style = plain } } } widget_class "*BonoboDockItem" style "menubar" class "*BonoboDockItem" style "menubar" widget_class "*HandleBox" style "menubar" class "*HandleBox" style "menubar" widget_class "*ToolBar" style "menubar" class "*ToolBar" style "menubar" widget_class "*MenuBar" style "menubar" class "*MenuBar" style "menubar" style "menuitem" = "colored" { xthickness = 2 ythickness = 2 engine "xfce" { smooth_edge = true grip_style = slide boxfill { fill_style = gradient orientation = auto shade_start = 0.80 shade_end = 1.80 } } } widget_class "*MenuItem*" style "menuitem" class "*MenuItem*" style "menuitem" style "scrollbar" = "default" { xthickness = 2 ythickness = 2 engine "xfce" { smooth_edge = true grip_style = slide boxfill { fill_style = gradient orientation = auto shade_start = 0.80 shade_end = 1.80 } } } widget_class "*Scrollbar*" style "scrollbar" class "*Scrollbar*" style "scrollbar" widget_class "*GtkProgress*" style "scrollbar" class "*GtkProgress*" style "scrollbar" style "button" = "colored" { xthickness = 3 ythickness = 3 engine "xfce" { smooth_edge = true grip_style = slide boxfill { fill_style = gradient orientation = vertical shade_start = 0.80 shade_end = 1.80 } } } widget_class "*Button*" style "button" class "*Button*" style "button" widget_class "*button*" style "button" class "*button*" style "button" widget_class "*OptionMenu*" style "button" class "*OptionMenu*" style "button" widget_class "*Tree*" style "button" class "*Tree*" style "button" widget_class "*GtkScale*" style "button" class "*GtkScale*" style "button" widget_class "*CheckButton*" style "default" class "*CheckButton*" style "default" widget_class "*RadioButton*" style "default" class "*RadioButton*" style "default" # This is for ROX-Filer # style "rox" = "default" { bg[NORMAL] = "#ffffff" bg[ACTIVE] = "#ffffff" } widget_class "*Collection" style "rox" # This is for the window borders (xfwm4 & metacity) # style "titlebar" = "default" { bg[SELECTED] = "#b3c8dd" fg[SELECTED] = "#224466" bg[INSENSITIVE] = "#d8d8d6" fg[INSENSITIVE] = "#000000" } widget "xfwm" style "titlebar" class "MetaFrames" style "titlebar" widget_class "MetaFrames" style "titlebar" emma-0.6/theme/ICON.png0000644000175000017500000000000010526160413012744 0ustar flofloemma-0.6/theme/README.html0000644000175000017500000000015410526160413013343 0ustar floflo
A much colder version of the default theme for xfce.

Olivier Fourdan <fourdan@xfce.org>
emma-0.6/changelog0000644000175000017500000000766410526165355012317 0ustar flofloemma (0.6) unstable; urgency=low Tue, 24 Oct 2006 22:35:22 +0200: * let all data files install to prefix + share/emma Mon, 13 Nov 2006 19:45:01 +0100 * debian bug+patch Bug#398337: emma: Impossible to create a new connection from Manolo Daz * bugfix: connection test button does not use given port number * remember treeview cursor per tab * automatic tab nameing with user defined pre-postfixes * support for gtk themes thru "theme" config option - either as absolute path to theme-dir or as a subdir of os.path.join(sys.prefix, "share", "themes") * included xfce's "Xfce-winter" theme from Olivier Fourdan * first experimental pretty_format plugin - much work needed -- Florian Schmidt Mon, 13 Nov 2006 22:36:32 +0100 emma (0.5) unstable; urgency=low * added copyright information to each .py file * enclose import of gtk in try/catch to allow setup.py to run without X * added first *very* experimental version of pretty format plugin * added command line option and suppress debug output * create config directory on first save * fixed default localhost configuration on first startup * no database neccessary to connect to mysql host * added message window wm hint * newly created query get current host assigned * fixed bug in query tab closing -- Florian Schmidt Sun, 22 Oct 2006 22:56:59 +0200 emma (0.4) unstable; urgency=low Sat, 12 Aug 2006 19:54:37 +0200 * clear all sql_log/message entries via popup * extended blobview for editing of big values (save/load to/from) disk * per query-tab encoding support (all python supported encodings) * fixed tab-signal handling - popup and buttons now work on all tabs * reread-config menu option * store table order on request Sun, 13 Aug 2006 17:52:25 +0200 * improved append to table-function to requery created record * each tab has its on selected database (show beneath the encoding) * automatic query reexecution (until error) * new feature: save result as sql script * new field_conditions operators: "isnull", "not isnull" and "not like" * pass unescaped input to query if field_conditions value starts with "eval: " * fixed sql_log when > and < chars appear in query * fixed sql_log to retain the complete sql query even if it doesn't display it -- Florian Schmidt Sun, 13 Aug 2006 17:52:25 +0200 emma (0.3) unstable; urgency=low Sun, 04 Jun 2006 17:47:06 +0200 * fixed field escaping at order-change * introduced config directory * first plugin system * first table_editor plugin -- Florian Schmidt Sun, 04 Jun 2006 17:47:06 +0200 emma (0.2) unstable; urgency=low Sun, 26 Feb 2006 01:45:54 +0100 * table view * query result popup * order by click * field condition filter * local regex search * append row * delete row * edit row Sun, 26 Feb 2006 14:05:56 +0100 * fixed database/table order * show tables while loading their descriptions * process events in show_sql_log() * update table view after first nb-page change * db-tables view * refresh/truncate/drop table * refresh db * new table * drop db * new database * refresh host * delete host * new host * modify host Wed, 01 Mar 2006 19:48:49 +0100 * load query * save query * mysql_ping to connected hosts * save workspace * load workspace * first easy pickle/unpickle Fri, 03 Mar 2006 18:59:23 +0100 * save result Sat, 04 Mar 2006 18:53:21 +0100 * debian package Sat, 11 Mar 2006 16:03:49 +0100 * execute big sql skripts from file * better placement of text-cursor on mysql error * support for automatic reading bzip2 files on "execute big sql skripts from file" * exclude regex -- Florian Schmidt Sat, 11 Mar 2006 16:03:49 +0100 emma (0.1) unstable; urgency=low * copied yamysqlfront.glade to emma.glade * connect first signal handlers in menubar -- Florian Schmidt Sun, 15 Jan 2006 17:50:07 +0100 emma-0.6/emma0000755000175000017500000000162010517473514011273 0ustar floflo#!/usr/bin/python2.4 # -*- 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 sys.path.insert(0, "./") import emmalib sys.exit(emmalib.start(sys.argv[1:])) emma-0.6/setup.py0000644000175000017500000000325210526165256012144 0ustar floflo#!/usr/bin/env python2.4 import os import os.path import sys from glob import glob from distutils.core import setup from emmalib import version icon_data = glob('icons/*.png') glade_data = ['emmalib/emma.glade', 'emmalib/plugins/table_editor/table_editor.glade'] theme_data = ["theme/README.html"] theme_gtk_data = glob("theme/gtk-2.0/*") other_data = ['changelog'] setup(name="emma", version=version, description="emma is the extendable mysql managing assistant", author="Florian Schmidt", author_email="flo@fastflo.de", url="http://emma.sourceforge.net", scripts=['emma'], package_dir={'emmalib': 'emmalib'}, packages=[ 'emmalib', 'emmalib.plugins.table_editor', 'emmalib.plugins.pretty_format' ], data_files=[ ("share/emma/icons", icon_data), ("share/emma/glade", glade_data), ("share/emma/theme", theme_data), ("share/emma/theme/gtk-2.0", theme_gtk_data), ("share/emma", other_data), ], license="GPL", long_description=""" Emma is a graphical toolkit for MySQL database developers and administrators It provides dialogs to create or modify mysql databases, tables and associated indexes. it has a built-in syntax highlighting sql editor with table- and fieldname tab-completion and automatic sql statement formatting. the results of an executed query are displayed in a resultset where the record- data can be edited by the user, if the sql statemant allows for it. the sql editor and resultset-view are grouped in tabs. results can be exported to csv files. multiple simultanios opend mysql connections are possible. Emma is the successor of yamysqlfront. """ ) emma-0.6/PKG-INFO0000644000175000017500000000167310526165411011525 0ustar flofloMetadata-Version: 1.0 Name: emma Version: 0.6 Summary: emma is the extendable mysql managing assistant Home-page: http://emma.sourceforge.net Author: Florian Schmidt Author-email: flo@fastflo.de License: GPL Description: Emma is a graphical toolkit for MySQL database developers and administrators It provides dialogs to create or modify mysql databases, tables and associated indexes. it has a built-in syntax highlighting sql editor with table- and fieldname tab-completion and automatic sql statement formatting. the results of an executed query are displayed in a resultset where the record- data can be edited by the user, if the sql statemant allows for it. the sql editor and resultset-view are grouped in tabs. results can be exported to csv files. multiple simultanios opend mysql connections are possible. Emma is the successor of yamysqlfront. Platform: UNKNOWN