Merge branch 'master' of git://git.assembla.com/fpdb-mme

Conflicts:
	pyfpdb/Configuration.py
This commit is contained in:
Worros 2009-11-03 17:25:54 +08:00
commit f276ad4dfa
3 changed files with 496 additions and 235 deletions

View File

@ -44,14 +44,31 @@ except ConfigParser.NoSectionError: # debian package path
log = logging.getLogger("config") log = logging.getLogger("config")
log.debug("config logger initialised") log.debug("config logger initialised")
########################################################################
# application wide consts
def fix_tf(x, default = True): APPLICATION_NAME_SHORT = 'fpdb'
# The xml parser doesn't translate "True" to True. Therefore, we never get APPLICATION_VERSION = 'xx.xx.xx'
# True or False from the parser only "True" or "False". So translate the
# string to the python boolean representation. DATABASE_TYPE_POSTGRESQL = 'postgresql'
if x == "1" or x == 1 or string.lower(x) == "true" or string.lower(x) == "t": DATABASE_TYPE_SQLITE = 'sqlite'
DATABASE_TYPE_MYSQL = 'mysql'
DATABASE_TYPES = (
DATABASE_TYPE_POSTGRESQL,
DATABASE_TYPE_SQLITE,
DATABASE_TYPE_MYSQL,
)
########################################################################
def string_to_bool(string, default=True):
"""converts a string representation of a boolean value to boolean True or False
@param string: (str) the string to convert
@param default: value to return if the string can not be converted to a boolean value
"""
string = string.lower()
if string in ('1', 'true', 't'):
return True return True
if x == "0" or x == 0 or string.lower(x) == "false" or string.lower(x) == "f": elif string in ('0', 'false', 'f'):
return False return False
return default return default
@ -106,7 +123,7 @@ class Site:
self.font = node.getAttribute("font") self.font = node.getAttribute("font")
self.font_size = node.getAttribute("font_size") self.font_size = node.getAttribute("font_size")
self.use_frames = node.getAttribute("use_frames") self.use_frames = node.getAttribute("use_frames")
self.enabled = fix_tf(node.getAttribute("enabled"), default = True) self.enabled = string_to_bool(node.getAttribute("enabled"), default=True)
self.xpad = node.getAttribute("xpad") self.xpad = node.getAttribute("xpad")
self.ypad = node.getAttribute("ypad") self.ypad = node.getAttribute("ypad")
self.layout = {} self.layout = {}
@ -213,7 +230,7 @@ class Database:
self.db_user = node.getAttribute("db_user") self.db_user = node.getAttribute("db_user")
self.db_type = node.getAttribute("db_type") self.db_type = node.getAttribute("db_type")
self.db_pass = node.getAttribute("db_pass") self.db_pass = node.getAttribute("db_pass")
self.db_selected = fix_tf(node.getAttribute("default"),"False") self.db_selected = string_to_bool(node.getAttribute("default"), default=False)
log.debug("Database db_name:'%(name)s' db_server:'%(server)s' db_ip:'%(ip)s' db_user:'%(user)s' db_type:'%(type)s' db_pass (not logged) selected:'%(sel)s'" \ log.debug("Database db_name:'%(name)s' db_server:'%(server)s' db_ip:'%(ip)s' db_user:'%(user)s' db_type:'%(type)s' db_pass (not logged) selected:'%(sel)s'" \
% { 'name':self.db_name, 'server':self.db_server, 'ip':self.db_ip, 'user':self.db_user, 'type':self.db_type, 'sel':self.db_selected} ) % { 'name':self.db_name, 'server':self.db_server, 'ip':self.db_ip, 'user':self.db_user, 'type':self.db_type, 'sel':self.db_selected} )
@ -277,8 +294,8 @@ class Import:
self.interval = node.getAttribute("interval") self.interval = node.getAttribute("interval")
self.callFpdbHud = node.getAttribute("callFpdbHud") self.callFpdbHud = node.getAttribute("callFpdbHud")
self.hhArchiveBase = node.getAttribute("hhArchiveBase") self.hhArchiveBase = node.getAttribute("hhArchiveBase")
self.saveActions = fix_tf(node.getAttribute("saveActions"), True) self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True)
self.fastStoreHudCache = fix_tf(node.getAttribute("fastStoreHudCache"), False) self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False)
def __str__(self): def __str__(self):
return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \ return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \
@ -289,14 +306,14 @@ class HudUI:
self.node = node self.node = node
self.label = node.getAttribute('label') self.label = node.getAttribute('label')
# #
self.aggregate_ring = fix_tf(node.getAttribute('aggregate_ring_game_stats')) self.aggregate_ring = string_to_bool(node.getAttribute('aggregate_ring_game_stats'))
self.aggregate_tour = fix_tf(node.getAttribute('aggregate_tourney_stats')) self.aggregate_tour = string_to_bool(node.getAttribute('aggregate_tourney_stats'))
self.hud_style = node.getAttribute('stat_aggregation_range') self.hud_style = node.getAttribute('stat_aggregation_range')
self.hud_days = node.getAttribute('aggregation_days') self.hud_days = node.getAttribute('aggregation_days')
self.agg_bb_mult = node.getAttribute('aggregation_level_multiplier') self.agg_bb_mult = node.getAttribute('aggregation_level_multiplier')
# #
self.h_aggregate_ring = fix_tf(node.getAttribute('aggregate_hero_ring_game_stats')) self.h_aggregate_ring = string_to_bool(node.getAttribute('aggregate_hero_ring_game_stats'))
self.h_aggregate_tour = fix_tf(node.getAttribute('aggregate_hero_tourney_stats')) self.h_aggregate_tour = string_to_bool(node.getAttribute('aggregate_hero_tourney_stats'))
self.h_hud_style = node.getAttribute('hero_stat_aggregation_range') self.h_hud_style = node.getAttribute('hero_stat_aggregation_range')
self.h_hud_days = node.getAttribute('hero_aggregation_days') self.h_hud_days = node.getAttribute('hero_aggregation_days')
self.h_agg_bb_mult = node.getAttribute('hero_aggregation_level_multiplier') self.h_agg_bb_mult = node.getAttribute('hero_aggregation_level_multiplier')
@ -367,10 +384,11 @@ class Config:
self.file = file self.file = file
self.supported_sites = {} self.supported_sites = {}
self.supported_games = {} self.supported_games = {}
self.supported_databases = {} self.supported_databases = {} # databaseName --> Database instance
self.aux_windows = {} self.aux_windows = {}
self.hhcs = {} self.hhcs = {}
self.popup_windows = {} self.popup_windows = {}
self.db_selected = None # database the user would like to use
# s_sites = doc.getElementsByTagName("supported_sites") # s_sites = doc.getElementsByTagName("supported_sites")
for site_node in doc.getElementsByTagName("site"): for site_node in doc.getElementsByTagName("site"):
@ -382,29 +400,26 @@ class Config:
game = Game(node = game_node) game = Game(node = game_node)
self.supported_games[game.game_name] = game self.supported_games[game.game_name] = game
# parse databases defined by user in the <supported_databases> section
# the user may select the actual database to use via commandline or by setting the selected="bool"
# attribute of the tag. if no database is explicitely selected, we use the first one we come across
# s_dbs = doc.getElementsByTagName("supported_databases") # s_dbs = doc.getElementsByTagName("supported_databases")
# select database from those defined in config by: #TODO: do we want to take all <database> tags or all <database> tags contained in <supported_databases>
# 1) command line option # ..this may break stuff for some users. so leave it unchanged for now untill there is a decission
# or 2) selected="True" in config element
# or 3) just choose the first we come across
for db_node in doc.getElementsByTagName("database"): for db_node in doc.getElementsByTagName("database"):
try:
db = Database(node=db_node) db = Database(node=db_node)
except:
raise FpdbError("Unable to create database object")
else:
if db.db_name in self.supported_databases: if db.db_name in self.supported_databases:
raise FpdbError("Database names must be unique") raise ValueError("Database names must be unique")
# If there is only one Database node, or none are marked if self.db_selected is None or db.db_selected:
# default, use first
if not self.supported_databases:
self.db_selected = db.db_name self.db_selected = db.db_name
self.supported_databases[db.db_name] = db self.supported_databases[db.db_name] = db
if db.db_selected: #TODO: if the user may passes '' (empty string) as database name via command line, his choice is ignored
self.db_selected = db.db_name # ..when we parse the xml we allow for ''. there has to be a decission if to allow '' or not
if dbname and dbname in self.supported_databases: if dbname and dbname in self.supported_databases:
self.db_selected = dbname self.db_selected = dbname
#NOTE: fpdb can not handle the case when no database is defined in xml, so we throw an exception for now
if self.db_selected is None:
raise ValueError('There must be at least one database defined')
# s_dbs = doc.getElementsByTagName("mucked_windows") # s_dbs = doc.getElementsByTagName("mucked_windows")
for aw_node in doc.getElementsByTagName("aw"): for aw_node in doc.getElementsByTagName("aw"):
@ -578,6 +593,13 @@ class Config:
else: else:
self.aux_windows[aux_name].layout[max].location[i] = ( locations[i][0], locations[i][1] ) self.aux_windows[aux_name].layout[max].location[i] = ( locations[i][0], locations[i][1] )
#NOTE: we got a nice Database class, so why map it again here?
# user input validation should be done when initializing the Database class. this allows to give appropriate feddback when something goes wrong
# try ..except is evil here. it swallows all kinds of errors. dont do this
# naming database types 2, 3, 4 on the fly is no good idea. i see this all over the code. better use some globally defined consts (see DATABASE_TYPE_*)
# i would like to drop this method entirely and replace it by get_selected_database() or better get_active_database(), returning one of our Database instances
# thus we can drop self.db_selected (holding database name) entirely and replace it with self._active_database = Database, avoiding to define the same
# thing multiple times
def get_db_parameters(self): def get_db_parameters(self):
db = {} db = {}
name = self.db_selected name = self.db_selected

View File

@ -1,28 +1,85 @@
"""Database manager
@todo: (gtk) how to validate user input in gtk.Dialog? as soon as the user clicks ok the dialog is dead. we use a while loop as workaround. not nice
@todo: (fpdb) we need the application name 'fpdb' from somewhere to put it in dialog titles
@todo: (fpdb) config object should be initialized globally and accessible from all modules via Configuration.py
@todo: (all dialogs) save/restore size and pos
@todo: (WidgetDatabaseManager) give database status meaningful colors
@todo: (WidgetDatabaseManager) implement database purging
@todo: (WidgetDatabaseManager) implement database export
@todo: (WidgetDatabaseManager) what to do on database doubleclick?
@todo: (WidgetDatabaseManager) context menu for database tree
@todo: (WidgetDatabaseManager) initializing/validating databases may take a while. how to give feedback?
"""
import os import os
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import gobject
#******************************************************************************************************* #*******************************************************************************************************
class DatabaseManager(object): class DatabaseManager(gobject.GObject):
DatabaseTypes = {} DatabaseTypes = {}
@classmethod @classmethod
def from_fpdb(klass, data, defaultDatabaseType=None): def from_fpdb(klass, data, defaultDatabaseType=None):
#TODO: parse whatever data is
#TODO: sort out unsupported databases passed by user and log
databases = (
DatabaseTypeSqLite(name='myDb'),
DatabaseTypeSqLite(name='myDb2'),
) #NOTE: if no databases are present in config fpdb fails with
# Traceback (most recent call last):
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/DatabaseManager.py", line 783, in <module>
# databaseManager = DatabaseManager.from_fpdb('', defaultDatabaseType=DatabaseTypeSqLite)
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/DatabaseManager.py", line 36, in from_fpdb
# config = Configuration.Config(file=options.config, dbname=options.dbname)
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/Configuration.py", line 436, in __init__
# db = self.get_db_parameters()
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/Configuration.py", line 583, in get_db_parameters
# name = self.db_selected
# AttributeError: Config instance has no attribute 'db_selected'
import sys
import Options
import Configuration
#NOTE: fpdb should perform this globally
(options, sys.argv) = Options.fpdb_options()
config = Configuration.Config(file=options.config, dbname=options.dbname)
#TODO: handle no database present
defaultDatabaseName = config.get_db_parameters().get('db-databaseName', None)
#TODO: fpdb stores databases in no particular order. this has to be fixed somehow
databases = []
for name, fpdbDatabase in config.supported_databases.items():
databaseKlass = klass.DatabaseTypes.get(fpdbDatabase.db_type, None)
#NOTE: Config does not seem to validate user input, so anything may end up here
if databaseKlass is None:
raise ValueError('Unknown databasetype: %s' % fpdbDatabase.db_type)
database = databaseKlass()
if database.Type == 'sqlite':
database.name = fpdbDatabase.db_name
database.file = fpdbDatabase.db_server
else:
database.name = fpdbDatabase.db_name
database.host = fpdbDatabase.db_server
#NOTE: fpdbDatabase.db_ip is no is a string
database.port = int(fpdbDatabase.db_ip)
database.user = fpdbDatabase.db_user
database.password = fpdbDatabase.db_pass
databases.append(database)
return klass(databases=databases, defaultDatabaseType=defaultDatabaseType) return klass(databases=databases, defaultDatabaseType=defaultDatabaseType)
def to_fpdb(self):
pass
def __init__(self, databases=None, defaultDatabaseType=None): def __init__(self, databases=None, defaultDatabaseType=None):
gobject.GObject.__init__(self)
self._defaultDatabaseType = defaultDatabaseType self._defaultDatabaseType = defaultDatabaseType
self._databases = [] if databases is None else list(databases) self._databases = [] if databases is None else list(databases)
self._activeDatabase = None
def __iter__(self): def __iter__(self):
return iter(self._databases) return iter(self._databases)
def set_default_database_type(self, databaseType): def set_default_database_type(self, databaseType):
@ -31,7 +88,7 @@ class DatabaseManager(object):
return self._defaultDatabaseType return self._defaultDatabaseType
def database_from_id(self, idDatabase): def database_from_id(self, idDatabase):
for database in self._databases: for database in self._databases:
if idDatabase == id(database): if idDatabase == self.database_id(database):
return database return database
def database_id(self, database): def database_id(self, database):
return id(database) return id(database)
@ -41,8 +98,26 @@ class DatabaseManager(object):
self._databases.append(database) self._databases.append(database)
def remove_database(self, database): def remove_database(self, database):
self._databases.remove(database) self._databases.remove(database)
def init_database(self, database):
pass def activate_database(self, database):
if self._activeDatabase is not None:
self._activeDatabase.status = self._activeDatabase.StatusInactive
#TODO: finalize database
self.emit('database-deactivated', self.database_id(self._activeDatabase) )
database.status = database.StatusActive
#TODO: activate database
self._activeDatabase = database
self.emit('database-activated', self.database_id(database) )
def active_database(self):
return self._activeDatabase
# register DatabaseManager signals
gobject.type_register(DatabaseManager)
gobject.signal_new('database-activated', DatabaseManager, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (int, ))
gobject.signal_new('database-deactivated', DatabaseManager, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (int, ))
gobject.signal_new('database-error', DatabaseManager, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (int, ))
class DatabaseTypeMeta(type): class DatabaseTypeMeta(type):
def __new__(klass, name, bases, kws): def __new__(klass, name, bases, kws):
@ -56,10 +131,25 @@ class DatabaseTypeMeta(type):
class DatabaseTypeBase(object): class DatabaseTypeBase(object):
__metaclass__ = DatabaseTypeMeta __metaclass__ = DatabaseTypeMeta
Type = None Type = None
Params = () StatusActive = 'active'
StatusInactive = 'inactive'
StatusError = 'error' #TODO: not implemented
#TODO: not happy with returning error string. just being too lazy to impl dozens of error codes for later translation
def init_new_database(self):
"""initializes a new empty database
@return: (str) error if something goes wrong, None otherwise
"""
raise NotImplementedError()
def validate_database(self):
"""checks if the database is valid
@return: (str) error if something goes wrong, None otherwise
"""
raise NotImplementedError()
class DatabaseTypePostgres(DatabaseTypeBase): class DatabaseTypePostgres(DatabaseTypeBase):
Type = 'postgres' Type = 'postgresql'
@classmethod @classmethod
def display_name(klass): def display_name(klass):
return 'Postgres' return 'Postgres'
@ -70,6 +160,15 @@ class DatabaseTypePostgres(DatabaseTypeBase):
self.user = user self.user = user
self.password = password self.password = password
self.database = database self.database = database
self.status = self.StatusInactive
#TODO: implement
def init_new_database(self):
pass
#TODO: implement
def validate_database(self):
pass
class DatabaseTypeMysql(DatabaseTypeBase): class DatabaseTypeMysql(DatabaseTypeBase):
Type = 'mysql' Type = 'mysql'
@ -83,6 +182,16 @@ class DatabaseTypeMysql(DatabaseTypeBase):
self.user = user self.user = user
self.password = password self.password = password
self.database = database self.database = database
self.status = self.StatusInactive
#TODO: implement
def init_new_database(self):
pass
#TODO: implement
def validate_database(self):
pass
class DatabaseTypeSqLite(DatabaseTypeBase): class DatabaseTypeSqLite(DatabaseTypeBase):
Type = 'sqlite' Type = 'sqlite'
@ -92,7 +201,26 @@ class DatabaseTypeSqLite(DatabaseTypeBase):
def __init__(self, name='', host='', file='', database='fpdb'): def __init__(self, name='', host='', file='', database='fpdb'):
self.name = name self.name = name
self.file = file self.file = file
self.database = database self.status = self.StatusInactive
def init_new_database(self):
# make shure all attrs are specified
if not self.file:
return 'no database file specified'
# create file if necessary (this will truncate file if it exists)
try:
open(self.file, 'w').close()
except IOError:
return 'can not write file'
#TODO: init tables (...)
def validate_database(self):
pass
#TODO: check if tables (...) exist
#TODO: how do we want to handle unsupported database types? #TODO: how do we want to handle unsupported database types?
# ..uncomment to remove unsupported database types # ..uncomment to remove unsupported database types
@ -104,6 +232,20 @@ class DatabaseTypeSqLite(DatabaseTypeBase):
#except ImportError: del DatabaseManager.DatabaseTypes['sqlite'] #except ImportError: del DatabaseManager.DatabaseTypes['sqlite']
#*************************************************************************************************************************** #***************************************************************************************************************************
#TODO: there is no title (on linux), wtf?
def DialogError(parent=None, msg=''):
dlg = gtk.MessageDialog(
parent=parent,
flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_OK,
message_format=msg,
)
dlg.run()
dlg.destroy()
return None
#TODO: derrive from gtk.VBox? #TODO: derrive from gtk.VBox?
class WidgetDatabaseProperties(gtk.VBox): class WidgetDatabaseProperties(gtk.VBox):
@ -158,7 +300,7 @@ class WidgetDatabaseProperties(gtk.VBox):
dlg.destroy() dlg.destroy()
#TODO: bit ugly this thingy. try to find a better way to map database attrs to gtk widgets
class FieldWidget(object): class FieldWidget(object):
def __init__(self, text='', attrDatabase='', widget=None, attrGet=None, attrSet=None, defaultValue=None, canEdit=False, tooltip=''): def __init__(self, text='', attrDatabase='', widget=None, attrGet=None, attrSet=None, defaultValue=None, canEdit=False, tooltip=''):
""" """
@ -212,16 +354,6 @@ class WidgetDatabaseProperties(gtk.VBox):
canEdit=True, canEdit=True,
tooltip='Any name you like to name the database ' tooltip='Any name you like to name the database '
), ),
self.FieldWidget(
text='Db:',
attrDatabase='database',
widget=gtk.Entry(),
defaultValue='',
attrGet='get_text',
attrSet='set_text',
canEdit=False,
tooltip='Name of the database to create'
),
self.FieldWidget( self.FieldWidget(
text='File:', text='File:',
attrDatabase='file', attrDatabase='file',
@ -272,6 +404,16 @@ class WidgetDatabaseProperties(gtk.VBox):
canEdit=False, canEdit=False,
tooltip='Password used to login to the host' tooltip='Password used to login to the host'
), ),
self.FieldWidget(
text='Db:',
attrDatabase='database',
widget=gtk.Entry(),
defaultValue='',
attrGet='get_text',
attrSet='set_text',
canEdit=False,
tooltip='Name of the database'
),
) )
# setup database type combo # setup database type combo
@ -372,11 +514,22 @@ class DialogDatabaseProperties(gtk.Dialog):
#TODO: derrive from gtk.VBox? #TODO: derrive from gtk.VBox?
# ..is there a way to derrive from gtk.Widget or similar? this would make parentWidget kw obsolete # ..is there a way to derrive from gtk.Widget or similar? this would make parentWidget kw obsolete
class WidgetDatabaseManager(gtk.VBox): class WidgetDatabaseManager(gtk.VBox):
"""
"""
def __init__(self, databaseManager, parentWidget=None): def __init__(self, databaseManager, parentWidget=None):
gtk.VBox.__init__(self) gtk.VBox.__init__(self)
self.databaseManager = databaseManager
self.parentWidget = parentWidget self.parentWidget = parentWidget
self.databaseManager = databaseManager
self.databaseManager.connect('database-activated', self.on_database_manager_database_activated)
self.databaseManager.connect('database-deactivated', self.on_database_manager_database_deactivated)
self.databaseStatusNames = {
DatabaseTypeBase.StatusActive: 'Active',
DatabaseTypeBase.StatusInactive: 'Inactive',
DatabaseTypeBase.StatusError: 'Error',
}
#TODO: dono how to make word wrap work as expected #TODO: dono how to make word wrap work as expected
self.labelInfo = gtk.Label('database management') self.labelInfo = gtk.Label('database management')
@ -389,6 +542,10 @@ class WidgetDatabaseManager(gtk.VBox):
#TODO: bit messy the distinction New/Add/Edit. we'd have to pass three flags to DialogDatabaseProperties #TODO: bit messy the distinction New/Add/Edit. we'd have to pass three flags to DialogDatabaseProperties
# to handle this. maybe drop Edit (is just a Remove + Add), to keep things simple # to handle this. maybe drop Edit (is just a Remove + Add), to keep things simple
self.buttonDatabaseActivate = gtk.Button("Activate")
self.buttonDatabaseActivate.set_tooltip_text('activates the database')
self.buttonDatabaseActivate.connect('clicked', self.on_button_database_activate_clicked)
self.buttonDatabaseActivate.set_sensitive(False)
self.buttonDatabaseNew = gtk.Button("New..") self.buttonDatabaseNew = gtk.Button("New..")
self.buttonDatabaseNew.set_tooltip_text('creates a new database') self.buttonDatabaseNew.set_tooltip_text('creates a new database')
self.buttonDatabaseNew.connect('clicked', self.on_button_database_new_clicked) self.buttonDatabaseNew.connect('clicked', self.on_button_database_new_clicked)
@ -402,31 +559,30 @@ class WidgetDatabaseManager(gtk.VBox):
self.buttonDatabaseRemove = gtk.Button("Remove") self.buttonDatabaseRemove = gtk.Button("Remove")
self.buttonDatabaseRemove.set_tooltip_text('removes the database from the list') self.buttonDatabaseRemove.set_tooltip_text('removes the database from the list')
self.buttonDatabaseRemove.set_sensitive(False) self.buttonDatabaseRemove.set_sensitive(False)
self.buttonDatabaseRemove.connect('clicked', self.on_button_database_remove_clicked)
#TODO: i dont think we should do any real database management here. maybe drop it #TODO: i dont think we should do any real database management here. maybe drop it
self.buttonDatabaseDelete = gtk.Button("Delete") #self.buttonDatabaseDelete = gtk.Button("Delete")
self.buttonDatabaseDelete.set_tooltip_text('removes the database from the list and deletes it') #self.buttonDatabaseDelete.set_tooltip_text('removes the database from the list and deletes it')
self.buttonDatabaseDelete.set_sensitive(False) #self.buttonDatabaseDelete.set_sensitive(False)
# init database tree # init database tree
self.treeDatabases = gtk.TreeView() self.treeDatabases = gtk.TreeView()
self.treeDatabaseColumns = ( #NOTE: column names starting with '_' will be hidden treeDatabaseColumns = ( # name, displayName, dataType
'Name', ('name', 'Name', str),
'Status', ('status', 'Status', str),
'Type', ('type', 'Type', str),
'_id', ('_id', '', int),
) )
self.treeDatabaseColumns = {} # name --> index
store = gtk.ListStore(str, str, str, int) store = gtk.ListStore( *[i[2] for i in treeDatabaseColumns] )
self.treeDatabases.set_model(store) self.treeDatabases.set_model(store)
columns = ('Name', 'Status', 'Type', '_id') for i, (name, displayName, dataType) in enumerate(treeDatabaseColumns):
for i, column in enumerate(columns): col = gtk.TreeViewColumn(displayName, gtk.CellRendererText(), text=i)
col = gtk.TreeViewColumn(column, gtk.CellRendererText(), text=i)
self.treeDatabases.append_column(col) self.treeDatabases.append_column(col)
if column.startswith('_'): if name.startswith('_'):
col.set_visible(False) col.set_visible(False)
self.treeDatabaseColumns[name] = i
self.treeDatabaseColumns = dict([(name, i) for (i, name) in enumerate(self.treeDatabaseColumns)])
self.treeDatabases.get_selection().connect('changed', self.on_tree_databases_selection_changed) self.treeDatabases.get_selection().connect('changed', self.on_tree_databases_selection_changed)
# layout widgets # layout widgets
@ -438,11 +594,12 @@ class WidgetDatabaseManager(gtk.VBox):
hbox.set_homogeneous(False) hbox.set_homogeneous(False)
vbox = gtk.VBox() vbox = gtk.VBox()
hbox.pack_start(vbox, False, False, 2) hbox.pack_start(vbox, False, False, 2)
vbox.pack_start(self.buttonDatabaseActivate, False, False, 2)
vbox.pack_start(self.buttonDatabaseNew, False, False, 2) vbox.pack_start(self.buttonDatabaseNew, False, False, 2)
vbox.pack_start(self.buttonDatabaseAdd, False, False, 2) vbox.pack_start(self.buttonDatabaseAdd, False, False, 2)
vbox.pack_start(self.buttonDatabaseEdit, False, False, 2) vbox.pack_start(self.buttonDatabaseEdit, False, False, 2)
vbox.pack_start(self.buttonDatabaseRemove, False, False, 2) vbox.pack_start(self.buttonDatabaseRemove, False, False, 2)
vbox.pack_start(self.buttonDatabaseDelete, False, False, 2) #vbox.pack_start(self.buttonDatabaseDelete, False, False, 2)
box = gtk.VBox() box = gtk.VBox()
vbox.pack_start(box, True, True, 0) vbox.pack_start(box, True, True, 0)
@ -452,48 +609,128 @@ class WidgetDatabaseManager(gtk.VBox):
self.show_all() self.show_all()
# init widget # init widget
model = self.treeDatabases.get_model()
for database in self.databaseManager: for database in self.databaseManager:
self.treeDatabases.get_model().append( (database.name, 'foo', database.Type, self.databaseManager.database_id(database)) ) it = model.append()
model.set_value(it, self.treeDatabaseColumns['name'], database.name)
model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] )
model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() )
model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database))
def on_database_manager_database_activated(self, databaseManager, idDatabase):
database = self.databaseManager.database_from_id(idDatabase)
model = self.treeDatabases.get_model()
for row in iter(model):
if row[self.treeDatabaseColumns['_id']] == idDatabase:
row[self.treeDatabaseColumns['status']] = self.databaseStatusNames[database.StatusActive]
break
else:
raise ValueError('database not found')
def on_database_manager_database_deactivated(self, databaseManager, idDatabase):
database = self.databaseManager.database_from_id(idDatabase)
model = self.treeDatabases.get_model()
for row in iter(model):
if row[self.treeDatabaseColumns['_id']] == idDatabase:
row[self.treeDatabaseColumns['status']] = self.databaseStatusNames[database.StatusInactive]
break
else:
raise ValueError('database not found')
def on_button_database_activate_clicked(self, button):
selection = self.treeDatabases.get_selection()
if selection is None:
return
model, it = selection.get_selected()
idDatabase = model.get_value(it, self.treeDatabaseColumns['_id'])
database = self.databaseManager.database_from_id(idDatabase)
self.databaseManager.activate_database(database)
#TODO: for some reason i have to click OK/Cancel twice to close the dialog #TODO: for some reason i have to click OK/Cancel twice to close the dialog
def on_button_database_new_clicked(self, button): def on_button_database_new_clicked(self, button):
databaseType = self.databaseManager.get_default_database_type() databaseKlass = self.databaseManager.get_default_database_type()
if databaseType is None: if databaseKlass is None:
raise ValueError('no defult database type set') raise ValueError('no default database type set')
database = databaseKlass()
while True:
dlg = DialogDatabaseProperties( dlg = DialogDatabaseProperties(
self.databaseManager, self.databaseManager,
databaseType(), database,
parent=self.parentWidget, parent=self.parentWidget,
mode=WidgetDatabaseProperties.ModeNew, mode=WidgetDatabaseProperties.ModeNew,
title='[New database] - database properties' title='New database'
) )
if dlg.run() == gtk.RESPONSE_REJECT: response = dlg.run()
pass if response == gtk.RESPONSE_ACCEPT:
if dlg.run() == gtk.RESPONSE_ACCEPT:
database = dlg.get_widget_database_properties().get_database() database = dlg.get_widget_database_properties().get_database()
#TODO: sanity checks + init databse if necessary #TODO: initing may or may not take a while. how to handle?
self.databaseManager.add_database(database) error = database.init_new_database()
self.treeDatabases.get_model().append( (database.name, 'foo', database.Type, self.databaseManager.database_id(database)) ) if error:
DialogError(parent=dlg, msg=error)
dlg.destroy() dlg.destroy()
continue
else:
database = None
dlg.destroy()
break
if database is None:
return
self.databaseManager.add_database(database)
model = self.treeDatabases.get_model()
it = model.append()
model.set_value(it, self.treeDatabaseColumns['name'], database.name)
model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] )
model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() )
model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database))
def on_button_database_add_clicked(self, button): def on_button_database_add_clicked(self, button):
databaseType = self.databaseManager.get_default_database_type() databaseKlass = self.databaseManager.get_default_database_type()
if databaseType is None: if databaseKlass is None:
raise ValueError('no defult database type set') raise ValueError('no defult database type set')
database = databaseKlass()
while True:
dlg = DialogDatabaseProperties( dlg = DialogDatabaseProperties(
self.databaseManager, self.databaseManager,
databaseType(), database,
parent=self.parentWidget, parent=self.parentWidget,
mode=WidgetDatabaseProperties.ModeAdd, mode=WidgetDatabaseProperties.ModeAdd,
title='[Add database] - database properties' title='Add database'
) )
if dlg.run() == gtk.RESPONSE_REJECT: response = dlg.run()
pass if response == gtk.RESPONSE_ACCEPT:
if dlg.run() == gtk.RESPONSE_ACCEPT:
database = dlg.get_widget_database_properties().get_database() database = dlg.get_widget_database_properties().get_database()
#TODO: sanity checks #TODO: validating may or may not take a while. how to handle?
error = database.validate_database()
if error:
DialogError(parent=self.parentWidget, msg=error)
dlg.destroy()
continue
else:
database = None
dlg.destroy()
break
if database is None:
return
self.databaseManager.add_database(database) self.databaseManager.add_database(database)
self.treeDatabases.get_model().append( (database.name, 'foo', database.Type, self.databaseManager.database_id(database)) ) model = self.treeDatabases.get_model()
it = model.append()
model.set_value(it, self.treeDatabaseColumns['name'], database.name)
model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] )
model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() )
model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database))
dlg.destroy() dlg.destroy()
def on_button_database_edit_clicked(self, button): def on_button_database_edit_clicked(self, button):
@ -501,39 +738,52 @@ class WidgetDatabaseManager(gtk.VBox):
if selection is None: if selection is None:
return return
model, iter = selection.get_selected() model, it = selection.get_selected()
idDatabase = model.get_value(iter, self.treeDatabaseColumns['_id']) idDatabase = model.get_value(it, self.treeDatabaseColumns['_id'])
database = self.databaseManager.database_from_id(idDatabase) database = self.databaseManager.database_from_id(idDatabase)
dlg = DialogDatabaseProperties( dlg = DialogDatabaseProperties(
self.databaseManager, self.databaseManager,
database=database, database,
parent=self.parentWidget, parent=self.parentWidget,
mode=WidgetDatabaseProperties.ModeEdit, mode=WidgetDatabaseProperties.ModeEdit,
title='[Edit database] - database properties' title='Edit database'
) )
if dlg.run() == gtk.RESPONSE_REJECT: response = dlg.run()
if response == gtk.RESPONSE_REJECT:
pass pass
if dlg.run() == gtk.RESPONSE_ACCEPT: elif response == gtk.RESPONSE_ACCEPT:
database = dlg.get_database() database = dlg.get_database()
selection = self.treeDatabases.get_selection() selection = self.treeDatabases.get_selection()
if selection is not None: if selection is not None:
model, iter = selection.get_selected() model, it = selection.get_selected()
model.set_value(iter, 0, database.name) model.set_value(it, self.treeDatabaseColumns['name'], database.name)
dlg.destroy() dlg.destroy()
def on_button_database_remove_clicked(self, button):
selection = self.treeDatabases.get_selection()
if selection is None:
return
model, it = selection.get_selected()
#TODO: finalize database
model.remove(it)
def on_tree_databases_selection_changed(self, treeSelection): def on_tree_databases_selection_changed(self, treeSelection):
hasSelection = bool(treeSelection.count_selected_rows()) hasSelection = bool(treeSelection.count_selected_rows())
# enable/disable selection dependend widgets # enable/disable selection dependend widgets
self.buttonDatabaseActivate.set_sensitive(hasSelection)
self.buttonDatabaseEdit.set_sensitive(hasSelection) self.buttonDatabaseEdit.set_sensitive(hasSelection)
self.buttonDatabaseRemove.set_sensitive(hasSelection) self.buttonDatabaseRemove.set_sensitive(hasSelection)
self.buttonDatabaseDelete.set_sensitive(hasSelection) #self.buttonDatabaseDelete.set_sensitive(hasSelection)
class DialogDatabaseManager(gtk.Dialog): class DialogDatabaseManager(gtk.Dialog):
def __init__(self, databaseManager, parent=None): def __init__(self, databaseManager, parent=None):
gtk.Dialog.__init__(self, gtk.Dialog.__init__(self,
title="My dialog", title="Databases",
parent=parent, parent=parent,
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
buttons=( buttons=(

View File

@ -60,13 +60,9 @@ class InterProcessLockBase:
self._has_lock = False self._has_lock = False
def locked(self): def locked(self):
if self._has_lock: if self.acquire():
return True
try:
self.acquire()
self.release() self.release()
return False return False
except SingleInstanceError:
return True return True
LOCK_FILE_DIRECTORY = '/tmp' LOCK_FILE_DIRECTORY = '/tmp'
@ -162,38 +158,33 @@ def test_construct():
>>> lock1 = InterProcessLock(name=test_name) >>> lock1 = InterProcessLock(name=test_name)
>>> lock1.acquire() >>> lock1.acquire()
True
>>> lock2 = InterProcessLock(name=test_name) >>> lock2 = InterProcessLock(name=test_name)
>>> lock3 = InterProcessLock(name=test_name) >>> lock3 = InterProcessLock(name=test_name)
# Since lock1 is locked, other attempts to acquire it fail. # Since lock1 is locked, other attempts to acquire it fail.
>>> lock2.acquire() >>> lock2.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> lock3.acquire() >>> lock3.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release the lock and let lock2 have it. # Release the lock and let lock2 have it.
>>> lock1.release() >>> lock1.release()
>>> lock2.acquire() >>> lock2.acquire()
True
>>> lock3.acquire() >>> lock3.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release it and give it back to lock1 # Release it and give it back to lock1
>>> lock2.release() >>> lock2.release()
>>> lock1.acquire() >>> lock1.acquire()
True
>>> lock2.acquire() >>> lock2.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Test lock status # Test lock status
>>> lock2.locked() >>> lock2.locked()
@ -224,7 +215,7 @@ def test_construct():
... import win32con ... import win32con
... import pywintypes ... import pywintypes
... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid) ... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid)
... return (0 != win32api.TerminateProcess(handle, 0)) ... #return (0 != win32api.TerminateProcess(handle, 0))
# Test to acquire the lock in another process. # Test to acquire the lock in another process.
>>> def execute(cmd): >>> def execute(cmd):
@ -237,15 +228,14 @@ def test_construct():
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();') >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire() >>> lock1.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid) >>> os_independent_kill(pid)
>>> time.sleep(1) >>> time.sleep(1)
>>> lock1.acquire() >>> lock1.acquire()
True
>>> lock1.release() >>> lock1.release()
# Testing wait # Testing wait
@ -253,13 +243,12 @@ def test_construct():
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();') >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire() >>> lock1.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid) >>> os_independent_kill(pid)
>>> lock1.acquire(True) >>> lock1.acquire(True)
True
>>> lock1.release() >>> lock1.release()
""" """