class Application(object): def __init__(self, name='', badge='', statistics='', is_long=False, tasks=None): """use is_long = True when passed name is the long name (i.e. from wuprop)""" if name == None: raise ValueError('Name is None') if not(is_long): self.setName_shortLong(name) # Hopefully on the form Say No to Schistosoma (sn2s) else: self.name_short = name self.name_long = name if tasks is None: self.tasks = list() else: self.tasks = tasks self.badge = badge self.statistics = statistics @property def credit(self): try: return self.statistics.credit except: try: return self.badge.credit except: return 0 @property def runtime(self): try: return self.statistics.runtime except: try: return self.badge.runtime except: return datetime.timedelta(0) def setNameFromXML(self, xml): """ Expects the application block: <app> ... </app> from the boinc rpc """ soup = BeautifulSoup(xml, "xml") self.setNameFromSoup(soup) def setNameFromSoup(self, soup): self.name_short = soup.find('name').text # Vops: soup.name is 'reserved' so need to use find('name') user_friendly_name = soup.find('user_friendly_name').text #self.setName_shortLong(user_friendly_name) self.name_long = user_friendly_name def appendTaskFromXML(self, xml): t = task.Task_local.createFromXML(xml) self.tasks.append(t) return t def appendStatistics(self, statistics): #self.statistics += str(statistics) logger.debug('Appending application statistics "%s" to "%s"', statistics, self.statistics) # TODO: shorten the code? if self.statistics == '': if isinstance(statistics, StatisticsList): self.statistics = statistics else: self.statistics = StatisticsList([statistics]) else: if isinstance(statistics, StatisticsList): self.statistics.extend(statistics) else: self.statistics.append(statistics) # Name """Name should be on the form <long> (<short>), do a regexp when the name is set. name_long returns <long> and name_short returns <short>""" @property def name(self): return "{} ({})".format(self.name_long.encode('utf8'), self.name_short.encode('utf8')) def setName_shortLong(self, name): """ Sets self.name_long and self.name_short based on the following pattern <long> (<short>) """ try: reg = re.findall('([^(]+)\((\w+)\)', name) except TypeError as e: logging.exception('Expected string, got type = %s, "%s"', type(name), name) reg = [] if reg != []: reg = reduce(lambda x,y: x+y, reg) # flatten list name_long = "".join(reg[:-1]).strip() name_short = reg[-1].strip() else: name_long = name name_short = '' self.setName_long(name_long) self.name_short = name_short def setName_long(self, name_long=None): """ Removes the version part of the application long name (if any) """ if name_long is None: name_long = self.name_long reg = re.search('(v\d+.\d+)', name_long) if reg: s = reg.start() e = reg.end() self.version = reg.group() name_long = name_long[:s] + name_long[e:] name_long = name_long.replace(' ', ' ') else: name_long = name_long self.name_long = name_long.strip() def __str__(self): ret = ["= {} = {} {}".format(self.name, self.statistics, self.badge)] for t in self.tasks: ret.append(str(t)) return "\n".join(ret) def __len__(self): """ Number of tasks """ return len(self.tasks) def pendingTime(self, include_elapsedCPUtime=True): """Returns total seconds for pending, started and tasks waiting for validation. """ pending = 0 running = 0 validation = 0 for task in self.tasks: try: (p, r, v) = task.pendingTime(include_elapsedCPUtime=include_elapsedCPUtime) pending += p running += r validation += v except AttributeError: pass return pending, running, validation
class Project(object): def __init__(self, url=None, name=None, user=None, statistics=None, settings=None): self.name = name if name == None: self.setName(url) self.setUrl(url) self.user = user self.applications = dict() self.statistics = statistics self.settings = settings self.fileTransfers = list() # list of task like objects with files in transit (will mostly be empty) self._appNames = dict() # key is task name and value is application name self._badges = list() self.show_empty = False # # XML related # @staticmethod def createFromXML(xml): """ Expects the project block: <project> ... </project> from the boinc rpc """ soup = BeautifulSoup(xml, "xml") settings = Settings.createFromSoup(soup) # Get the statistics s = ProjectStatistics.createFromSoup(soup) url = soup.master_url.text name = soup.project_name.text return Project(url=url, name=name, statistics=s, settings=settings) def appendApplicationFromXML(self, xml): a = Application() a.setNameFromXML(xml) self.applications[a.name_long] = a return a def appendWorkunitFromXML(self, xml): # Currently, the only thing of interest is the mapping between name and app_name soup = BeautifulSoup(xml) name = soup.find('name').text app_name = soup.find('app_name').text self._appNames[name] = app_name return 'name %s, app_name %s' % (name, app_name) def appendResultFromXML(self, xml): t = Task_local.createFromXML(xml) try: app_name = self._appNames[t.name] except KeyError: raise KeyError('Unknown app_name for task %s, known names %s' % (t.name, self._appNames)) #logger.debug('trying to find app_name %s', app_name) for key, app in self.applications.items(): if app.name_short == app_name: app.tasks.append(t) break else: raise KeyError('Could not find app_name %s in list of applications' % app_name) return t # # HTML related # def appendApplicationShort(self, name_short): for key, app in self.applications.items(): if app.name_short == name_short: return app else: app = Application(name_short, is_long=True) self.applications[name_short] = app return app def appendApplication(self, name, is_long=False): app = Application(name=name, is_long=is_long) name_long = app.name_long if not(name_long in self.applications): self.applications[name_long] = app return self.applications[name_long] def appendBadge(self, app_name='', badge=''): # Normally badges are associated with an application, but numbersfields and wuprop associates with project instead. if app_name != '': app = self.appendApplication(app_name) logger.debug('Appending badge %s, to %s', badge, app_name) app.badge = badge return app else: self._badges.append((self, badge)) @property def badges(self): ret = list() for key, app in sorted(self.applications.items()): b = app.badge if b != '': ret.append((app, b)) ret.extend(self._badges) return ret def appendStatistics(self, statistics): if statistics is None: return logger.debug('Appending project statistics "%s" to "%s"', statistics, self.statistics) if self.statistics is None: self.statistics = StatisticsList([statistics]) elif isinstance(statistics, list): self.statistics.extend(statistics) else: self.statistics.append(statistics) def setName(self, name): if name == None: self.name = name return self.name = name.replace('https://', '') self.name = self.name.replace('http://', '') self.name = self.name.replace('www.', '') self.name = self.name.replace('.org', '') if self.name[-1] == '/': self.name = self.name[:-1] self.name = self.name.replace('/', '_') self.name = self.name#.capitalize() def setUrl(self, url): if url is None: self.url = url return if url.endswith('/'): self.url = url[:-1] else: self.url = url http = 'http://' ix = self.url.find(http) if ix != -1: name = self.url[ix+len(http):] if not(name.startswith('www.')): name = 'www.' + name self.url = http + name def __str__(self): ret = ["== {} ==".format(self.name.title())] for prop in [self.settings, self.statistics]: if prop != None: ret.append(str(prop)) for _, badge in self._badges: ret.append(str(badge)) for key in sorted(self.applications): if len(self.applications[key]) != 0 or self.show_empty: ret.append(str(self.applications[key])) if len(self.fileTransfers) != 0: ret.append('- File Transfers -') for t in self.fileTransfers: ret.append(str(t)) return "\n".join(ret) def __len__(self): """ Number of tasks """ n = 0 for key in self.applications: n += len(self.applications[key]) return n def tasks(self): """ Syntax sugar for generator for each task contained by project """ for key in sorted(self.applications): for task in self.applications[key].tasks: yield task