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()
+