From 90d7a32e66e5e86caca8c1c43f72783ebbcb9502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Sun, 13 Mar 2011 11:36:25 +0100 Subject: [PATCH] Added a connection manager like gajim --- CHANGELOG | 1 + tryton/common/common.py | 85 ++++- tryton/gui/main.py | 6 +- tryton/gui/window/dbcreate.py | 65 ++-- tryton/gui/window/dbdumpdrop.py | 57 ++-- tryton/gui/window/dblogin.py | 584 +++++++++++++++++++++++--------- tryton/gui/window/dbrestore.py | 11 +- tryton/rpc.py | 9 +- 8 files changed, 578 insertions(+), 240 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4cde9c2dfe..65fb7251b3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Added a connection manager à la gajim * Fix warning in wizards * Added possibility to use server-side icons * Added additional gtk.Entry for filename on BinaryField diff --git a/tryton/common/common.py b/tryton/common/common.py index 64c62bdcff..9190b73c02 100644 --- a/tryton/common/common.py +++ b/tryton/common/common.py @@ -21,6 +21,7 @@ except ImportError: import md5 import webbrowser import traceback +import threading import tryton.rpc as rpc import locale import socket @@ -35,7 +36,7 @@ try: except ImportError: ssl = None import dis -from threading import Lock +from threading import Lock, Semaphore _ = gettext.gettext @@ -126,7 +127,11 @@ def find_in_path(name): return val return name -def refresh_dblist(db_widget, host, port, dbtoload=None): +def false_call(function, *args): + function(*args) + return None + +def refresh_dblist(host, port): ''' Return the number of database available or None if it is impossible to connect @@ -137,22 +142,7 @@ def refresh_dblist(db_widget, host, port, dbtoload=None): if hasattr(version, 'split'): if version.split('.')[:2] != VERSION.split('.')[:2]: return -1 - if not dbtoload: - dbtoload = CONFIG['login.db'] - index = 0 - liststore = db_widget.get_model() - liststore.clear() - result = rpc.db_list(host, port) - from tryton.gui.main import Main - Main.get_main().refresh_ssl() - if result is None: - return None - for db_num, dbname in enumerate(result): - liststore.append([dbname]) - if dbname == dbtoload: - index = db_num - db_widget.set_active(index) - return len(liststore) + return rpc.db_list(host, port) def refresh_langlist(lang_widget, host, port): liststore = lang_widget.get_model() @@ -1030,6 +1020,65 @@ def generateColorscheme(masterColor, keys, light=0.06): for i, key in enumerate(keys)]) +class DBProgress(object): + + def __init__(self, host, port): + self.dbs, self.createdb = None, None + self.host, self.port = host, port + self.updated = threading.Event() + self.db_info = None + + def start(self): + key = (self.host, self.port) + try: + dbs = refresh_dblist(self.host, self.port) + createdb = True + except Exception, exception: + if exception[0] == 'AccessDenied': + dbs, createdb = [], False + else: + raise + self.db_info = (dbs, createdb) + self.updated.set() + + def update(self, combo, progressbar, dbname=''): + key = (self.host, self.port) + self.db_info = None + thread = threading.Thread(target=self.start).start() + + i = 0 + while not self.updated.isSet(): + i += 1 + time.sleep(0.1) + if i >= 10: + progressbar.show() + progressbar.pulse() + while gtk.events_pending(): + gtk.main_iteration() + progressbar.hide() + dbs, createdb = self.db_info + + if dbs is None: + return None, False + elif dbs == -1: + return -1, False + + from tryton.gui.main import Main + Main.get_main().refresh_ssl() + liststore = combo.get_model() + liststore.clear() + index = -1 + for db_num, db_name in enumerate(dbs): + liststore.append([db_name]) + if db_name == dbname: + index = db_num + if index == -1: + index = 0 + combo.set_active(index) + + return len(dbs), createdb + + class RPCProgress(object): def __init__(self, method, args, parent): diff --git a/tryton/gui/main.py b/tryton/gui/main.py index 1816897afa..5999c8a83d 100644 --- a/tryton/gui/main.py +++ b/tryton/gui/main.py @@ -1145,13 +1145,13 @@ class Main(object): raise return ([], []) - def sig_login(self, widget=None, dbname=False, res=None): + def sig_login(self, widget=None, profile_name=False, res=None): if not self.sig_logout(widget, disconnect=False): return if not res: try: dblogin = DBLogin(self.window) - res = dblogin.run(dbname, self.window) + res = dblogin.run(profile_name, self.window) except Exception, exception: if exception.args == ('QueryCanceled',): return False @@ -1513,7 +1513,7 @@ class Main(object): def sig_db_new(self, widget): if not self.sig_logout(widget): return False - dia = DBCreate(self.sig_login) + dia = DBCreate(sig_login=self.sig_login) res = dia.run(self.window) if res: CONFIG.save() diff --git a/tryton/gui/window/dbcreate.py b/tryton/gui/window/dbcreate.py index 11a7179e8c..ca69c12d23 100644 --- a/tryton/gui/window/dbcreate.py +++ b/tryton/gui/window/dbcreate.py @@ -1,13 +1,14 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. from __future__ import with_statement - +import ConfigParser +import os import gtk import gobject import gettext import re import tryton.common as common -from tryton.config import CONFIG, TRYTON_ICON, PIXMAPS_DIR +from tryton.config import CONFIG, TRYTON_ICON, PIXMAPS_DIR, get_config_dir import tryton.rpc as rpc _ = gettext.gettext @@ -72,8 +73,6 @@ class DBCreate(object): except Exception: self.server_connection_state(False) return False - CONFIG['login.server'] = host - CONFIG['login.port'] = port return True def event_passwd_clear(self, widget, event, data=None): @@ -131,10 +130,11 @@ class DBCreate(object): gobject.idle_add(_move_cursor) entry.stop_emission("insert-text") - def __init__(self, sig_login=None): - """ - This method defines the complete GUI. - """ + def __init__(self, host=None, port=None, sig_login=None): + self.host = host + self.port = port + + # GTK Stuffs self.dialog = gtk.Dialog( title= _("Create new database"), flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT @@ -178,7 +178,6 @@ class DBCreate(object): self.entry_server_connection.set_sensitive(False) self.entry_server_connection.unset_flags(gtk.CAN_FOCUS) self.entry_server_connection.set_editable(False) - table.attach(self.entry_server_connection, 1, 2, 1, 2) self.tooltips.set_tip(self.entry_server_connection, _("This is the URL of " \ "the server. Use server 'localhost' and port '8070' if " \ "the server is installed on this computer. Click on 'Change' to " \ @@ -192,6 +191,10 @@ class DBCreate(object): table.attach(self.button_server_change, 2, 3, 1, 2, yoptions=False, xoptions=gtk.FILL) self.tooltips.set_tip(self.button_server_change, _("Setup the " \ "server connection...")) + self.button_server_change.set_sensitive(not bool(self.host + and self.port)) + + table.attach(self.entry_server_connection, 1, 2, 1, 2) self.label_serverpasswd = gtk.Label(_("Tryton Server Password:")) self.label_serverpasswd.set_justify(gtk.JUSTIFY_RIGHT) self.label_serverpasswd.set_alignment(1, 0.5) @@ -293,30 +296,22 @@ class DBCreate(object): self.dialog.set_transient_for(parent) self.dialog.show_all() - if not CONFIG['login.host']: - self.label_server_setup.hide() - self.label_server.hide() - self.entry_server_connection.hide() - self.button_server_change.hide() - self.label_serverpasswd.hide() - self.entry_serverpasswd.hide() - self.hseparator.hide() - pass_widget = self.entry_serverpasswd change_button = self.button_server_change admin_passwd = self.entry_adminpasswd admin_passwd2 = self.entry_adminpasswd2 - change_button.connect_after('clicked', self.server_change, self.dialog) - host = CONFIG['login.server'] - port = int(CONFIG['login.port']) - url = '%s:%d' % (host, port) + + if self.host and self.port: + url = '%s:%d' % (self.host, self.port) + else: + url = '' self.entry_server_connection.set_text(url) liststore = gtk.ListStore(str, str) self.combo_language.set_model(liststore) try: - common.refresh_langlist(self.combo_language, host, port) + common.refresh_langlist(self.combo_language, self.host, self.port) except Exception: self.button_create.set_sensitive(False) @@ -372,9 +367,8 @@ class DBCreate(object): self.entry_dbname.grab_focus() continue else: # Everything runs fine, break the block here - CONFIG['login.server'] = host = url_m.group(1) - CONFIG['login.port'] = port = url_m.group(2) - CONFIG["login.login"] = "admin" + host = url_m.group(1) + port = url_m.group(2) try: rpcprogress = common.RPCProgress('db_exec', (host, int(port), 'create', dbname, passwd, @@ -409,10 +403,27 @@ class DBCreate(object): parent.present() self.dialog.destroy() if self.sig_login: - self.sig_login(dbname=dbname) + profile_cfg = os.path.join(get_config_dir(), + 'profiles.cfg') + profiles = ConfigParser.SafeConfigParser() + if os.path.exists(profile_cfg): + profiles.read(profile_cfg) + i, profile_name = 0, dbname + while profile_name in profiles.sections(): + i += 1 + profile_name = '%s_%s' % (dbname, i) + profiles.add_section(profile_name) + profiles.set(profile_name, 'host', host) + profiles.set(profile_name, 'port', port) + profiles.set(profile_name, 'database', dbname) + profiles.set(profile_name, 'username', '') + with open(profile_cfg, 'wb') as configfile: + profiles.write(configfile) + self.sig_login(profile_name=profile_name) break break parent.present() self.dialog.destroy() + return dbname diff --git a/tryton/gui/window/dbdumpdrop.py b/tryton/gui/window/dbdumpdrop.py index e904edfbab..6749e29015 100644 --- a/tryton/gui/window/dbdumpdrop.py +++ b/tryton/gui/window/dbdumpdrop.py @@ -1,5 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of this # repository contains the full copyright notices and license terms. +import threading + import gtk import gettext import tryton.common as common @@ -13,19 +15,22 @@ class DBBackupDrop(object): Widget for database backup and drop. """ @staticmethod - def refreshlist(widget, db_widget, label, host, port): - res = common.refresh_dblist(db_widget, host, port) - if res is None or res == -1: - if res is None: + def refreshlist(widget, db_widget, label, db_progress, host, port): + db_widget.hide() + label.hide() + dbprogress = common.DBProgress(host, port) + dbs, createdb = dbprogress.update(db_widget, db_progress) + if dbs is None or dbs == -1: + if dbs is None: label.set_label('' + \ _('Could not connect to server!') + '') else: label.set_label('' + \ - _('This client version is not compatible with the server!') +\ - '') + _('This client version is not compatible with the server!') + + '') db_widget.hide() label.show() - elif res == 0: + elif dbs == 0: label.set_label('' + \ _('No database found, you must create one!') + '') db_widget.hide() @@ -33,17 +38,17 @@ class DBBackupDrop(object): else: label.hide() db_widget.show() - return res + return dbs @staticmethod - def refreshlist_ask(widget, server_widget, db_widget, label, parent=None): + def refreshlist_ask(widget, server_widget, db_widget, label, db_progress, + parent=None): res = common.request_server(server_widget, parent) if not res: return None host, port = res - if DBBackupDrop.refreshlist(widget, db_widget, label, host, port): - CONFIG['login.server'] = host - CONFIG['login.port'] = port + DBBackupDrop.refreshlist(widget, db_widget, label, db_progress, host, + port) return (host, port) def event_show_button_ok(self, widget, event, data=None): @@ -157,11 +162,18 @@ class DBBackupDrop(object): vbox_combo = gtk.VBox() self.combo_database = gtk.ComboBox() + self.db_progressbar = gtk.ProgressBar() self.combo_database_label = gtk.Label() self.combo_database_label.set_use_markup(True) self.combo_database_label.set_alignment(0, 1) - vbox_combo.pack_start(self.combo_database, True, True, 0) - vbox_combo.pack_start(self.combo_database_label, False, False, 0) + vbox_combo.pack_start(self.combo_database, True, True) + vbox_combo.pack_start(self.combo_database_label, False, False) + vbox_combo.pack_start(self.db_progressbar, True, True) + width, height = 0, 0 + for child in vbox_combo.get_children(): + cwidth, cheight = child.size_request() + width, height = max(width, cwidth), max(height, cheight) + vbox_combo.set_size_request(width, height) table.attach(vbox_combo, 1, 3, 3, 4, yoptions=gtk.FILL) self.label_serverpasswd = gtk.Label(_("Tryton Server Password:")) @@ -188,33 +200,24 @@ class DBBackupDrop(object): self.dialog.set_transient_for(parent) self.dialog.show_all() - if not CONFIG['login.host']: - self.label_server.hide() - self.entry_server_connection.hide() - self.button_server_change.hide() - self.label_serverpasswd.hide() - self.entry_serverpasswd.hide() - pass_widget = self.entry_serverpasswd server_widget = self.entry_server_connection db_widget = self.combo_database + db_progress = self.db_progressbar label = self.combo_database_label - host = CONFIG['login.server'] - port = int(CONFIG['login.port']) - url = '%s:%d' % (host, port) - server_widget.set_text(url) + server_widget.set_text('') liststore = gtk.ListStore(str) db_widget.set_model(liststore) cell = gtk.CellRendererText() db_widget.pack_start(cell, True) db_widget.add_attribute(cell, 'text', 0) - res = self.refreshlist(None, db_widget, label, host, port) + res = self.refreshlist(None, db_widget, label, db_progress, '', '') change_button = self.button_server_change change_button.connect_after('clicked', DBBackupDrop.refreshlist_ask, \ - server_widget, db_widget, label, self.dialog) + server_widget, db_widget, label, db_progress, self.dialog) while True: database = False diff --git a/tryton/gui/window/dblogin.py b/tryton/gui/window/dblogin.py index 1816323871..65abe999f4 100644 --- a/tryton/gui/window/dblogin.py +++ b/tryton/gui/window/dblogin.py @@ -1,19 +1,351 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. +from __future__ import with_statement + +import ConfigParser import gtk +import gobject import os import re import gettext +import threading +import time + +from tryton.version import VERSION import tryton.common as common -from tryton.config import CONFIG, TRYTON_ICON, PIXMAPS_DIR +from tryton.config import CONFIG, TRYTON_ICON, PIXMAPS_DIR, get_config_dir import tryton.rpc as rpc from tryton.gui.window.dbcreate import DBCreate _ = gettext.gettext +class DBListEditor(object): + + def __init__(self, parent, profile_store, profiles): + self.profiles = profiles + self.current_database = None + self.old_profile, self.current_profile = None, None + self.updating_db = False + + # GTK Stuffs + self.parent = parent + self.dialog = gtk.Dialog(title=_(u'Profile Editor'), parent=parent, + flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT) + self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, + gtk.RESPONSE_CANCEL) + self.ok_button = self.dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) + self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.dialog.set_has_separator(True) + self.dialog.set_icon(TRYTON_ICON) + + hpaned = gtk.HPaned() + vbox_profiles = gtk.VBox(homogeneous=False, spacing=6) + self.cell = gtk.CellRendererText() + self.cell.set_property('editable', True) + self.cell.connect('edited', self.edit_profilename) + self.cell.connect('editing-started', self.edit_started) + self.cell.connect('editing-canceled', self.edit_canceled) + self.profile_tree = gtk.TreeView() + self.profile_tree.set_model(profile_store) + self.profile_tree.insert_column_with_attributes(-1, _(u'Profile'), + self.cell, text=0) + self.profile_tree.connect('cursor-changed', self.profile_selected) + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scroll.add(self.profile_tree) + self.add_button = gtk.Button(_(u'_Add')) + self.add_button.connect('clicked', self.profile_create) + add_image = gtk.Image() + add_image.set_from_stock('gtk-add', gtk.ICON_SIZE_BUTTON) + self.add_button.set_image(add_image) + self.remove_button = gtk.Button(_(u'_Remove')) + self.remove_button.connect('clicked', self.profile_delete) + remove_image = gtk.Image() + remove_image.set_from_stock('gtk-remove', gtk.ICON_SIZE_BUTTON) + self.remove_button.set_image(remove_image) + vbox_profiles.pack_start(scroll, expand=True, fill=True) + vbox_profiles.pack_start(self.add_button, expand=False, fill=True) + vbox_profiles.pack_start(self.remove_button, expand=False, fill=True) + hpaned.add1(vbox_profiles) + + table = gtk.Table(4, 2, homogeneous=False) + table.set_row_spacings(3) + table.set_col_spacings(3) + host = gtk.Label(_(u'Hostname:')) + host.set_alignment(1, 0.5) + host.set_padding(3, 3) + self.host_entry = gtk.Entry() + self.host_entry.connect('focus-out-event', self.display_dbwidget) + self.host_entry.connect('changed', self.update_profiles, 'host') + self.host_entry.set_activates_default(True) + table.attach(host, 0, 1, 1, 2, yoptions=False, xoptions=gtk.FILL) + table.attach(self.host_entry, 1, 2, 1, 2, yoptions=False) + port = gtk.Label(_(u'Port:')) + port.set_alignment(1, 0.5) + port.set_padding(3, 3) + self.port_entry = gtk.Entry() + self.port_entry.connect('focus-out-event', self.display_dbwidget) + self.port_entry.connect('changed', self.update_profiles, 'port') + self.port_entry.set_activates_default(True) + table.attach(port, 0, 1, 2, 3, yoptions=False, xoptions=gtk.FILL) + table.attach(self.port_entry, 1, 2, 2, 3, yoptions=False) + database = gtk.Label(_(u'Database:')) + database.set_alignment(1, 0.5) + database.set_padding(3, 3) + self.database_entry = gtk.Entry() + self.database_entry.connect('changed', self.dbentry_changed) + self.database_entry.connect('changed', self.update_profiles, 'database') + self.database_entry.set_activates_default(True) + self.database_label = gtk.Label() + self.database_label.set_use_markup(True) + self.database_label.set_alignment(0, 0.5) + self.database_combo = gtk.ComboBox() + dbstore = gtk.ListStore(gobject.TYPE_STRING) + cell = gtk.CellRendererText() + self.database_combo.pack_start(cell, True) + self.database_combo.add_attribute(cell, 'text', 0) + self.database_combo.set_model(dbstore) + self.database_combo.connect('changed', self.dbcombo_changed) + self.database_button = gtk.Button(_(u'Create')) + self.database_button.connect('clicked', self.db_create) + self.database_progressbar= gtk.ProgressBar() + self.database_progressbar.set_text(_(u'Fetching databases list')) + image = gtk.Image() + image.set_from_stock('tryton-new', gtk.ICON_SIZE_BUTTON) + self.database_button.set_image(image) + db_box = gtk.VBox(homogeneous=True) + db_box.pack_start(self.database_entry) + db_box.pack_start(self.database_combo) + db_box.pack_start(self.database_label) + db_box.pack_start(self.database_button) + db_box.pack_start(self.database_progressbar) + # Compute size_request of box in order to prevent "form jumping" + width, height = 0, 0 + for child in db_box.get_children(): + cwidth, cheight = child.size_request() + width, height = max(width, cwidth), max(height, cheight) + db_box.set_size_request(width, height) + table.attach(database, 0, 1, 3, 4, yoptions=False, xoptions=gtk.FILL) + table.attach(db_box, 1, 2, 3, 4, yoptions=False) + username = gtk.Label(_(u'Username:')) + username.set_alignment(1, 0.5) + username.set_padding(3, 3) + self.username_entry = gtk.Entry() + self.username_entry.connect('changed', self.update_profiles, 'username') + self.username_entry.set_activates_default(True) + table.attach(username, 0, 1, 4, 5, yoptions=False, xoptions=gtk.FILL) + table.attach(self.username_entry, 1, 2, 4, 5, yoptions=False) + hpaned.add2(table) + hpaned.set_position(250) + + self.dialog.vbox.pack_start(hpaned) + self.dialog.set_default_size(640, 350) + self.dialog.set_default_response(gtk.RESPONSE_ACCEPT) + + def run(self): + self.dialog.show_all() + self.clear_entries() + model = self.profile_tree.get_model() + if model: + self.profile_tree.get_selection().select_path((0,)) + self.profile_selected(self.profile_tree) + self.dialog.run() + self.parent.present() + self.dialog.destroy() + + def _current_profile(self): + model, selection = self.profile_tree.get_selection().get_selected() + if not selection: + return {'name': None, 'iter': None} + return {'name': model[selection][0], 'iter': selection} + + def clear_entries(self): + for entryname in ('host', 'port', 'database', 'username'): + entry = getattr(self, '%s_entry' % entryname) + if entryname == 'port': + entry.set_text('8070') + else: + entry.set_text('') + self.current_database = None + self.database_combo.set_active(-1) + self.database_combo.get_model().clear() + self.hide_database_info() + + def hide_database_info(self): + self.database_entry.hide() + self.database_combo.hide() + self.database_label.hide() + self.database_button.hide() + self.database_progressbar.hide() + + def profile_create(self, button): + self.clear_entries() + model = self.profile_tree.get_model() + selection = self.profile_tree.get_selection() + model.append(['', False]) + column = self.profile_tree.get_column(0) + self.profile_tree.set_cursor(len(model)-1, column, start_editing=True) + + def profile_delete(self, button): + self.clear_entries() + model, selection = self.profile_tree.get_selection().get_selected() + if not selection: + return + profile_name = model[selection][0] + self.profiles.remove_section(profile_name) + del model[selection] + + def profile_selected(self, treeview): + self.old_profile = self.current_profile + self.current_profile = self._current_profile() + if not self.current_profile['name']: + return + if self.updating_db: + self.current_profile = self.old_profile + selection = treeview.get_selection() + selection.select_iter(self.old_profile['iter']) + return + self.clear_entries() + fields = ('host', 'port', 'database', 'username') + for field in fields: + entry = getattr(self, '%s_entry' % field) + try: + entry_value = self.profiles.get(self.current_profile['name'], + field) + entry.set_text(entry_value) + if field == 'database': + self.current_database = entry_value + except ConfigParser.NoOptionError: + pass + + self.display_dbwidget(None, None, self.current_database) + + def edit_canceled(self, renderer): + model = self.profile_tree.get_model() + for i, row in enumerate(list(model)): + if not row[0]: + del model[i] + + def check_edit_cancel(self, editable, event, renderer, path): + renderer.emit('edited', path, editable.get_text()) + + def edit_started(self, renderer, editable, path): + if isinstance(editable, gtk.Entry): + editable.connect('focus-out-event', self.check_edit_cancel, + renderer, path) + + def edit_profilename(self, renderer, path, newtext): + model = self.profile_tree.get_model() + oldname = model[path][0] + if oldname == newtext == '': + del model[path] + return + elif oldname == newtext or newtext == '': + return + if newtext in self.profiles.sections(): + del model[path] + return + elif oldname in self.profiles.sections(): + self.profiles.add_section(newtext) + for itemname, value in self.profiles.items(oldname): + self.profiles.set(newtext, itemname, value) + self.profiles.remove_section(oldname) + model[path][0] = newtext + else: + model[path][0] = newtext + self.profiles.add_section(newtext) + self.current_profile = self._current_profile() + self.host_entry.grab_focus() + + def update_profiles(self, editable, entryname): + new_value = editable.get_text() + if not new_value: + return + section = self._current_profile()['name'] + self.profiles.set(section, entryname, new_value) + self.validate_profile(section) + + def validate_profile(self, profile_name): + model, selection = self.profile_tree.get_selection().get_selected() + if not selection: + return + active = all(self.profiles.has_option(profile_name, option) + for option in ('host', 'port', 'database')) + model[selection][1] = active + + def display_dbwidget(self, entry, event, dbname=None): + host = self.host_entry.get_text() + port = self.port_entry.get_text() + if not (host and port): + return + if dbname is None: + dbname = self.current_database + + dbprogress = common.DBProgress(host, int(port)) + self.hide_database_info() + self.add_button.set_sensitive(False) + self.remove_button.set_sensitive(False) + self.ok_button.set_sensitive(False) + self.cancel_button.set_sensitive(False) + self.cell.set_property('editable', False) + self.updating_db = True + dbs, createdb = dbprogress.update(self.database_combo, + self.database_progressbar, dbname) + self.updating_db = False + + if dbs is None and createdb is None: + pass + elif dbs is None or dbs == -1: + if dbs is None: + label = _(u'Could not connect to the server') + else: + label = _(u'Incompatible version of the server') + self.database_label.set_label('%s' % label) + self.database_label.show() + elif dbs == 0: + if createdb: + self.database_button.show() + else: + self.database_entry.show() + else: + self.database_entry.set_text(dbname if dbname else '') + self.database_combo.show() + + self.add_button.set_sensitive(True) + self.remove_button.set_sensitive(True) + self.ok_button.set_sensitive(True) + self.cancel_button.set_sensitive(True) + self.cell.set_property('editable', True) + + def db_create(self, button): + if not self.current_profile['name']: + return + host = self.host_entry.get_text() + port = int(self.port_entry.get_text()) + dia = DBCreate(host, port) + dbname = dia.run(self.dialog) + self.username_entry.set_text('admin') + self.display_dbwidget(None, None, dbname) + + def dbcombo_changed(self, combobox): + dbname = combobox.get_active_text() + if dbname: + self.current_database = dbname + self.profiles.set(self.current_profile['name'], 'database', dbname) + self.validate_profile(self.current_profile['name']) + + def dbentry_changed(self, entry): + dbname = entry.get_text() + if dbname: + self.current_database = dbname + self.profiles.set(self.current_profile['name'], 'database', dbname) + self.validate_profile(self.current_profile['name']) + + class DBLogin(object): def __init__(self, parent): + # GTK Stuffs self.dialog = gtk.Dialog(title=_('Login'), parent=parent, flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT) self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) @@ -25,7 +357,8 @@ class DBLogin(object): img_cancel = gtk.Image() img_cancel.set_from_stock('tryton-cancel', gtk.ICON_SIZE_BUTTON) button_cancel.set_image(img_cancel) - tooltips.set_tip(button_cancel, _('Cancel connection to the Tryton server')) + tooltips.set_tip(button_cancel, + _('Cancel connection to the Tryton server')) self.dialog.add_action_widget(button_cancel, gtk.RESPONSE_CANCEL) self.button_connect = gtk.Button(_('C_onnect')) img_connect = gtk.Image() @@ -42,179 +375,126 @@ class DBLogin(object): ebox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#1b2019")) ebox.add(image) self.dialog.vbox.pack_start(ebox) - table_main = gtk.Table(4, 3, False) - table_main.set_border_width(10) - table_main.set_row_spacings(3) - table_main.set_col_spacings(3) - self.dialog.vbox.pack_start(table_main, True, True, 0) - vbox_combo = gtk.VBox() - self.hbox_combo = gtk.HBox() - self.combo_database = gtk.ComboBox() - self.combo_label = gtk.Label() - self.combo_label.set_use_markup(True) - self.combo_label.set_alignment(0, 0.5) - self.combo_button = gtk.Button(_('C_reate')) - self.combo_button.connect('clicked', self.db_create) + self.table_main = gtk.Table(3, 3, False) + self.table_main.set_border_width(10) + self.table_main.set_row_spacings(3) + self.table_main.set_col_spacings(3) + self.dialog.vbox.pack_start(self.table_main, True, True, 0) + self.profile_store = gtk.ListStore(gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN) + self.combo_profile = gtk.ComboBox() + cell = gtk.CellRendererText() + self.combo_profile.pack_start(cell, True) + self.combo_profile.add_attribute(cell, 'text', 0) + self.combo_profile.add_attribute(cell, 'sensitive', 1) + self.combo_profile.set_model(self.profile_store) + self.combo_profile.connect('changed', self.profile_changed) + self.profile_label = gtk.Label(_(u'Profile:')) + self.profile_label.set_justify(gtk.JUSTIFY_RIGHT) + self.profile_label.set_alignment(1, 0.5) + self.profile_label.set_padding(3, 3) + self.profile_button = gtk.Button(_('_Manage profiles')) + self.profile_button.connect('clicked', self.profile_manage) + self.table_main.attach(self.profile_label, 0, 1, 0, 1, + xoptions=gtk.FILL) + self.table_main.attach(self.combo_profile, 1, 2, 0, 1) + self.table_main.attach(self.profile_button, 2, 3, 0, 1, + xoptions=gtk.FILL) image = gtk.Image() - image.set_from_stock('tryton-new', gtk.ICON_SIZE_BUTTON) - self.combo_button.set_image(image) - vbox_combo.pack_start(self.combo_database, True, True, 0) - self.hbox_combo.pack_start(self.combo_label, True, True, 0) - self.hbox_combo.pack_start(self.combo_button, False, False, 0) - vbox_combo.pack_start(self.hbox_combo, True, True, 0) - table_main.attach(vbox_combo, 1, 3, 1, 2) + image.set_from_stock('gtk-edit', gtk.ICON_SIZE_BUTTON) + self.profile_button.set_image(image) self.entry_password = gtk.Entry() self.entry_password.set_visibility(False) self.entry_password.set_activates_default(True) - table_main.attach(self.entry_password, 1, 3, 3, 4) + self.table_main.attach(self.entry_password, 1, 3, 3, 4) self.entry_login = gtk.Entry() self.entry_login.set_activates_default(True) - table_main.attach(self.entry_login, 1, 3, 2, 3) - self.label_server = gtk.Label() - self.label_server.set_text(_("Server:")) - self.label_server.set_alignment(1, 0.5) - self.label_server.set_padding(3, 3) - table_main.attach(self.label_server, 0, 1, 0, 1, xoptions=gtk.FILL) - label_database = gtk.Label() - label_database.set_text(_("Database:")) - label_database.set_alignment(1, 0.5) - label_database.set_padding(3, 3) - table_main.attach(label_database, 0, 1, 1, 2, xoptions=gtk.FILL) - self.entry_server = gtk.Entry() - table_main.attach(self.entry_server, 1, 2, 0, 1) - self.entry_server.set_sensitive(False) - self.entry_server.unset_flags(gtk.CAN_FOCUS) - self.entry_server.set_editable(False) - self.entry_server.set_text("localhost") - self.entry_server.set_activates_default(True) - self.entry_server.set_width_chars(16) - self.button_server = gtk.Button(label=_("C_hange"), stock=None, - use_underline=True) - tooltips.set_tip(self.button_server, - _('Configure the Tryton server connection')) - table_main.attach(self.button_server, 2, 3, 0, 1, xoptions=gtk.FILL) + self.table_main.attach(self.entry_login, 1, 3, 2, 3) label_password = gtk.Label(str = _("Password:")) label_password.set_justify(gtk.JUSTIFY_RIGHT) label_password.set_alignment(1, 0.5) label_password.set_padding(3, 3) - table_main.attach(label_password, 0, 1, 3, 4, xoptions=gtk.FILL) + self.table_main.attach(label_password, 0, 1, 3, 4, xoptions=gtk.FILL) label_username = gtk.Label(str = _("User name:")) label_username.set_alignment(1, 0.5) label_username.set_padding(3, 3) - table_main.attach(label_username, 0, 1, 2, 3, xoptions=gtk.FILL) + self.table_main.attach(label_username, 0, 1, 2, 3, xoptions=gtk.FILL) self.entry_password.grab_focus() - @staticmethod - def refreshlist(widget, db_widget, label, button, host, port, - butconnect=None): - res = common.refresh_dblist(db_widget, host, port) - if res is None or res == -1: - if res is None: - label.set_label('' + _('Could not connect to server!') + \ - '') - else: - label.set_label('' + \ - _('Incompatible version of the server!') + '') - db_widget.hide() - label.show() - button.hide() - if butconnect: - butconnect.set_sensitive(False) - elif res == 0: - label.set_label('' + \ - _('No database found, you must create one!') + '') - db_widget.hide() - label.show() - button.show() - if butconnect: - butconnect.set_sensitive(False) + # Profile informations + self.profile_cfg = os.path.join(get_config_dir(), 'profiles.cfg') + self.profiles = ConfigParser.SafeConfigParser({'port': '8070'}) + if not os.path.exists(self.profile_cfg): + short_version = '.'.join(VERSION.split('.', 2)[:2]) + name = 'demo%s.tryton.org' % short_version + self.profiles.add_section(name) + self.profiles.set(name, 'host', name) + self.profiles.set(name, 'port', '8070') + self.profiles.set(name, 'database', 'demo%s' % short_version) + self.profiles.set(name, 'username', 'demo') else: - label.hide() - button.hide() - db_widget.show() - if butconnect: - butconnect.set_sensitive(True) - return res + self.profiles.read(self.profile_cfg) + for section in self.profiles.sections(): + active = all(self.profiles.has_option(section, option) + for option in ('host', 'port', 'database')) + self.profile_store.append([section, active]) - @staticmethod - def refreshlist_ask(widget, server_widget, db_widget, label, button, - butconnect=False, host=False, port=0, parent=None): - res = common.request_server(server_widget, parent) or (host, port) - if not res: - return False - host, port = res - res = DBLogin.refreshlist(widget, db_widget, label, button, host, port, - butconnect) - if res: - CONFIG['login.server'] = host - CONFIG['login.port'] = port - return res + def profile_manage(self, widget): + dia = DBListEditor(self.dialog, self.profile_store, self.profiles) + dia.run() + with open(self.profile_cfg, 'wb') as configfile: + self.profiles.write(configfile) - def db_create(self, widget): - dia = DBCreate() - dia.run(self.dialog) - host = CONFIG['login.server'] - port = int(CONFIG['login.port']) - DBLogin.refreshlist(None, self.combo_database, - self.combo_label, self.combo_button, host, port, - self.button_connect) - return + def profile_changed(self, combobox): + position = combobox.get_active() + if position == -1: + return + profile = self.profile_store[position][0] + try: + username = self.profiles.get(profile, 'username') + except ConfigParser.NoOptionError: + username = '' + if username: + self.entry_login.set_text(username) + self.entry_password.grab_focus() + else: + self.entry_login.set_text('') + self.entry_login.grab_focus() - def run(self, dbname, parent): + def run(self, profile_name, parent): + if not profile_name: + selected_profile = CONFIG['login.profile'] + if not selected_profile: + short_version = '.'.join(VERSION.split('.', 2)[:2]) + selected_profile = 'demo%s.tryton.org' % short_version + else: + selected_profile = profile_name + for idx, row in enumerate(self.profile_store): + if row[0] == selected_profile: + self.combo_profile.set_active(idx) + break self.dialog.show_all() - self.combo_label.hide() - self.combo_button.hide() - - if not CONFIG['login.host']: - self.label_server.hide() - self.entry_server.hide() - self.button_server.hide() - - host = CONFIG['login.server'] - port = int(CONFIG['login.port']) - - url = '%s:%d' % (host, port) - self.entry_server.set_text(url) - self.entry_login.set_text(CONFIG['login.login']) - - # construct the list of available db and select the last one used - liststore = gtk.ListStore(str) - self.combo_database.set_model(liststore) - cell = gtk.CellRendererText() - self.combo_database.pack_start(cell, True) - self.combo_database.add_attribute(cell, 'text', 0) - - res = self.refreshlist(None, self.combo_database, self.combo_label, - self.combo_button, host, port, self.button_connect) - - self.button_server.connect_after('clicked', DBLogin.refreshlist_ask, - self.entry_server, self.combo_database, self.combo_label, - self.combo_button, self.button_connect, host, port, self.dialog) - if dbname: - i = liststore.get_iter_root() - while i: - if liststore.get_value(i, 0)==dbname: - self.combo_database.set_active_iter(i) - break - i = liststore.iter_next(i) # Reshow dialog for gtk-quarks self.dialog.reshow_with_initial_size() - res = self.dialog.run() - url_m = re.match('^([\w.:\-\d]+):(\d{1,5})$', - self.entry_server.get_text() or '') - if url_m: - CONFIG['login.server'] = url_m.group(1) - CONFIG['login.login'] = self.entry_login.get_text() - CONFIG['login.port'] = url_m.group(2) - CONFIG['login.db'] = self.combo_database.get_active_text() - result = (self.entry_login.get_text(), - self.entry_password.get_text(), url_m.group(1), - int(url_m.group(2)), self.combo_database.get_active_text()) - else: - parent.present() - self.dialog.destroy() - raise Exception('QueryCanceled') + + res, result = None, ('', '', '', '', '') + while not (res in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT) + or (res == gtk.RESPONSE_OK and all(result))): + res = self.dialog.run() + active_profile = self.combo_profile.get_active() + if active_profile != -1: + profile = self.profile_store[active_profile][0] + CONFIG['login.profile'] = profile + try: + result = (self.entry_login.get_text(), + self.entry_password.get_text(), + self.profiles.get(profile, 'host'), + int(self.profiles.get(profile, 'port')), + self.profiles.get(profile, 'database')) + except ConfigParser.NoOptionError, e: + pass + if res != gtk.RESPONSE_OK: parent.present() self.dialog.destroy() diff --git a/tryton/gui/window/dbrestore.py b/tryton/gui/window/dbrestore.py index 07a54fc702..77ccdf83ae 100644 --- a/tryton/gui/window/dbrestore.py +++ b/tryton/gui/window/dbrestore.py @@ -173,16 +173,7 @@ class DBRestore(object): self.dialog.set_transient_for(parent) self.dialog.show_all() - if not CONFIG['login.host']: - self.label_server_url.hide() - self.entry_server_url.hide() - self.button_server_change.hide() - self.label_server_password.hide() - self.entry_server_password.hide() - self.hseparator.hide() - - url = '%s:%d' % (CONFIG['login.server'], int(CONFIG['login.port'])) - self.entry_server_url.set_text(url) + self.entry_server_url.set_text('') while True: database = False passwd = False diff --git a/tryton/rpc.py b/tryton/rpc.py index 7e14218a61..3368588eac 100644 --- a/tryton/rpc.py +++ b/tryton/rpc.py @@ -49,9 +49,12 @@ def db_list(host, port): _SEMAPHORE.release() logging.getLogger('rpc.result').debug(repr(res)) return res - except Exception: - logging.getLogger('rpc.result').debug(repr(None)) - return None + except Exception, exception: + if exception[0] == 'AccessDenied': + raise + else: + logging.getLogger('rpc.result').debug(repr(None)) + return None def db_exec(host, port, method, *args): global _SOCK, SECURE