From cd8e9396a77234158dcd6c3c5edfb3a60c3e50d2 Mon Sep 17 00:00:00 2001 From: Karl Chen Date: Tue, 13 Jan 2004 06:51:19 +0000 Subject: [PATCH] xadd stuff svn path=/trunk/boinc/; revision=2867 --- checkin_notes | 36 +++++++ py/Boinc/add_util.py | 179 +++++++++++++++++++++++++++++++++ py/Boinc/boinc_project_path.py | 6 +- py/Boinc/boincxml.py | 139 +++++++++++++++++++++++++ py/Boinc/configxml.py | 150 +++------------------------ py/Boinc/db_base.py | 16 ++- py/Boinc/projectxml.py | 58 +++++++++++ tools/add | 178 +++++--------------------------- tools/make_project | 5 - tools/xadd | 12 +++ 10 files changed, 481 insertions(+), 298 deletions(-) create mode 100644 py/Boinc/add_util.py create mode 100644 py/Boinc/boincxml.py create mode 100644 py/Boinc/projectxml.py create mode 100755 tools/xadd diff --git a/checkin_notes b/checkin_notes index 50441ba702..2582424e3d 100755 --- a/checkin_notes +++ b/checkin_notes @@ -9091,3 +9091,39 @@ David 9 Jan 2004 win_build/ boinc.sln *.vcproj + +Karl 2004-01-12 + + - BOINC how has a project.xml file (by default in the same location as + config.xml) that can contain database information: + - projects + - platforms + - core versions + - apps + - app versions + - this information used to be added one at a time using the `add' + command-line tool; now there is a new tool `xadd' available, which + parses project.xml and adds anything necessary. + + - refactored configxml.py into boincxml.py and configxml.py: + - boincxml.py contains generic XML utility code + - configxml.py contains code specific to config.xml and run_state.XML + - external interface to configxml.py unchanged + + - new projectxml.py that parses and writes project.xml + + - refactored tools/add into tools/add and py/Boinc/add_util.py + + - added new addable fields + + py/Boinc/ + boinc_project_path.py + configxml.py + boincxml.py (new) + projectxml.py (new) + add_util.py (new) + db_base.py + tools/ + xadd (new) + add + diff --git a/py/Boinc/add_util.py b/py/Boinc/add_util.py new file mode 100644 index 0000000000..1c595a6fcd --- /dev/null +++ b/py/Boinc/add_util.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python + +# $Id$ + +# add_util.py - code shared between add and xadd + +from Boinc import database, tools +import time, pprint +import MySQLdb + +CREATE_TIME = ['?create_time', int(time.time())] +TRANSITION_TIME = ['?transition_time', int(time.time())] + +class XCoreVersion(database.CoreVersion): + def __init__(self,**kwargs): + kwargs['xml_doc'] = tools.process_executable_file(kwargs['exec_file']) + del kwargs['exec_file'] + database.CoreVersion.__init__(self, **kwargs) + +class XAppVersion(database.AppVersion): + def __init__(self,**kwargs): + kwargs['xml_doc'] = tools.process_app_version( + app = kwargs['app'], + version_num = int(kwargs['version_num']), + exec_files = kwargs['exec_files'], + signature_files = kwargs.setdefault('signature_files',{})) + del kwargs['signature_files'] + del kwargs['exec_files'] + del kwargs['exec_file'] + database.AppVersion.__init__(self,**kwargs) + +# format: [ database.Object, args, ...] +# arg format: +# 'arg' +# '?arg' optional +# [ 'arg', default_value ] +list_objects_to_add = [ + [ database.Project, 'short_name', 'long_name' ], + [ database.Platform, 'name', 'user_friendly_name', CREATE_TIME ], + [ XCoreVersion, 'platform', 'version_num', 'exec_file', + ['?message',''], ['?message_priority','']], + [ database.App, 'name', 'user_friendly_name', ['?min_version',0], CREATE_TIME], + [ XAppVersion, 'app', 'platform', 'version_num', 'exec_file', '?signature_file', + CREATE_TIME ], + [ database.User, 'name', 'email_addr', 'authenticator', + ['?country','United States'], ['?postal_code','94703'], + '?global_prefs', '?global_prefs_file', + CREATE_TIME ], + # [ database.Workunit, 'zzzz' ], + ] + +class AddObject: + pass + +add_objects = {} +for o in list_objects_to_add: + add_object = AddObject() + add_object.DatabaseObject = o[0] + add_object.name = add_object.DatabaseObject._table.table + add_object.args = [] + add_object.optional_args = [] + add_object.default_values = {} + for arg in o[1:]: + if isinstance(arg, list): + default_value = arg[1] + arg = arg[0] + else: + default_value = None + if arg.startswith('?'): + optional = True + arg = arg[1:] + else: + optional = False + if optional: + add_object.optional_args.append(arg) + else: + add_object.args.append(arg) + if default_value: + add_object.default_values[arg] = default_value + add_objects[add_object.name] = add_object + +most_recent_exec_file = None + +def translate_arg(object, arg, value, args_dict): + '''Translate various user argument values, for adding given ``object``. + Modifies ``args_dict``.''' + + database_table = None + try: + database_table = database.__dict__[arg.capitalize()]._table + except: + pass + if database_table: + args_dict[arg] = translate_database_arg(database_table, arg, value) + return + + if arg == 'global_prefs_file': + args_dict['global_prefs'] = open(value).read() + return + + if object.DatabaseObject == XAppVersion: + # 'add app_version' accepts multiple '-exec_file's with + # '-signature_file' applying to the most recent exec_file + if arg == 'exec_file': + global most_recent_exec_file + most_recent_exec_file = value + args_dict.setdefault('exec_files',[]).append(value) + # since 'exec_file' (without 's') is required, set it to None so + # that argument checker knows we got one; we'll delete it later. + args_dict[arg] = None + return + if arg == 'signature_file': + args_dict.setdefault('signature_files',{})[most_recent_exec_file] = value + return + + args_dict[arg] = value + +def translate_database_arg(database_table, arg, value): + '''Look up an object ``value`` either as a database ID or string. + This allows us to accept e.g. either --app Astropulse or --app 1''' + try: + id = int(value) + results = database_table.find(id=id) + if not results: + raise Exception("") + except: + results = database_table.find(name=value) + if len(results) == 0: + raise SystemExit('No %s "%s" found' %(arg,value)) + if len(results) > 1: + print >>sys.stderr, 'Too many %ss match "%s": '%(arg,value) + for result in results: + print >>sys.stderr, ' ', result.name + raise SystemExit + return results[0] + +class AddObjectException(Exception): pass + +def check_required_arguments(add_object, args_dict): + for arg in add_object.args: + if not arg in args_dict: + raise AddObjectException('required value for %s not given'%arg) + +def translate_args_dict(add_object, untranslated_args_dict): + args_dict = add_object.default_values.copy() + for arg,value in untranslated_args_dict.items(): + translate_arg(add_object,arg,value,args_dict) + return args_dict + +def exception_is_duplicate_entry(exception): + '''Checks a MySQLdb.IntegrityError for whether the error is Duplicate + Entry. Kludgy.''' + return (isinstance(exception, MySQLdb.IntegrityError) and + str(exception).find('Duplicate entry')!=-1) + +def do_add_object(add_object, untranslated_args_dict, skip_old=False): + '''Input ```args_dict``` must have been translated already.''' + args_dict = translate_args_dict(add_object, untranslated_args_dict) + check_required_arguments(add_object, args_dict) + dbobject = add_object.DatabaseObject(**args_dict) + print "Processing", dbobject, "..." + # print "Commiting", dbobject, "with args:" + # pprint.pprint(dbobject.__dict__) + try: + dbobject.commit() + except MySQLdb.MySQLError, e: + if skip_old and exception_is_duplicate_entry(e): + print " Skipped existing", dbobject + return + else: + raise SystemExit('Error committing %s: %s' %(dbobject, e)) + + # delete object and re-select it from database to allow user to check + # consistency + id = dbobject.id + del dbobject + dbobject = add_object.DatabaseObject._table[id] + print " Committed", dbobject, "; values:" + pprint.pprint(dbobject.__dict__) diff --git a/py/Boinc/boinc_project_path.py b/py/Boinc/boinc_project_path.py index 0e7400bc31..9265ec1312 100644 --- a/py/Boinc/boinc_project_path.py +++ b/py/Boinc/boinc_project_path.py @@ -3,7 +3,7 @@ # boinc_project_path.py - defines the directories where config.xml and # run_state.xml are kept. # -# You can override these defaults +# You can override these defaults with one of these options: # 1) modify this file directly (if you have only one project on your server # or have separate copies for each) # 2) create a new boinc_project_path.py and place it earlier in PYTHONPATH @@ -19,6 +19,10 @@ config_xml_filename = os.environ.get( 'BOINC_CONFIG_XML', os.path.join(PROGRAM_PARENT_DIR,'config.xml')) +project_xml_filename = os.environ.get( + 'BOINC_PROJECT_XML', + os.path.join(PROGRAM_PARENT_DIR,'project.xml')) + run_state_xml_filename = os.environ.get( 'BOINC_RUN_STATE_XML', os.path.join(PROGRAM_PARENT_DIR,'run_state.xml')) diff --git a/py/Boinc/boincxml.py b/py/Boinc/boincxml.py new file mode 100644 index 0000000000..1627041bb5 --- /dev/null +++ b/py/Boinc/boincxml.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python + +# $Id$ + +# boincxml.py - xml utilities for boinc + +import sys +import xml.dom.minidom + +def append_new_element(parent_node, name): + new_element = xml.dom.minidom.Element(name) + if isinstance(parent_node,xml.dom.minidom.Document): + new_element.ownerDocument = parent_node + else: + assert(parent_node.ownerDocument) + new_element.ownerDocument = parent_node.ownerDocument + parent_node.appendChild(new_element) + return new_element + +def get_elements(node, name): + return node.getElementsByTagName(name) + +def get_element(node, name, optional=True): + try: + return get_elements(node,name)[0] + except IndexError: + if optional: + return append_new_element(node, name) + raise SystemExit("ERROR: Couldn't find xml node <%s>"% name) + +def _None2Str(object): + if object == None: + return '' + else: + return object + +def get_element_data(node): + return node and _None2Str(node.firstChild and node.firstChild.data) + +def get_element_int(node, default=0): + try: + return int(_get_element_data(node)) + except: + return default + +def get_child_elements(node): + return filter(lambda node: node.nodeType == node.ELEMENT_NODE, node.childNodes) + +def set_element(node, new_data): + if node.firstChild and node.firstChild.data: + node.firstChild.data = str(new_data) + else: + new_data_node = xml.dom.minidom.Text() + new_data_node.data = str(new_data) + new_data_node.ownerDocument = node.ownerDocument + node.appendChild(new_data_node) + +def strip_white_space(node): + ''' strip white space from text nodes, and remove empty nodes. ''' + # the [:] below is to copy the list since removing children modifies the + # list. + for child in node.childNodes[:]: + if child.nodeType == child.TEXT_NODE: + child.data = child.data.strip() + if not child.data:# and (child.nextSibling or child.previousSibling): + node.removeChild(child) + else: + strip_white_space(child) + +def get_elements_as_dict(node): + dict = {} + for child in get_child_elements(node): + # note: str() changes from unicode to single-byte strings + dict[str(child.nodeName)] = get_element_data(child) + return dict + +class ConfigDict: + def __init__(self, dom_node): + self._node = dom_node + self._name = self._node.nodeName + self.__dict__.update(get_elements_as_dict(self._node)) + def save(self): + for key in self.__dict__: + if key.startswith('_'): continue + set_element( get_element(self._node,key,1), str(self.__dict__[key]) ) + def debug_print(self): + for key in self.__dict__.keys(): + print key.rjust(15), '=', self.__dict__[key] + +class ConfigDictList(list): + def __init__(self, dom_node, item_class=ConfigDict): + self._node = dom_node + list.__init__(self, map(item_class, get_child_elements(self._node))) + def save(self): + map(ConfigDict.save, self) + def make_node_and_append(self, name): + '''Make a new ConfigDict and append it. Returns new ConfigDict.''' + new_element = append_new_element(self._node, name) + new_cd = ConfigDict(new_element) + self.append(new_cd) + return new_cd + +class XMLConfig: + '''Base class for xml config files''' + def __init__(self, filename = None): + # default_filename should be defined by children + self.filename = filename or self.default_filename + def _init_empty_xml(self): + self.xml = xml.dom.minidom.parseString(self.default_xml) + def init_empty(self): + self._init_empty_xml() + self._get_elements() + return self + def read(self, failopen_ok=False): + """Read the XML object's file source and return self.""" + try: + self.xml = xml.dom.minidom.parse(self.filename) + strip_white_space(self.xml) + except IOError, e: + if not failopen_ok: + # raise + raise Exception("%s: Couldn't parse XML config\n%s: %s"%(sys.argv[0],sys.argv[0],e)) + print >>sys.stderr, "Warning:", e + # self.xml = xml.dom.minidom.Document() + self._init_empty_xml() + self._get_elements() + return self + def _get_elements(self): + pass + def write(self, output=None): + """Write XML data to filename, or other stream.""" + self._set_elements() + if not output: + output = open(self.filename,'w') + self.xml.writexml(output, "", " ", "\n") + print >>output + return self + def _set_elements(self): + pass diff --git a/py/Boinc/configxml.py b/py/Boinc/configxml.py index 4645bb4d1c..8e044d6fad 100644 --- a/py/Boinc/configxml.py +++ b/py/Boinc/configxml.py @@ -22,136 +22,11 @@ USAGE: from Boinc import configxml ''' import sys -import xml.dom.minidom import boinc_project_path +from boincxml import * default_config_file = None -def _append_new_element(parent_node, name): - new_element = xml.dom.minidom.Element(name) - if isinstance(parent_node,xml.dom.minidom.Document): - new_element.ownerDocument = parent_node - else: - assert(parent_node.ownerDocument) - new_element.ownerDocument = parent_node.ownerDocument - parent_node.appendChild(new_element) - return new_element - -def _get_elements(node, name): - return node.getElementsByTagName(name) - -def _get_element(node, name, optional=True): - try: - return _get_elements(node,name)[0] - except IndexError: - if optional: - return _append_new_element(node, name) - raise SystemExit("ERROR: Couldn't find xml node <%s>"% name) - -def _None2Str(object): - if object == None: - return '' - else: - return object - -def _get_element_data(node): - return node and _None2Str(node.firstChild and node.firstChild.data) - -def _get_element_int(node, default=0): - try: - return int(_get_element_data(node)) - except: - return default - -def _get_child_elements(node): - return filter(lambda node: node.nodeType == node.ELEMENT_NODE, node.childNodes) - -def _set_element(node, new_data): - if node.firstChild and node.firstChild.data: - node.firstChild.data = str(new_data) - else: - new_data_node = xml.dom.minidom.Text() - new_data_node.data = str(new_data) - new_data_node.ownerDocument = node.ownerDocument - node.appendChild(new_data_node) - -def strip_white_space(node): - ''' strip white space from text nodes, and remove empty nodes. ''' - # the [:] below is to copy the list since removing children modifies the - # list. - for child in node.childNodes[:]: - if child.nodeType == child.TEXT_NODE: - child.data = child.data.strip() - if not child.data:# and (child.nextSibling or child.previousSibling): - node.removeChild(child) - else: - strip_white_space(child) - -class ConfigDict: - def __init__(self, dom_node): - self._node = dom_node - self._name = self._node.nodeName - for node in _get_child_elements(self._node): - self.__dict__[node.nodeName] = _get_element_data(node) - def save(self): - for key in self.__dict__: - if key.startswith('_'): continue - _set_element( _get_element(self._node,key,1), str(self.__dict__[key]) ) - def debug_print(self): - for key in self.__dict__.keys(): - print key.rjust(15), '=', self.__dict__[key] - -class ConfigDictList(list): - def __init__(self, dom_node, item_class=ConfigDict): - self._node = dom_node - list.__init__(self, map(item_class, _get_child_elements(self._node))) - def save(self): - map(ConfigDict.save, self) - def make_node_and_append(self, name): - '''Make a new ConfigDict and append it. Returns new ConfigDict.''' - new_element = _append_new_element(self._node, name) - new_cd = ConfigDict(new_element) - self.append(new_cd) - return new_cd - -class XMLConfig: - '''Base class for xml config files''' - def __init__(self, filename = None): - # default_filename should be defined by children - self.filename = filename or self.default_filename - def _init_empty_xml(self): - self.xml = xml.dom.minidom.parseString(self.default_xml) - def init_empty(self): - self._init_empty_xml() - self._get_elements() - return self - def read(self, failopen_ok=False): - """Read the XML object's file source and return self.""" - try: - self.xml = xml.dom.minidom.parse(self.filename) - strip_white_space(self.xml) - except IOError, e: - if not failopen_ok: - # raise - raise Exception("%s: Couldn't parse XML config\n%s: %s"%(sys.argv[0],sys.argv[0],e)) - print >>sys.stderr, "Warning:", e - # self.xml = xml.dom.minidom.Document() - self._init_empty_xml() - self._get_elements() - return self - def _get_elements(self): - pass - def write(self, output=None): - """Write XML data to filename, or other stream.""" - self._set_elements() - if not output: - output = open(self.filename,'w') - self.xml.writexml(output, "", " ", "\n") - print >>output - return self - def _set_elements(self): - pass - class ConfigFile(XMLConfig): ''' embodies config.xml @@ -162,14 +37,14 @@ class ConfigFile(XMLConfig): ''' default_filename = boinc_project_path.config_xml_filename def __init__(self, *args, **kwargs): - apply(XMLConfig.__init__,(self,)+args,kwargs) + XMLConfig.__init__(self, *args, **kwargs) global default_config_file default_config_file = self def _get_elements(self): - self.xml_boinc = _get_element(self.xml, 'boinc', optional=False) - self.xml_config = _get_element(self.xml_boinc, 'config', optional=False) - self.xml_tasks = _get_element(self.xml_boinc, 'tasks') - self.xml_daemons = _get_element(self.xml_boinc, 'daemons') + self.xml_boinc = get_element(self.xml, 'boinc', optional=False) + self.xml_config = get_element(self.xml_boinc, 'config', optional=False) + self.xml_tasks = get_element(self.xml_boinc, 'tasks') + self.xml_daemons = get_element(self.xml_boinc, 'daemons') self.config = ConfigDict(self.xml_config) self.daemons = ConfigDictList(self.xml_daemons) self.tasks = ConfigDictList(self.xml_tasks) @@ -208,18 +83,19 @@ class RunStateFile(XMLConfig): ''' default_filename = boinc_project_path.run_state_xml_filename def _get_elements(self): - self.xml_boinc = _get_element(self.xml, 'boinc', optional=False) - self.xml_tasks = _get_element(self.xml_boinc, 'tasks') - self.xml_enabled = _get_element(self.xml_boinc, 'enabled') + self.xml_boinc = get_element(self.xml, 'boinc', optional=False) + self.xml_tasks = get_element(self.xml_boinc, 'tasks') + self.xml_enabled = get_element(self.xml_boinc, 'enabled') self.tasks = ConfigDictList(self.xml_tasks) - self.enabled = _get_element_int(self.xml_enabled) + self.enabled = get_element_int(self.xml_enabled) def _set_elements(self): - _set_element( self.xml_enabled, self.enabled ) + set_element( self.xml_enabled, self.enabled ) self.tasks.save() default_xml = '' def default_config(): - '''If any config file has been read, return it. Else open the default one and return it.''' + '''If any config file has been read, return it. Else open the default one + and return it.''' if not default_config_file: ConfigFile().read() assert(default_config_file) return default_config_file diff --git a/py/Boinc/db_base.py b/py/Boinc/db_base.py index b503e941ed..3667246271 100644 --- a/py/Boinc/db_base.py +++ b/py/Boinc/db_base.py @@ -57,6 +57,14 @@ class Debug: debug = Debug() debug.mysql = not not os.environ.get('DEBUG_DB') +def _execute_sql(cursor, command): + '''Same as ``cursor.execute(command)``, but more verbose on error.''' + try: + cursor.execute(command) + except MySQLdb.MySQLError, e: + e.args += (command,) + raise e + def _commit_object(tablename, paramdict, id=None): """Takes a tablename, a parameter dict, and an optional id. Puts together the appropriate SQL command to commit the object to the database. @@ -77,14 +85,14 @@ def _commit_object(tablename, paramdict, id=None): (tablename, ', '.join(equalcommands)) if debug.mysql: debug.printline("query: "+command) - cursor.execute(command) + _execute_sql(cursor, command) id = cursor.insert_id() #porters note: works w/MySQLdb only else: command = "UPDATE %s SET %s WHERE id=%d" % \ (tablename, ', '.join(equalcommands), id) if debug.mysql: debug.printline("query: "+command) - cursor.execute(command) + _execute_sql(cursor, command) cursor.close() dbconnection.commit() return id @@ -101,7 +109,7 @@ def _remove_object(command, id=None): ' WHERE id=%d' % id if debug.mysql: debug.printline("query: "+command) - cursor.execute(command) + _execute_sql(cursor, command) cursor.close() dbconnection.commit() def _select_object(table, searchdict, extra_args="", extra_params=[], select_what=None): @@ -132,7 +140,7 @@ def _select_object(table, searchdict, extra_args="", extra_params=[], select_wha cursor = dbconnection.cursor() if debug.mysql: debug.printline("query: "+command) - cursor.execute(command) + _execute_sql(cursor, command) return cursor def _select_object_fetchall(*args, **kwargs): diff --git a/py/Boinc/projectxml.py b/py/Boinc/projectxml.py new file mode 100644 index 0000000000..71c7cfdfc6 --- /dev/null +++ b/py/Boinc/projectxml.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +# $Id$ + +# projectxml.py - module to read and parse project.xml + +''' +SYNOPSIS: parses and writes project.xml + +USAGE: from Boinc import projectxml + project = projectxml.ProjectFile().read() + project.commit_all() + +''' + +import sys +import boinc_path_config +import boinc_project_path +from Boinc.boincxml import * +from Boinc.add_util import * + +default_project_file = None + +class ProjectFile(XMLConfig): + ''' + embodies project.xml + ''' + default_filename = boinc_project_path.project_xml_filename + def __init__(self, *args, **kwargs): + XMLConfig.__init__(self, *args, **kwargs) + global default_project_file + default_project_file = self + def _get_elements(self): + self.xml_boinc = get_element(self.xml, 'boinc', optional=False) + self.add_objects_and_args = [] + for node in self.xml_boinc.childNodes: + add_object = add_objects.get(node.nodeName) + if not add_object: + raise SystemExit("Error in %s: No such object '%s' to add." %(self.filename,node.nodeName)) + self.add_objects_and_args.append((add_object, get_elements_as_dict(node))) + def _set_elements(self): + assert(0) # TODO (maybe) + def commit_all(self): + '''Commits all new data to the BOINC project database.''' + for add_object, untranslated_args_dict in self.add_objects_and_args: + try: + do_add_object(add_object, untranslated_args_dict, skip_old=True) + except AddObjectException, e: + raise SystemExit('Error in %s: %s' %(self.filename,e)) + + default_xml = '' + +def default_project(): + '''If any project file has been read, return it. Else open the default one + and return it.''' + if not default_project_file: ProjectFile().read() + assert(default_project_file) + return default_project_file diff --git a/tools/add b/tools/add index 2864de7262..71413651a3 100755 --- a/tools/add +++ b/tools/add @@ -6,7 +6,7 @@ #XXX TODO: add app should modify config.xml to add application-specific daemons ''' -add items to the BOINC database. +add items to the BOINC database -- command-line interface. See also ``xadd``. Usages: @@ -50,100 +50,10 @@ add workunit (TODO) add result (TODO) ''' import boinc_path_config -from Boinc import database, db_mid, configxml, tools +from Boinc import database, db_mid, configxml from Boinc.util import * -import sys, os, getopt, time, pprint - -CREATE_TIME = ['?create_time', int(time.time())] -TRANSITION_TIME = ['?transition_time', int(time.time())] - -class XCoreVersion(database.CoreVersion): - def __init__(self,**kwargs): - kwargs['xml_doc'] = tools.process_executable_file(kwargs['exec_file']) - del kwargs['exec_file'] - apply(database.CoreVersion.__init__,[self],kwargs) - -class XAppVersion(database.AppVersion): - def __init__(self,**kwargs): - kwargs['xml_doc'] = tools.process_app_version( - app = kwargs['app'], - version_num = int(kwargs['version_num']), - exec_files = kwargs['exec_files'], - signature_files = kwargs.setdefault('signature_files',{})) - del kwargs['signature_files'] - del kwargs['exec_files'] - del kwargs['exec_file'] - apply(database.AppVersion.__init__,[self],kwargs) - -# format: [ database.Object, args, ...] -# arg format: -# 'arg' -# '?arg' optional -# [ 'arg', default_value ] -list_objects_to_add = [ - [ database.Project, 'name', '?long_name' ], - [ database.Platform, 'name', 'user_friendly_name', CREATE_TIME ], - [ XCoreVersion, 'platform', 'version_num', 'exec_file', - ['?message',''], ['?message_priority','']], - [ database.App, 'name', ['?min_version',0], CREATE_TIME], - [ XAppVersion, 'app', 'platform', 'version_num', 'exec_file', '?signature_file', - CREATE_TIME ], - [ database.User, 'name', 'email_addr', 'authenticator', - ['?country','United States'], ['?postal_code','94703'], - '?global_prefs', '?global_prefs_file', - CREATE_TIME ], - # [ database.Workunit, 'zzzz' ], - ] - -most_recent_exec_file = None - -def translate_arg(object, arg, value, args_dict): - '''Translate various arguments''' - database_table = None - try: - database_table = database.__dict__[arg.capitalize()]._table - except: - pass - if database_table: - return (arg,translate_database_arg(database_table, arg, value)) - - if arg == 'global_prefs_file': - return ('global_prefs', open(value).read()) - - if object.DatabaseObject == XAppVersion: - # 'add app_version' accepts multiple '-exec_file's with - # '-signature_file' applying to the most recent exec_file - if arg == 'exec_file': - global most_recent_exec_file - most_recent_exec_file = value - args_dict.setdefault('exec_files',[]).append(value) - # since this is required, set it to None so that argument checker - # knows we got one; we'll delete it later. - return (arg,None) - if arg == 'signature_file': - args_dict.setdefault('signature_files',{})[most_recent_exec_file] = value - return (None,None) - - return (arg,value) - - -def translate_database_arg(database_table, arg, value): - '''Accept e.g. either --app Astropulse or --app 1''' - try: - id = int(value) - results = database_table.find(id=id) - if not results: - raise Exception("") - except: - results = database_table.find(name=value) - if len(results) == 0: - raise SystemExit('No %s "%s" found' %(arg,value)) - if len(results) > 1: - print >>sys.stderr, 'Too many %ss match "%s": '%(arg,value) - for result in results: - print >>sys.stderr, ' ', result.name - raise SystemExit - return results[0] +from Boinc.add_util import * +import sys, os, getopt def ambiguous_lookup(string, dict): results = [] @@ -156,6 +66,17 @@ def ambiguous_lookup(string, dict): results.append(dict[key]) return results +def lookup_object_to_add(name_of_object_to_add): + name_of_object_to_add = name_of_object_to_add.strip().lower() + possible_objects = ambiguous_lookup(name_of_object_to_add, add_objects) + if len(possible_objects) == 0: + raise SystemExit("No such object '%s' to add"%name_of_object_to_add) + if len(possible_objects) > 1: + print >>sys.stderr, "Object name '%s' matches multiple objects:"%name_of_object_to_add + for object in possible_objects: + print " ", object.name + raise SystemExit(1) + return possible_objects[0] def parse_global_options(args): # raise SystemExit('todo') @@ -179,62 +100,25 @@ def help_object(object, msg=None): print >>sys.stderr, dv(object,arg) raise SystemExit -def add_object(object, args): +def commandline_add_object(add_object, args): try: parsed_opts, placement_args = \ getopt.getopt(args, '', map(lambda s: s+'=', - object.args + object.optional_args)) + add_object.args + add_object.optional_args)) if placement_args: raise getopt.GetoptError('Unknown args '+' '.join(placement_args)) except getopt.GetoptError, e: - help_object(object, e) - args_dict = object.default_values.copy() + help_object(add_object, e) + untranslated_args_dict = {} for arg,value in parsed_opts: if not arg.startswith('--'): raise Exception('internal error: arg should start with "--"') - arg = arg[2:] - (arg,value) = translate_arg(object,arg,value,args_dict) - if not arg: continue - args_dict[arg] = value - for arg in object.args: - if not arg in args_dict: - help_object(object, 'required argument --%s not given'%arg) - - object = apply(object.DatabaseObject, [], args_dict) - object.commit() - print "Committed", object, "with args:" - pprint.pprint(object.__dict__) - -class Dict: - pass - -objects_to_add = {} -for o in list_objects_to_add: - object = Dict() - object.DatabaseObject = o[0] - object.name = object.DatabaseObject._table.table - object.args = [] - object.optional_args = [] - object.default_values = {} - for arg in o[1:]: - if isinstance(arg, list): - default_value = arg[1] - arg = arg[0] - else: - default_value = None - if arg.startswith('?'): - optional = True - arg = arg[1:] - else: - optional = False - if optional: - object.optional_args.append(arg) - else: - object.args.append(arg) - if default_value: - object.default_values[arg] = default_value - objects_to_add[object.name] = object + untranslated_args_dict[arg[2:]] = value + try: + do_add_object(add_object, untranslated_args_dict) + except AddObjectException, e: + help_object(add_object, e) if len(sys.argv) < 2: print >>sys.stderr, """Syntax: add @@ -247,6 +131,7 @@ Objects to add:""" print >>sys.stderr, """ Global options: --config=config.xml Path to configuration file. + --skip_old Ignore database objects that already exist These override config.xml: --db_name Database name @@ -259,16 +144,7 @@ arguments. raise SystemExit(1) -name_of_object_to_add = sys.argv[1].strip().lower() - -possible_objects = ambiguous_lookup(name_of_object_to_add, objects_to_add) -if len(possible_objects) == 0: - raise SystemExit("No such object '%s' to add"%name_of_object_to_add) -if len(possible_objects) > 1: - print >>sys.stderr, "Object name '%s' matches multiple objects:"%name_of_object_to_add - for object in possible_objects: - print " ", object.name - raise SystemExit(1) +add_object = lookup_object_to_add(sys.argv[1]) args = sys.argv[2:] parse_global_options(args) @@ -276,4 +152,4 @@ parse_global_options(args) config = configxml.default_config() database.connect(config.config) -add_object(possible_objects[0], args) +commandline_add_object(add_object, args) diff --git a/tools/make_project b/tools/make_project index 3d84671cda..58c06e6693 100755 --- a/tools/make_project +++ b/tools/make_project @@ -3,11 +3,6 @@ # $Id$ # Creates a new BOINC project. -# TODO: use database.py (mainly for setup_project.py) -# TODO: don't add app / app version here. -# TODO: create 'apps' subdirectory and set it in config.xml -# TODO: use configxml module - import boinc_path_config from Boinc.setup_project import * from Boinc import database, db_mid, configxml, tools diff --git a/tools/xadd b/tools/xadd new file mode 100755 index 0000000000..90e0c03f04 --- /dev/null +++ b/tools/xadd @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +# $Id$ + +import boinc_path_config +from Boinc import database, db_mid, configxml, projectxml + +config = configxml.default_config() +database.connect(config.config) + +projectxml.ProjectFile().read().commit_all() +