From 59df11a25adbdefa8075bb5f89de23786b581e45 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Wed, 18 Aug 2010 21:24:14 -0400 Subject: [PATCH] cleaning up .svn files --- mythtv/.svn/all-wcprops | 35 - mythtv/.svn/entries | 204 -- mythtv/.svn/prop-base/MythBase.py.svn-base | 5 - mythtv/.svn/prop-base/MythData.py.svn-base | 5 - mythtv/.svn/prop-base/MythFunc.py.svn-base | 5 - mythtv/.svn/prop-base/MythStatic.py.svn-base | 5 - mythtv/.svn/prop-base/__init__.py.svn-base | 17 - mythtv/.svn/text-base/MythBase.py.svn-base | 1868 ----------------- mythtv/.svn/text-base/MythData.py.svn-base | 1504 ------------- mythtv/.svn/text-base/MythFunc.py.svn-base | 1170 ----------- mythtv/.svn/text-base/MythStatic.py.svn-base | 13 - mythtv/.svn/text-base/__init__.py.svn-base | 54 - mythtv/tmdb/.svn/all-wcprops | 29 - mythtv/tmdb/.svn/entries | 164 -- .../tmdb/.svn/text-base/__init__.py.svn-base | 0 .../tmdb/.svn/text-base/tmdb_api.py.svn-base | 1005 --------- .../text-base/tmdb_exceptions.py.svn-base | 45 - .../tmdb/.svn/text-base/tmdb_ui.py.svn-base | 266 --- mythtv/ttvdb/.svn/all-wcprops | 41 - mythtv/ttvdb/.svn/entries | 232 -- .../ttvdb/.svn/text-base/__init__.py.svn-base | 0 mythtv/ttvdb/.svn/text-base/cache.py.svn-base | 232 -- .../text-base/ttvdb-example.conf.svn-base | 109 - .../ttvdb/.svn/text-base/tvdb_api.py.svn-base | 805 ------- .../text-base/tvdb_exceptions.py.svn-base | 48 - .../ttvdb/.svn/text-base/tvdb_ui.py.svn-base | 124 -- 26 files changed, 7985 deletions(-) delete mode 100644 mythtv/.svn/all-wcprops delete mode 100644 mythtv/.svn/entries delete mode 100644 mythtv/.svn/prop-base/MythBase.py.svn-base delete mode 100644 mythtv/.svn/prop-base/MythData.py.svn-base delete mode 100644 mythtv/.svn/prop-base/MythFunc.py.svn-base delete mode 100644 mythtv/.svn/prop-base/MythStatic.py.svn-base delete mode 100644 mythtv/.svn/prop-base/__init__.py.svn-base delete mode 100644 mythtv/.svn/text-base/MythBase.py.svn-base delete mode 100644 mythtv/.svn/text-base/MythData.py.svn-base delete mode 100644 mythtv/.svn/text-base/MythFunc.py.svn-base delete mode 100644 mythtv/.svn/text-base/MythStatic.py.svn-base delete mode 100644 mythtv/.svn/text-base/__init__.py.svn-base delete mode 100644 mythtv/tmdb/.svn/all-wcprops delete mode 100644 mythtv/tmdb/.svn/entries delete mode 100644 mythtv/tmdb/.svn/text-base/__init__.py.svn-base delete mode 100644 mythtv/tmdb/.svn/text-base/tmdb_api.py.svn-base delete mode 100644 mythtv/tmdb/.svn/text-base/tmdb_exceptions.py.svn-base delete mode 100644 mythtv/tmdb/.svn/text-base/tmdb_ui.py.svn-base delete mode 100644 mythtv/ttvdb/.svn/all-wcprops delete mode 100644 mythtv/ttvdb/.svn/entries delete mode 100644 mythtv/ttvdb/.svn/text-base/__init__.py.svn-base delete mode 100644 mythtv/ttvdb/.svn/text-base/cache.py.svn-base delete mode 100644 mythtv/ttvdb/.svn/text-base/ttvdb-example.conf.svn-base delete mode 100644 mythtv/ttvdb/.svn/text-base/tvdb_api.py.svn-base delete mode 100644 mythtv/ttvdb/.svn/text-base/tvdb_exceptions.py.svn-base delete mode 100644 mythtv/ttvdb/.svn/text-base/tvdb_ui.py.svn-base diff --git a/mythtv/.svn/all-wcprops b/mythtv/.svn/all-wcprops deleted file mode 100644 index edc90f4..0000000 --- a/mythtv/.svn/all-wcprops +++ /dev/null @@ -1,35 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 69 -/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV -END -MythStatic.py -K 25 -svn:wc:ra_dav:version-url -V 83 -/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythStatic.py -END -MythData.py -K 25 -svn:wc:ra_dav:version-url -V 81 -/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythData.py -END -MythBase.py -K 25 -svn:wc:ra_dav:version-url -V 81 -/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythBase.py -END -MythFunc.py -K 25 -svn:wc:ra_dav:version-url -V 81 -/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythFunc.py -END -__init__.py -K 25 -svn:wc:ra_dav:version-url -V 81 -/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/__init__.py -END diff --git a/mythtv/.svn/entries b/mythtv/.svn/entries deleted file mode 100644 index 8690d9b..0000000 --- a/mythtv/.svn/entries +++ /dev/null @@ -1,204 +0,0 @@ -10 - -dir -25726 -http://svn.mythtv.org/svn/tags/release-0-23-1/mythtv/bindings/python/MythTV -http://svn.mythtv.org/svn - - - -2010-07-22T02:21:00.112292Z -25396 -gigem - - - - - - - - - - - - - - -7dbf422c-18fa-0310-86e9-fd20926502f2 - -MythStatic.py -file - - - - -2010-08-18T01:49:43.000000Z -2645324c83c882ef4d85662fe2c3c9d7 -2010-07-22T02:21:00.112292Z -25396 -gigem -has-props - - - - - - - - - - - - - - - - - - - - -261 - -MythData.py -file - - - - -2010-08-18T01:49:43.000000Z -f973584ab4647d8e5f7a0eb16d51bf16 -2010-06-07T00:03:02.104550Z -25014 -wagnerrp -has-props - - - - - - - - - - - - - - - - - - - - -57441 - -ttvdb -dir - -MythBase.py -file - - - - -2010-08-18T01:49:43.000000Z -4e2f2010af14b04d6ebb3ead0606faa1 -2010-05-05T00:45:58.150174Z -24420 -wagnerrp -has-props - - - - - - - - - - - - - - - - - - - - -69983 - -MythFunc.py -file - - - - -2010-08-18T01:49:43.000000Z -3f6f4d43c4dc47e8c6ab2ab747d8042f -2010-05-23T19:52:40.992320Z -24818 -wagnerrp -has-props - - - - - - - - - - - - - - - - - - - - -41550 - -tmdb -dir - -__init__.py -file - - - - -2010-08-18T01:49:43.000000Z -fc69a94c478228940c6be7eee98a8903 -2010-06-22T03:35:41.632640Z -25154 -wagnerrp -has-props - - - - - - - - - - - - - - - - - - - - -1532 - diff --git a/mythtv/.svn/prop-base/MythBase.py.svn-base b/mythtv/.svn/prop-base/MythBase.py.svn-base deleted file mode 100644 index 869ac71..0000000 --- a/mythtv/.svn/prop-base/MythBase.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 14 -svn:executable -V 1 -* -END diff --git a/mythtv/.svn/prop-base/MythData.py.svn-base b/mythtv/.svn/prop-base/MythData.py.svn-base deleted file mode 100644 index 869ac71..0000000 --- a/mythtv/.svn/prop-base/MythData.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 14 -svn:executable -V 1 -* -END diff --git a/mythtv/.svn/prop-base/MythFunc.py.svn-base b/mythtv/.svn/prop-base/MythFunc.py.svn-base deleted file mode 100644 index 869ac71..0000000 --- a/mythtv/.svn/prop-base/MythFunc.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 14 -svn:executable -V 1 -* -END diff --git a/mythtv/.svn/prop-base/MythStatic.py.svn-base b/mythtv/.svn/prop-base/MythStatic.py.svn-base deleted file mode 100644 index 869ac71..0000000 --- a/mythtv/.svn/prop-base/MythStatic.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 14 -svn:executable -V 1 -* -END diff --git a/mythtv/.svn/prop-base/__init__.py.svn-base b/mythtv/.svn/prop-base/__init__.py.svn-base deleted file mode 100644 index 2880729..0000000 --- a/mythtv/.svn/prop-base/__init__.py.svn-base +++ /dev/null @@ -1,17 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -K 14 -svn:executable -V 1 -* -K 12 -svn:keywords -V 31 -Id Date Revision Author HeadURL -K 13 -svn:mime-type -V 13 -text/x-python -END diff --git a/mythtv/.svn/text-base/MythBase.py.svn-base b/mythtv/.svn/text-base/MythBase.py.svn-base deleted file mode 100644 index d42c32b..0000000 --- a/mythtv/.svn/text-base/MythBase.py.svn-base +++ /dev/null @@ -1,1868 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Provides base classes for accessing MythTV -""" - -from MythStatic import * - -import os, re, socket, sys, locale, weakref -import xml.etree.cElementTree as etree -from datetime import datetime -from time import sleep, time -from urllib import urlopen -from subprocess import Popen -from sys import version_info - -import MySQLdb, MySQLdb.cursors -MySQLdb.__version__ = tuple([v for v in MySQLdb.__version__.split('.')]) - -class DictData( object ): - """ - DictData.__init__(raw) -> DictData object - - Basic class for providing management of data resembling dictionaries. - Must be subclassed for use. - - Subclasses must provide: - field_order - string list of field names - field_type - integer list of field types by: - 0: integer - int(value) - 1: float - float(value) - 2: bool - bool(value) - 3: string - value - 4: date - datetime.fromtimestamp(int(value)) - can be the single string 'Pass', to pass all values untouched - - Data can be accessed as: - obj.field_name - -- or -- - obj['field_name'] - """ - - class DictIterator( object ): - def __init__(self, mode, parent): - # modes = 1:keys, 2:values, 3:items - self.index = 0 - self.mode = mode - self.data = parent - def __iter__(self): return self - def next(self): - self.index += 1 - if self.index > len(self.data.field_order): - raise StopIteration - key = self.data.field_order[self.index-1] - if self.mode == 1: - return key - elif self.mode == 2: - return self.data[key] - elif self.mode == 3: - return (key, self.data[key]) - - logmodule = 'Python DictData' - # class emulation functions - def __getattr__(self,name): - # function for class attribute access of data fields - # accesses real attributes, falls back to data fields, and errors - if name in self.__dict__: - return self.__dict__[name] - elif name in self.field_order: - return self.data[name] - else: - raise AttributeError("'DictData' object has no attribute '%s'"%name) - - def __setattr__(self,name,value): - # function for class attribute access of data fields - # sets value in data fields, falling back to real attributes - if name in self.field_order: - self.data[name] = value - else: - self.__dict__[name] = value - - # container emulation functions - def __getitem__(self,key): - # function for and dist-like access to content - # as a side effect of dual operation, dict data cannot be indexed - # keyed off integer values - if key in self.data: - return self.data[key] - else: - raise KeyError("'DictData' object has no key '%s'" %key) - - def __setitem__(self,key,value): - # function for dist-like access to content - # does not allow setting of data not already in field_order - if key in self.field_order: - self.data[key] = value - else: - raise KeyError("'DictData' does not allow new data") - - def __contains__(self,item): - return bool(item in self.data.keys()) - - def __iter__(self): - return self.DictIterator(2,self) - - def iterkeys(self): - """ - obj.iterkeys() -> an iterator over the keys of obj - ordered by self.field_order - """ - return self.DictIterator(1,self) - - def itervalues(self): - """ - obj.itervalues() -> an iterator over the values of obj - ordered by self.field_order - """ - return self.DictIterator(2,self) - - def iteritems(self): - """ - obj.iteritems() -> an iterator of over the (key,value) pairs of obj - ordered by self.field_order - """ - return self.DictIterator(3,self) - - def keys(self): - """obj.keys() -> list of self.field_order""" - return list(self.iterkeys()) - - def has_key(self,item): - return self.__contains__(item) - - def values(self): - """obj.values() -> list of values, ordered by self.field_order""" - return list(self.itervalues()) - - def get(self,key): - return self.data[key] - - def items(self): - """ - obj.items() -> list of (key,value) pairs, ordered by self.field_order - """ - return list(self.iteritems()) - - def pop(self,key): - raise NotImplementedError - - def popitem(self): - raise NotImplementedError - - def update(self, *args, **keywords): - self.data.update(*args, **keywords) - - ### additional management functions - def _setDefs(self): - self.data = {} - self.field = 0 - self.log = MythLog(self.logmodule) - - def __init__(self, raw): - self._setDefs() - self.data.update(self._process(raw)) - - def _process(self, data): - data = list(data) - for i in range(0, len(data)): - if data[i] == '': - data[i] = None - elif self.field_type == 'Pass': - data[i] = data[i] - elif self.field_type[i] == 0: - data[i] = int(data[i]) - elif self.field_type[i] == 1: - data[i] = locale.atof(data[i]) - elif self.field_type[i] == 2: - data[i] = bool(data[i]) - elif self.field_type[i] == 3: - data[i] = data[i] - elif self.field_type[i] == 4: - data[i] = datetime.fromtimestamp(int(data[i])) - return dict(zip(self.field_order,data)) - - @staticmethod - def joinInt(high,low): - """obj.joinInt(high, low) -> 64-bit int, from two signed ints""" - return (high + (low<0))*2**32 + low - - @staticmethod - def splitInt(integer): - """obj.joinInt(64-bit int) -> (high, low)""" - return integer/(2**32),integer%2**32 - (integer%2**32 > 2**31)*2**32 - -class DBData( DictData ): - """ - DBData.__init__(data=None, db=None, raw=None) --> DBData object - - Altered DictData adding several functions for dealing with individual rows. - Must be subclassed for use. - - Subclasses must provide: - table - Name of database table to be accessed - where - String defining WHERE clause for database lookup - setwheredat - String of comma separated variables to be evaluated to set - 'wheredat'. 'eval(setwheredat)' must return a tuple, so string must - contain at least one comma. - - Subclasses may provide: - allow_empty - Controls whether DBData() will allow an empty instance. - schema_value, schema_local, schema_name - Allows checking of alternate plugin schemas - - Can be populated in two manners: - data - Tuple used to perform a SQL query, using the subclass provided - 'where' clause - raw - Raw list as returned by 'select * from mytable' - """ - field_type = 'Pass' - allow_empty = False - logmodule = 'Python DBData' - schema_value = 'DBSchemaVer' - schema_local = SCHEMA_VERSION - schema_name = 'Database' - - def getAllEntries(self): - """obj.getAllEntries() -> tuple of DBData objects""" - c = self.db.cursor(self.log) - query = """SELECT * FROM %s""" % self.table - self.log(MythLog.DATABASE, query) - if c.execute(query) == 0: - return () - objs = [] - for row in c.fetchall(): - objs.append(self.__class__(db=self.db, raw=row)) - c.close() - return objs - - def _setDefs(self): - self.__dict__['field_order'] = self.db.tablefields[self.table] - DictData._setDefs(self) - self._fillNone() - self.wheredat = None - self.log = MythLog(self.logmodule, db=self.db) - - def __init__(self, data=None, db=None, raw=None): - self.__dict__['db'] = MythDBBase(db) - self.db._check_schema(self.schema_value, - self.schema_local, self.schema_name) - self._setDefs() - - if raw is not None: - if len(raw) == len(self.field_order): - self.data.update(self._process(raw)) - self.wheredat = eval(self.setwheredat) - else: - raise MythError('Incorrect raw input length to DBData()') - elif data is not None: - if None not in data: - self.wheredat = tuple(data) - self._pull() - else: - if self.allow_empty: - self._fillNone() - else: - raise MythError('DBData() not given sufficient information') - - def _pull(self): - """Updates table with data pulled from database.""" - c = self.db.cursor(self.log) - c.execute("""SELECT * FROM %s WHERE %s""" \ - % (self.table, self.where), self.wheredat) - data = c.fetchone() - c.close() - if data is None: - return - self.data.update(self._process(data)) - - def _fillNone(self): - """Fills out dictionary fields with empty data""" - self.field_order = self.db.tablefields[self.table] - for field in self.field_order: - self.data[field] = None - -class DBDataWrite( DBData ): - """ - DBDataWrite.__init__(data=None, db=None, raw=None) --> DBDataWrite object - - Altered DBData, with support for writing back to the database. - Must be subclassed for use. - - Subclasses must provide: - table - Name of database table to be accessed - where - String defining WHERE clause for database lookup - setwheredat - String of comma separated variables to be evaluated to set - 'wheredat'. 'eval(setwheredat)' must return a tuple, so string must - contain at least one comma. - - Subclasses may provide: - defaults - Dictionary of default values to be used when creating new - database entries. Additionally, values of 'None' will be stripped - and not used to alter the database. - - Can be populated in two manners: - data - Tuple used to perform a SQL query, using the subclass provided - 'where' clause - raw - Raw list as returned by 'select * from mytable' - Additionally, can be left uninitialized to allow creation of a new entry - """ - defaults = None - allow_empty = True - logmodule = 'Python DBDataWrite' - - def _sanitize(self, data, fill=True): - """Remove fields from dictionary that are not in database table.""" - data = data.copy() - for key in data.keys(): - if key not in self.field_order: - del data[key] - if self.defaults is not None: - for key in self.defaults: - if key in data: - if self.defaults[key] is None: - del data[key] - elif data[key] is None: - if fill: - data[key] = self.defaults[key] - return data - - def _setDefs(self): - DBData._setDefs(self) - self._fillDefs() - - def _fillDefs(self): - self._fillNone() - self.data.update(self.defaults) - - def __init__(self, data=None, db=None, raw=None): - DBData.__init__(self, data, db, raw) - if raw is not None: - self.origdata = self.data.copy() - - def create(self,data=None): - """ - obj.create(data=None) -> new database row - - Creates a new database entry using given information. - Will add any information in 'data' dictionary to local information - before pushing the entire set onto the database. - Will only function with an uninitialized object. - """ - if self.wheredat is not None: - raise MythError('DBDataWrite object already bound to '+\ - 'existing instance') - - if data is not None: - data = self._sanitize(data, False) - self.data.update(data) - data = self._sanitize(self.data) - for key in data.keys(): - if data[key] is None: - del data[key] - c = self.db.cursor(self.log) - fields = ', '.join(data.keys()) - format_string = ', '.join(['%s' for d in data.values()]) - c.execute("""INSERT INTO %s (%s) VALUES(%s)""" \ - % (self.table, fields, format_string), data.values()) - intid = c.lastrowid - c.close() - return intid - - def _pull(self): - DBData._pull(self) - self.origdata = self.data.copy() - - def _push(self): - if (self.where is None) or (self.wheredat is None): - return - c = self.db.cursor(self.log) - data = self._sanitize(self.data) - for key, value in data.items(): - if value == self.origdata[key]: - # filter unchanged data - del data[key] - if len(data) == 0: - # no updates - return - format_string = ', '.join(['%s = %%s' % d for d in data]) - sql_values = data.values() - sql_values.extend(self.wheredat) - - c.execute("""UPDATE %s SET %s WHERE %s""" \ - % (self.table, format_string, self.where), sql_values) - c.close() - self._pull() - - def update(self, *args, **keywords): - """ - obj.update(*args, **keywords) -> None - - Follows dict.update() syntax. Updates local information, and pushes - changes onto the database. - Will only function on an initialized object. - """ - - data = {} - data.update(*args, **keywords) - self.data.update(self._sanitize(data)) - self._push() - - def delete(self): - """ - obj.delete() -> None - - Delete video entry from database. - """ - if (self.where is None) or \ - (self.wheredat is None) or \ - (self.data is None): - return - c = self.db.cursor(self.log) - c.execute("""DELETE FROM %s WHERE %s""" \ - % (self.table, self.where), self.wheredat) - c.close() - -class DBDataRef( object ): - """ - DBDataRef.__init__(where, db=None) --> DBDataRef object - - Class for managing lists of referenced data, such as recordedmarkup - Subclasses must provide: - table - Name of database table to be accessed - wfield - list of fields for WHERE argument in lookup - - Can be populated by supplying a tuple, matching the fields specified in wfield - """ - logmodule = 'Python DBDataRef' - - class SubData( DictData ): - def _setDefs(self): - self.__dict__['field_order'] = [] - DictData._setDefs(self) - def __repr__(self): - return str(tuple(self)) - def __init__(self,data,fields): - self._setDefs() - self.field_order = fields - self.field_type = 'Pass' - self.data.update(self._process(data)) - def __hash__(self): - dat = self.data.copy() - keys = dat.keys() - keys.sort() - return hash(str([hash(dat[k]) for k in keys])) - def __cmp__(self, x): - return cmp(hash(self),hash(x)) - - def _setDefs(self): - self.data = None - self.hash = None - self.log = MythLog(self.logmodule, db=self.db) - - def __repr__(self): - if self.data is None: - self._fill() - return "" % \ - (str(tuple(self.data)), hex(id(self))) - - def __getitem__(self,index): - if self.data is None: - self._fill() - if index in range(-len(self.data),0)+range(len(self.data)): - return self.data[index] - else: - raise IndexError - - def __iter__(self): - self.field = 0 - if self.data is None: - self._fill() - return self - - def next(self): - if self.field == len(self.data): - del self.field - raise StopIteration - self.field += 1 - return self[self.field-1] - - def __init__(self,where,db=None): - self.db = MythDBBase(db) - self._setDefs() - self.where = tuple(where) - self.dfield = list(self.db.tablefields[self.table]) - for field in self.wfield: - del self.dfield[self.dfield.index(field)] - - def _fill(self): - self.data = [] - self.hash = [] - fields = ','.join(self.dfield) - where = ' AND '.join('%s=%%s' % d for d in self.wfield) - c = self.db.cursor(self.log) - c.execute("""SELECT %s FROM %s WHERE %s""" \ - % (fields, self.table, where), self.where) - for entry in c.fetchall(): - self.data.append(self.SubData(entry, self.dfield)) - self.hash.append(hash(self.data[-1])) - - def add(self, data): - """obj.add(data) -> None""" - if self.data is None: - self._fill() - if len(data) != len(self.wfield): - return - - dat = self.SubData(data, self.dfield) - if hash(dat) in self.hash: - return - - c = self.db.cursor(self.log) - fields = ', '.join(self.wfield+self.dfield) - format = ', '.join(['%s' for d in fields]) - c.execute("""INSERT INTO %s (%s) VALUES(%s)""" \ - % (self.table, fields, format), - self.where+list(data)) - c.close() - - self.data.append(dat) - self.hash.append(hash(dat)) - - def delete(self, index): - """obj.delete(data) -> None""" - if self.data is None: - self._fill() - if index not in range(0, len(self.data)): - return - - where = ' AND '.join(['%s=%%s' % d for d in self.wfield+self.dfield]) - values = self.where+list(self.data[index]) - c = self.db.cursor(self.log) - c.execute("""DELETE FROM %s WHERE %s""" \ - % (self.table, where), values) - c.close() - - del self.data[index] - del self.hash[index] - - -class DBDataCRef( object ): - """ - DBDataRef.__init__(where, db=None) --> DBDataRef object - - Class for managing lists of crossreferenced data, such as recordedcredits - Subclasses must provide: - table - primary data table - rtable - cross reference table - t_ref - primary table field to align with cross reference table - r_ref - cross reference table field to align with primary table - t_add - list of fields for data stored in primary table - r_add - list of fields for data stored in cross reference table - w_field - list of fields for WHERE argument in lookup - """ - logmodule = 'Python DBDataCRef' - - class SubData( DictData ): - def _setDefs(self): - self.__dict__['field_order'] = [] - DictData._setDefs(self) - def __repr__(self): - return str(tuple(self.data.values())) - def __init__(self,data,fields): - self._setDefs() - self.field_order = ['cross']+fields - self.field_type = 'Pass' - self.data.update(self._process(data)) - def __hash__(self): - dat = self.data.copy() - del dat['cross'] - keys = dat.keys() - keys.sort() - return hash(str([hash(dat[k]) for k in keys])) - def __cmp__(self, x): - return cmp(hash(self),hash(x)) - - def _setDefs(self): - self.data = None - self.hash = None - self.log = MythLog(self.logmodule, db=self.db) - - def __repr__(self): - if self.data is None: - self._fill() - return "" % \ - (str(tuple(self.data)), hex(id(self))) - - def __getitem__(self,index): - if self.data is None: - self._fill() - if index in range(-len(self.data),0)+range(len(self.data)): - return self.data[index] - else: - raise IndexError - - def __iter__(self): - self.field = 0 - if self.data is None: - self._fill() - return self - - def next(self): - if self.field == len(self.data): - del self.field - raise StopIteration - self.field += 1 - return self[self.field-1] - - def __init__(self,where,db=None): - self.db = MythDBBase(db) - self._setDefs() - self.w_dat = tuple(where) - - def _fill(self): - self.data = [] - self.hash = [] - fields = ','.join([self.table+'.'+self.t_ref]+self.t_add+self.r_add) - where = ' AND '.join('%s=%%s' % d for d in self.w_field) - join = '%s.%s=%s.%s' % (self.table,self.t_ref,self.rtable,self.r_ref) - c = self.db.cursor(self.log) - c.execute("""SELECT %s FROM %s JOIN %s ON %s WHERE %s"""\ - % (fields, self.table, self.rtable, join, where), - self.w_dat) - for entry in c.fetchall(): - self.data.append(self.SubData(entry, self.t_add+self.r_add)) - self.hash.append(hash(self.data[-1])) - - def _tdata(self, sub): - return tuple([sub[key] for key in self.t_add]) - - def _rdata(self, sub): - return tuple([sub[key] for key in self.r_add+['cross']]+list(self.w_dat)) - - def _search(self, sub): - for i in range(len(self.data)): - if self.data[i] == sub: - return i - - def add(self,data): - """obj.add(data) -> None""" - if self.data is None: - self._fill() - if len(data) != len(self.t_add+self.r_add): - return - - # check for existing - dat = self.SubData([0]+list(data),self.t_add+self.r_add) - if dat in self.data: - return - - tdata = self._tdata(dat) - - c = self.db.cursor(self.log) - # search for existing table data, add if needed - where = ' AND '.join('%s=%%s' % d for d in self.t_add) - if c.execute("""SELECT %s FROM %s WHERE %s""" \ - % (self.t_ref, self.table, where), - self._tdata(dat)) > 0: - id = c.fetchone()[0] - else: - fields = ', '.join(self.t_add) - format = ', '.join(['%s' for d in self.t_add]) - c.execute("""INSERT INTO %s (%s) VALUES(%s)""" \ - % (self.table, fields, format), self._tdata(dat)) - id = c.lastrowid - - dat = self.SubData([id]+list(data),self.t_add+self.r_add) - - # double check for existing - fields = self.r_add+[self.r_ref]+self.w_field - where = ' AND '.join('%s=%%s' % d for d in fields) - if c.execute("""SELECT * FROM %s WHERE %s""" % (self.rtable, where), - self._rdata(dat)) > 0: - return - - # add cross reference - fields = ', '.join(self.r_add+[self.r_ref]+self.w_field) - format = ', '.join(['%s' for d in fields.split(', ')]) - c.execute("""INSERT INTO %s (%s) VALUES(%s)"""\ - % (self.rtable, fields, format), self._rdata(dat)) - c.close() - - # add local entry - self.data.append(dat) - - def delete(self,data): - """obj.delete(data) -> None""" - if self.data is None: - self._fill() - # check for local entry - dat = self.SubData([0]+list(data),self.t_add+self.r_add) - if dat not in self.data: - return - # remove local entry - index = self._search(dat) - dat = self.data.pop(index) - - # remove database cross reference - fields = self.r_add+[self.r_ref]+self.w_field - where = ' AND '.join('%s=%%s' % d for d in fields) - c = self.db.cursor(self.log) - c.execute("""DELETE FROM %s WHERE %s""" % (self.rtable, where), - self._rdata(dat)) - - # remove primary entry if unused - if c.execute("""SELECT %s FROM %s WHERE %s=%%s""" \ - % (self.r_ref, self.rtable, self.r_ref), - dat.cross) == 0: - c.execute("""DELETE FROM %s WHERE %s=%%s""" \ - % (self.table, self.t_ref), dat.cross) - c.close() - - index = self.hash.index[dat] - del self.hash[index] - del self.data[index] - -class MythDBCursor( MySQLdb.cursors.Cursor ): - """ - Custom cursor, offering logging and error handling - """ - def __init__(self, connection): - self.log = None - MySQLdb.cursors.Cursor.__init__(self, connection) - - def execute(self, query, args=None): - if MySQLdb.__version__ >= ('1','2','2'): - self.connection.ping(True) - else: - self.connection.ping() - if self.log == None: - self.log = MythLog('Python Database Connection') - if args: - self.log(self.log.DATABASE, ' '.join(query.split()), str(args)) - else: - self.log(self.log.DATABASE, ' '.join(query.split())) - try: - return MySQLdb.cursors.Cursor.execute(self, query, args) - except Exception, e: - raise MythDBError(MythDBError.DB_RAW, e.args) - - def executemany(self, query, args): - if MySQLdb.__version__ >= ('1','2','2'): - self.connection.ping(True) - else: - self.connection.ping() - if self.log == None: - self.log = MythLog('Python Database Connection') - for arg in args: - self.log(self.log.DATABASE, ' '.join(query.split()), str(arg)) - try: - return MySQLdb.cursors.Cursor.executemany(self, query, args) - except Exception, e: - raise MythDBError(MythDBError.DB_RAW, e.args) - -class MythDBConn( object ): - """ - This is the basic database connection object. - You dont want to use this directly. - """ - - def __init__(self, dbconn): - self.log = MythLog('Python Database Connection') - self.tablefields = {} - self.settings = {} - try: - self.log(MythLog.DATABASE, "Attempting connection", - str(dbconn)) - self.db = MySQLdb.connect( user= dbconn['DBUserName'], - host= dbconn['DBHostName'], - passwd= dbconn['DBPassword'], - db= dbconn['DBName'], - port= dbconn['DBPort'], - use_unicode=True, - charset='utf8') - except: - raise MythDBError(MythError.DB_CONNECTION, dbconn) - - def cursor(self, log=None, type=MythDBCursor): - if MySQLdb.__version__ >= ('1','2','2'): - self.db.ping(True) - else: - self.db.ping() - c = self.db.cursor(type) - if log: - c.log = log - else: - c.log = self.log - return c - -class MythDBBase( object ): - """ - MythDBBase(db=None, args=None, **kwargs) -> database connection object - - Basic connection to the mythtv database - Offers connection caching to prevent multiple connections - - 'db' will accept an existing MythDBBase object, or any subclass there of - 'args' will accept a tuple of 2-tuples for connection settings - 'kwargs' will accept a series of keyword arguments for connection settings - 'args' and 'kwargs' accept the following values: - DBHostName - DBName - DBUserName - DBPassword - SecurityPin - - The class will first use an existing connection if provided, use the 'args' - and 'kwargs' values if sufficient information is available, falling - back to '~/.mythtv/config.xml' and finally UPnP detection. If no - 'SecurityPin' is given, UPnP detection will default to 0000, and if - successful, will populate '~/.mythtv/config.xml' with the necessary - information. - - Available methods: - obj.cursor() - open a cursor for direct database - manipulation - obj.getStorageGroup() - return a tuple of StorageGroup objects - """ - logmodule = 'Python Database Connection' - cursorclass = MythDBCursor - shared = weakref.WeakValueDictionary() - - def __repr__(self): - return "<%s '%s' at %s>" % \ - (str(self.__class__).split("'")[1].split(".")[-1], - self.ident, hex(id(self))) - - class _TableFields(object): - """Provides a dictionary-like list of table fieldnames""" - def __init__(self, db, log): - self._db = db - self._log = log - def __getattr__(self,key): - if key in self.__dict__: - return self.__dict__[key] - elif key in self._db.tablefields: - return self._db.tablefields[key] - else: - return self.__getitem__(key) - def __getitem__(self,key): - if key in self._db.tablefields: - return self._db.tablefields[key] - table_names = [] - c = self._db.cursor(self._log) - try: - c.execute("DESC %s" % (key,)) - except: - return None - - for name in c.fetchall(): - table_names.append(name[0]) - c.close() - self._db.tablefields[key] = tuple(table_names) - return table_names - - class _Settings(object): - """Provides dictionary-like list of hosts""" - class __Settings(object): - """Provides dictionary-like list of settings""" - def __repr__(self): - return str(self._shared.keys()) - def __init__(self, db, log, host): - self._db = db - self._log = log - self._host = host - def __getattr__(self,name): - if name in self.__dict__: - return self.__dict__[name] - else: - return self.__getitem__(name) - def __setattr__(self,name,value): - if name in ('_db','_host','_log'): - self.__dict__[name] = value - else: - self.__setitem__(name,value) - def __getitem__(self,key): - if key in self._db.settings[self._host]: - return self._db.settings[self._host][key] - if self._host == 'NULL': - where = 'IS NULL' - wheredat = (key,) - else: - where = 'LIKE(%s)' - wheredat = (key, self._host) - c = self._db.cursor(self._log) - c.execute("""SELECT data FROM settings - WHERE value=%%s AND hostname %s - LIMIT 1""" % where, wheredat) - row = c.fetchone() - c.close() - if row: - self._db.settings[self._host][key] = row[0] - return row[0] - else: - return None - def __setitem__(self, key, value): - if key not in self._db.settings[self._host]: - self.__getitem__(key) - c = self._db.cursor(self._log) - if key not in self._db.settings[self._host]: - host = self._host - if host is 'NULL': - host = None - c.execute("""INSERT INTO settings (value,data,hostname) - VALUES (%s,%s,%s)""", (key, value, host)) - else: - query = """UPDATE settings SET data=%s - WHERE value=%s AND""" - dat = [value, key] - if self._host == 'NULL': - query += ' hostname IS NULL' - else: - query += ' hostname=%s' - dat.append(self._host) - c.execute(query, dat) - self._db.settings[self._host][key] = value - def __repr__(self): - return str(self._db.settings.keys()) - def __init__(self, db, log): - self._db = db - self._log = log - def __getattr__(self,key): - if key in self.__dict__: - return self.__dict__[key] - else: - return self.__getitem__(key) - def __getitem__(self,key): - if key not in self._db.settings: - self._db.settings[key] = {} - return self.__Settings(self._db, self._log, key) - - def __init__(self, db=None, args=None, **dbconn): - self.db = None - self.log = MythLog(self.logmodule) - self.settings = None - if db is not None: - # load existing database connection - self.log(MythLog.DATABASE, "Loading existing connection", - str(db.dbconn)) - dbconn.update(db.dbconn) - if args is not None: - # load user defined arguments (takes dict, or key/value tuple) - self.log(MythLog.DATABASE, "Loading user settings", str(args)) - dbconn.update(args) - if 'SecurityPin' not in dbconn: - dbconn['SecurityPin'] = 0 - if not self._check_dbconn(dbconn): - # insufficient information for connection given - # try to read from config.xml - config_files = [ os.path.expanduser('~/.mythtv/config.xml') ] - if 'MYTHCONFDIR' in os.environ: - config_files.append('%s/config.xml' %os.environ['MYTHCONFDIR']) - - for config_file in config_files: - dbconn.update({ 'DBHostName':None, 'DBName':None, - 'DBUserName':None, 'DBPassword':None, - 'DBPort':0}) - try: - config = etree.parse(config_file).getroot() - for child in config.find('UPnP').find('MythFrontend').\ - find('DefaultBackend').getchildren(): - if child.tag in dbconn: - dbconn[child.tag] = child.text - except: - continue - - if self._check_dbconn(dbconn): - self.log(MythLog.IMPORTANT|MythLog.DATABASE, - "Using connection settings from %s" % config_file) - break - else: - # fall back to UPnP - dbconn = self._listenUPNP(dbconn['SecurityPin'], 5.0) - - # push data to new settings file - settings = [dbconn[key] for key in \ - ('SecurityPin','DBHostName','DBUserName', - 'DBPassword','DBName','DBPort')] - config = """ - - - - - - - - %s - %s - %s - %s - %s - %s - - - - -""" % tuple(settings) - mythdir = os.path.expanduser('~/.mythtv') - if not os.access(mythdir, os.F_OK): - os.mkdir(mythdir,0755) - fp = open(mythdir+'/config.xml', 'w') - fp.write(config) - fp.close() - - if 'DBPort' not in dbconn: - dbconn['DBPort'] = 3306 - else: - dbconn['DBPort'] = int(dbconn['DBPort']) - if dbconn['DBPort'] == 0: - dbconn['DBPort'] = 3306 - - self.dbconn = dbconn - self.ident = "sql://%s@%s:%d/" % \ - (dbconn['DBName'],dbconn['DBHostName'],dbconn['DBPort']) - if self.ident in self.shared: - # reuse existing database connection and update count - self.db = self.shared[self.ident] - else: - # attempt new connection - self.db = MythDBConn(dbconn) - - # check schema version - self._check_schema('DBSchemaVer',SCHEMA_VERSION) - - # add connection to cache - self.shared[self.ident] = self.db - - # connect to table name cache - self.tablefields = self._TableFields(self.shared[self.ident], self.log) - self.settings = self._Settings(self.shared[self.ident], self.log) - - def _listenUPNP(self, pin, timeout): - # open outbound socket - - upnpport = 1900 - upnptup = ('239.255.255.250', upnpport) - sreq = '\r\n'.join(['M-SEARCH * HTTP/1.1', - 'HOST: %s:%s' % upnptup, - 'MAN: "ssdp:discover"', - 'MX: 5', - 'ST: ssdp:all','']) - - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.IPPROTO_UDP) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - try: - sock.bind(('', upnpport)) - except: - raise MythDBError(MythError.DB_CREDENTIALS) - sock.setblocking(0) - - # spam the request a couple times - sock.sendto(sreq, upnptup) - sock.sendto(sreq, upnptup) - sock.sendto(sreq, upnptup) - - reLOC = re.compile('http://(?P[0-9\.]+):(?P[0-9]+)/.*') - - # listen for - atime = time()+timeout - while time() < atime: - sleep(0.1) - try: - sdata, saddr = sock.recvfrom(2048) - except socket.error: - continue # on fault from nonblocking recv - - lines = sdata.split('\n') - sdict = {'request':lines[0].strip()} - for line in lines[1:]: - fields = line.split(':',1) - if len(fields) == 2: - sdict[fields[0].strip().lower()] = fields[1].strip() - - if 'st' not in sdict: - continue - if sdict['st'] not in \ - ('urn:schemas-mythtv-org:device:MasterMediaServer:1', - 'urn:schemas-mythtv-org:device:SlaveMediaServer:1', - 'urn:schemas-upnp-org:device:MediaServer:1'): - continue - ip, port = reLOC.match(sdict['location']).group(1,2) - xml = MythXMLConn(backend=ip, port=port) - dbconn = xml.getConnectionInfo(pin) - if self._check_dbconn(dbconn): - break - - else: - sock.close() - raise MythDBError(MythError.DB_CREDENTIALS) - - sock.close() - return dbconn - - def _check_dbconn(self,dbconn): - reqs = ['DBHostName','DBName','DBUserName','DBPassword'] - for req in reqs: - if req not in dbconn: - return False - if dbconn[req] is None: - return False - return True - - def _check_schema(self, value, local, name='Database'): - if self.settings is None: - c = self.cursor(self.log) - lines = c.execute("""SELECT data FROM settings - WHERE value LIKE(%s)""",(value,)) - if lines == 0: - c.close() - raise MythDBError(MythError.DB_SETTING, value) - - sver = int(c.fetchone()[0]) - c.close() - else: - sver = int(self.settings['NULL'][value]) - - if local != sver: - self.log(MythLog.DATABASE|MythLog.IMPORTANT, - "%s schema mismatch: we speak %d but database speaks %s" \ - % (name, local, sver)) - self.db = None - raise MythDBError(MythError.DB_SCHEMAMISMATCH, value, sver, local) - - def getStorageGroup(self, groupname=None, hostname=None): - """ - obj.getStorageGroup(groupname=None, hostname=None) - -> tuple of StorageGroup objects - groupname and hostname can be used as optional filters - """ - c = self.cursor(self.log) - where = [] - wheredat = [] - if groupname: - where.append("groupname=%s") - wheredat.append(groupname) - if hostname: - where.append("hostname=%s") - wheredat.append(hostname) - if len(where): - where = 'WHERE '+' AND '.join(where) - c.execute("""SELECT * FROM storagegroup %s ORDER BY id""" \ - % where, wheredat) - else: - c.execute("""SELECT * FROM storagegroup ORDER BY id""") - - ret = [] - for row in c.fetchall(): - ret.append(StorageGroup(raw=row,db=self)) - return ret - - def cursor(self, log=None): - if not log: - log = self.log - return self.db.cursor(log, self.cursorclass) - -class MythBEConn( object ): - """ - This is the basic backend connection object. - You probably dont want to use this directly. - """ - logmodule = 'Python Backend Connection' - - def __init__(self, backend, type, db=None): - """ - MythBEConn(backend, type, db=None) -> backend socket connection - - 'backend' can be either a hostname or IP address, or will default - to the master backend if None. - 'type' is any value, as required by obj.announce(type). The stock - method passes 'Monitor' or 'Playback' during connection to - backend. There is no checking on this input. - 'db' will accept an existing MythDBBase object, - or any subclass there of. - """ - self.connected = False - self.db = MythDBBase(db) - self.log = MythLog(self.logmodule, db=self.db) - if backend is None: - # use master backend, no sanity checks, these should always be set - self.host = self.db.settings.NULL.MasterServerIP - self.port = self.db.settings.NULL.MasterServerPort - else: - if re.match('(?:\d{1,3}\.){3}\d{1,3}',backend): - # process ip address - c = self.db.cursor(self.log) - if c.execute("""SELECT hostname FROM settings - WHERE value='BackendServerIP' - AND data=%s""", backend) == 0: - c.close() - raise MythError(MythError.DB_SETTING, - 'BackendServerIP', backend) - backend = c.fetchone()[0] - c.close() - self.host = self.db.settings[backend].BackendServerIP - if not self.host: - raise MythDBError(MythError.DB_SETTING, - 'BackendServerIP', backend) - self.port = self.db.settings[backend].BackendServerPort - if not self.port: - raise MythDBError(MythError.DB_SETTING, - 'BackendServerPort', backend) - self.port = int(self.port) - - try: - self.log(MythLog.SOCKET|MythLog.NETWORK, - "Connecting to backend %s:%d" % (self.host, self.port)) - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.settimeout(10) - self.socket.connect((self.host, self.port)) - self.check_version() - self.announce(type) - self.connected = True - except socket.error, e: - self.log(MythLog.IMPORTANT|MythLog.SOCKET, - "Couldn't connect to backend %s:%d" \ - % (self.host, self.port)) - raise MythBEError(MythError.PROTO_CONNECTION, self.host, self.port) - except: - raise - - def announce(self,type): - res = self.backendCommand('ANN %s %s 0' % (type, socket.gethostname())) - if res != 'OK': - self.log(MythLog.IMPORTANT|MythLog.NETWORK, - "Unexpected answer to ANN", res) - raise MythBEError(MythError.PROTO_ANNOUNCE, - self.host, self.port, res) - else: - self.log(MythLog.SOCKET,"Successfully connected to backend", - "%s:%d" % (self.host, self.port)) - self.hostname = self.backendCommand('QUERY_HOSTNAME') - - def check_version(self): - res = self.backendCommand('MYTH_PROTO_VERSION %s' \ - % PROTO_VERSION).split(BACKEND_SEP) - if res[0] == 'REJECT': - self.log(MythLog.IMPORTANT|MythLog.NETWORK, - "Backend has version %s, and we speak %d" %\ - (res[1], PROTO_VERSION)) - raise MythBEError(MythError.PROTO_MISMATCH, - int(res[1]), PROTO_VERSION) - - def backendCommand(self, data): - """ - obj.backendCommand(data) -> response string - - Sends a formatted command via a socket to the mythbackend. - """ - def recv(): - """ - Reads the data returned from the backend. - """ - # The first 8 bytes of the response gives us the length - data = self.socket.recv(8) - try: - length = int(data) - except: - return u'' - data = [] - while length > 0: - chunk = self.socket.recv(length) - length = length - len(chunk) - data.append(chunk) - try: - return unicode(''.join(data),'utf8') - except: - return u''.join(data) - - data = data.encode('utf-8') - command = '%-8d%s' % (len(data), data) - self.log(MythLog.NETWORK, "Sending command", command) - self.socket.send(command) - return recv() - - def __del__(self): - if self.connected: - self.log(MythLog.SOCKET|MythLog.NETWORK, - "Terminating connection to %s:%d" % (self.host, self.port)) - self.backendCommand('DONE') - self.socket.shutdown(1) - self.socket.close() - self.connected = False - -class MythBEBase( object ): - """ - MythBEBase(backend=None, type='Monitor', db=None) - -> MythBackend connection object - - Basic class for mythbackend socket connections. - Offers connection caching to prevent multiple connections. - - 'backend' allows a hostname or IP address to connect to. If not provided, - connect will be made to the master backend. Port is always - taken from the database. - 'db' allows an existing database object to be supplied. - 'type' specifies the type of connection to declare to the backend. Accepts - 'Monitor' or 'Playback'. - - Available methods: - joinInt() - convert two signed ints to one 64-bit - signed int - splitInt() - convert one 64-bit signed int to two - signed ints - backendCommand() - Sends a formatted command to the backend - and returns the response. - """ - logmodule = 'Python Backend Connection' - shared = weakref.WeakValueDictionary() - - def __repr__(self): - return "<%s 'myth://%s:%d/' at %s>" % \ - (str(self.__class__).split("'")[1].split(".")[-1], - self.hostname, self.port, hex(id(self))) - - def __init__(self, backend=None, type='Monitor', db=None): - self.db = MythDBBase(db) - self.log = MythLog(self.logmodule, db=self.db) - self._ident = '%s@%s' % (type, backend) - if self._ident in self.shared: - self.be = self.shared[self._ident] - else: - self.be = MythBEConn(backend, type, db) - self.shared[self._ident] = self.be - self.hostname = self.be.hostname - self.port = self.be.port - - def backendCommand(self, data): - """ - obj.backendCommand(data) -> response string - - Sends a formatted command via a socket to the mythbackend. - """ - return self.be.backendCommand(data) - - def joinInt(self,high,low): - """obj.joinInt(high, low) -> 64-bit int, from two signed ints""" - return (high + (low<0))*2**32 + low - - def splitInt(self,integer): - """obj.joinInt(64-bit int) -> (high, low)""" - return integer/(2**32),integer%2**32 - (integer%2**32 > 2**31)*2**32 - - -class MythXMLConn( object ): - """ - MythXMLConn(backend=None, db=None, port=None) -> Backend status object - - Basic access to MythBackend status page and XML data server - - 'backend' allows a hostname or IP, defaulting to the master backend. - 'port' defines the port used to access the backend, retrieved from the - database if not given. - 'db' allows an existing database connection. Will only be used if - either 'backend' or 'port' is not defined. - """ - def __repr__(self): - return "<%s 'http://%s:%d/' at %s>" % \ - (str(self.__class__).split("'")[1].split(".")[-1], - self.host, self.port, hex(id(self))) - - def __init__(self, backend=None, db=None, port=None): - if backend and port: - self.db = None - self.host, self.port = backend, int(port) - return - self.db = MythDBBase(db) - self.log = MythLog('Python XML Connection', db=self.db) - if backend is None: - # use master backend - backend = self.db.settings.NULL.MasterServerIP - if re.match('(?:\d{1,3}\.){3}\d{1,3}',backend): - # process ip address - c = self.db.cursor(self.log) - if c.execute("""SELECT hostname FROM settings - WHERE value='BackendServerIP' - AND data=%s""", backend) == 0: - raise MythDBError(MythError.DB_SETTING, - backend+': BackendServerIP') - self.host = c.fetchone()[0] - self.port = int(self.db.settings[self.host].BackendStatusPort) - c.close() - else: - # assume given a hostname - self.host = backend - self.port = int(self.db.settings[self.host].BackendStatusPort) - if not self.port: - # try a truncated hostname - self.host = backend.split('.')[0] - self.port = int(self.db.setting[self.host].BackendStatusPort) - if not self.port: - raise MythDBError(MythError.DB_SETTING, - backend+': BackendStatusPort') - - def _query(self, path=None, **keyvars): - """ - obj._query(path=None, **keyvars) -> xml string - - 'path' is an optional page to access. - 'keyvars' are a series of optional variables to specify on the URL. - """ - url = 'http://%s:%d/' % (self.host, self.port) - if path == 'xml': - url += 'xml' - elif path is not None: - url += 'Myth/%s' % path - if len(keyvars) > 0: - fields = [] - for key in keyvars: - fields.append('%s=%s' % (key, keyvars[key])) - url += '?'+'&'.join(fields) - ufd = urlopen(url) - res = ufd.read() - ufd.close() - return res - - def _queryTree(self, path=None, **keyvars): - """obj._queryTree(path=None, **keyvars) -> xml element tree""" - xmlstr = self._query(path, **keyvars) - if xmlstr.find('xmlns') >= 0: - ind1 = xmlstr.find('xmlns') - ind2 = xmlstr.find('"', ind1+7) + 1 - xmlstr = xmlstr[:ind1] + xmlstr[ind2:] - return etree.fromstring(xmlstr) - - def getConnectionInfo(self, pin=0): - """Return dbconn dict from backend connection info.""" - dbconn = {'SecurityPin':pin} - conv = {'Host':'DBHostName', 'Port':'DBPort', - 'UserName':'DBUserName','Password':'DBPassword', - 'Name':'DBName'} - tree = self._queryTree('GetConnectionInfo', Pin=pin) - if tree.tag == 'GetConnectionInfoResponse': - for child in tree.find('Info').find('Database'): - if child.tag in conv: - dbconn[conv[child.tag]] = child.text - if 'DBPort' in dbconn: - dbconn['DBPort'] = int(dbconn['DBPort']) - return dbconn - - -class MythLog( object ): - """ - MythLog(module='pythonbindings', lstr=None, lbit=None, \ - db=None, logfile=None) -> logging object - - 'module' defines the source of the message in the logs - 'lstr' and 'lbit' define the message filter - 'lbit' takes a bitwise value - 'lstr' takes a string in the normal '-v level' form - default is set to 'important,general' - 'logfile' sets a file to open and subsequently log to - - The filter level and logfile are global values, shared between all logging - instances. Furthermode, the logfile can only be set once, and cannot - be changed. - - The logging object is callable, and implements the MythLog.log() method. - """ - - ALL = int('1111111111111111111111111111', 2) - MOST = int('0011111111101111111111111111', 2) - NONE = int('0000000000000000000000000000', 2) - - IMPORTANT = int('0000000000000000000000000001', 2) - GENERAL = int('0000000000000000000000000010', 2) - RECORD = int('0000000000000000000000000100', 2) - PLAYBACK = int('0000000000000000000000001000', 2) - CHANNEL = int('0000000000000000000000010000', 2) - OSD = int('0000000000000000000000100000', 2) - FILE = int('0000000000000000000001000000', 2) - SCHEDULE = int('0000000000000000000010000000', 2) - NETWORK = int('0000000000000000000100000000', 2) - COMMFLAG = int('0000000000000000001000000000', 2) - AUDIO = int('0000000000000000010000000000', 2) - LIBAV = int('0000000000000000100000000000', 2) - JOBQUEUE = int('0000000000000001000000000000', 2) - SIPARSER = int('0000000000000010000000000000', 2) - EIT = int('0000000000000100000000000000', 2) - VBI = int('0000000000001000000000000000', 2) - DATABASE = int('0000000000010000000000000000', 2) - DSMCC = int('0000000000100000000000000000', 2) - MHEG = int('0000000001000000000000000000', 2) - UPNP = int('0000000010000000000000000000', 2) - SOCKET = int('0000000100000000000000000000', 2) - XMLTV = int('0000001000000000000000000000', 2) - DVBCAM = int('0000010000000000000000000000', 2) - MEDIA = int('0000100000000000000000000000', 2) - IDLE = int('0001000000000000000000000000', 2) - CHANNELSCAN = int('0010000000000000000000000000', 2) - EXTRA = int('0100000000000000000000000000', 2) - TIMESTAMP = int('1000000000000000000000000000', 2) - - DBALL = 8 - DBDEBUG = 7 - DBINFO = 6 - DBNOTICE = 5 - DBWARNING = 4 - DBERROR = 3 - DBCRITICAL = 2 - DBALERT = 1 - DBEMERGENCY = 0 - - LOGLEVEL = IMPORTANT|GENERAL - LOGFILE = None - - helptext = """Verbose debug levels. - Accepts any combination (separated by comma) of: - - " all " - ALL available debug output - " most " - Most debug (nodatabase,notimestamp,noextra) - " important " - Errors or other very important messages - " general " - General info - " record " - Recording related messages - " playback " - Playback related messages - " channel " - Channel related messages - " osd " - On-Screen Display related messages - " file " - File and AutoExpire related messages - " schedule " - Scheduling related messages - " network " - Network protocol related messages - " commflag " - Commercial Flagging related messages - " audio " - Audio related messages - " libav " - Enables libav debugging - " jobqueue " - JobQueue related messages - " siparser " - Siparser related messages - " eit " - EIT related messages - " vbi " - VBI related messages - " database " - Display all SQL commands executed - " dsmcc " - DSMCC carousel related messages - " mheg " - MHEG debugging messages - " upnp " - upnp debugging messages - " socket " - socket debugging messages - " xmltv " - xmltv output and related messages - " dvbcam " - DVB CAM debugging messages - " media " - Media Manager debugging messages - " idle " - System idle messages - " channelscan " - Channel Scanning messages - " extra " - More detailed messages in selected levels - " timestamp " - Conditional data driven messages - " none " - NO debug output - - The default for this program appears to be: '-v "important,general" ' - - Most options are additive except for none, all, and important. - These three are semi-exclusive and take precedence over any - prior options given. You can however use something like - '-v none,jobqueue' to get only JobQueue related messages - and override the default verbosity level. - - The additive options may also be subtracted from 'all' by - prefixing them with 'no', so you may use '-v all,nodatabase' - to view all but database debug messages. - - Some debug levels may not apply to this program. -""" - - def __repr__(self): - return "<%s '%s','%s' at %s>" % \ - (str(self.__class__).split("'")[1].split(".")[-1], - self.module, bin(self.LOGLEVEL), hex(id(self))) - - def __init__(self, module='pythonbindings', lstr=None, lbit=None, \ - db=None, logfile=None): - self._setlevel(lstr, lbit) - self.module = module - self.db = db - if self.db: - self.db = MythDBBase(self.db) - if (logfile is not None) and (self.LOGFILE is None): - self.LOGFILE = open(logfile,'w') - - def _setlevel(lstr=None, lbit=None): - if lstr: - MythLog.LOGLEVEL = MythLog._parselevel(lstr) - elif lbit: - MythLog.LOGLEVEL = lbit - _setlevel = staticmethod(_setlevel) - - def _parselevel(lstr): - level = MythLog.NONE - bwlist = ( 'important','general','record','playback','channel','osd', - 'file','schedule','network','commflag','audio','libav', - 'jobqueue','siparser','eit','vbi','database','dsmcc', - 'mheg','upnp','socket','xmltv','dvbcam','media','idle', - 'channelscan','extra','timestamp') - for l in lstr.split(','): - if l in ('all','most','none'): - # set initial bitfield - level = eval('MythLog.'+l.upper()) - elif l in bwlist: - # update bitfield OR - level |= eval('MythLog.'+l.upper()) - elif len(l) > 2: - if l[0:2] == 'no': - if l[2:] in bwlist: - # update bitfield NOT - level &= level^eval('MythLog.'+l[2:].upper()) - return level - _parselevel = staticmethod(_parselevel) - - def log(self, level, message, detail=None, dblevel=None): - """ - MythLog.log(level, message, detail=None, dblevel=None) -> None - - 'level' sets the bitwise log level, to be matched against the log - filter. If any bits match true, the message will be logged. - 'message' and 'detail' set the log message content using the format: - : - ---- or ---- - : -- - """ - if level&self.LOGLEVEL: - if (version_info[0]>2) | (version_info[1]>5): # 2.6 or newer - nowstr = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] - else: - nowstr = datetime.now().strftime('%Y-%m-%d %H:%M:%S.000') - if detail is not None: - lstr = "%s %s: %s -- %s"%(nowstr, self.module, message, detail) - else: - lstr = "%s %s: %s" % (nowstr, self.module, message) - if self.LOGFILE: - self.LOGFILE.write(lstr+'\n') - self.LOGFILE.flush() - os.fsync(self.LOGFILE.fileno()) - else: - print lstr - return - - if dblevel is not None: - if self.db is None: - self.db = MythDBBase() - c = self.db.cursor(self.log) - c.execute("""INSERT INTO mythlog (module, priority, logdate, - host, message, details) - VALUES (%s, %s, %s, %s, %s, %s)""", - (self.module, dblevel, now(), - self.host, message, detail)) - c.close() - - def __call__(self, level, message, detail=None, dblevel=None): - self.log(level, message, detail, dblevel) - -class MythError( Exception ): - """ - MythError('Generic Error Code') -> Exception - MythError(error_code, additional_arguments) -> Exception - - Objects will have an error string available at obj.args as well as an - error code at obj.ecode. Additional attributes may be available - depending on the error code. - """ - GENERIC = 0 - SYSTEM = 1 - DB_RAW = 50 - DB_CONNECTION = 51 - DB_CREDENTIALS = 52 - DB_SETTING = 53 - DB_SCHEMAMISMATCH = 54 - PROTO_CONNECTION = 100 - PROTO_ANNOUNCE = 101 - PROTO_MISMATCH = 102 - FE_CONNECTION = 150 - FE_ANNOUNCE = 151 - FILE_ERROR = 200 - FILE_FAILED_READ = 201 - FILE_FAILED_WRITE = 202 - def __init__(self, *args): - if args[0] == self.SYSTEM: - self.ename = 'SYSTEM' - self.ecode, self.retcode, self.command, self.stderr = args - self.args = ("External system call failed: code %d" %self.retcode,) - elif args[0] == self.DB_RAW: - self.ename = 'DB_RAW' - self.ecode, sqlerr = args - if len(sqlerr) == 1: - self.sqlcode = 0 - self.sqlerr = sqlerr[0] - self.args = ("MySQL error: %s" % self.sqlerr,) - else: - self.sqlcode, self.sqlerr = sqlerr - self.args = ("MySQL error %d: %s" % sqlerr,) - elif args[0] == self.DB_CONNECTION: - self.ename = 'DB_CONNECTION' - self.ecode, self.dbconn = args - self.args = ("Failed to connect to database at '%s'@'%s'" \ - % (self.dbconn['DBName'], self.dbconn['DBHostName']) \ - +"for user '%s' with password '%s'." \ - % (self.dbconn['DBUserName'], self.dbconn['DBPassword']),) - elif args[0] == self.DB_CREDENTIALS: - self.ename = 'DB_CREDENTIALS' - self.ecode = args - self.args = ("Could not find database login credentials",) - elif args[0] == self.DB_SETTING: - self.ename = 'DB_SETTING' - self.ecode, self.setting, self.hostname = args - self.args = ("Could not find setting '%s' on host '%s'" \ - % (self.setting, self.hostname),) - elif args[0] == self.DB_SCHEMAMISMATCH: - self.ename = 'DB_SCHEMAMISMATCH' - self.ecode, self.setting, self.remote, self.local = args - self.args = ("Mismatched schema version for '%s': " % self.setting \ - + "database speaks version %d, we speak version %d"\ - % (self.remote, self.local),) - elif args[0] == self.PROTO_CONNECTION: - self.ename = 'PROTO_CONNECTION' - self.ecode, self.backend, self.port = args - self.args = ("Failed to connect to backend at %s:%d" % \ - (self.backend, self.port),) - elif args[0] == self.PROTO_ANNOUNCE: - self.ename = 'PROTO_ANNOUNCE' - self.ecode, self.backend, self.port, self.response = args - self.args = ("Unexpected response to ANN on %s:%d - %s" \ - % (self.backend, self.port, self.response),) - elif args[0] == self.PROTO_MISMATCH: - self.ename = 'PROTO_MISMATCH' - self.ecode, self.remote, self.local = args - self.args = ("Backend speaks version %s, we speak version %s" % \ - (self.remote, self.local),) - elif args[0] == self.FE_CONNECTION: - self.ename = 'FE_CONNECTION' - self.ecode, self.frontend, self.port = args - self.args = ('Connection to frontend %s:%d failed' \ - % (self.frontend, self.port),) - elif args[0] == self.FE_ANNOUNCE: - self.ename = 'FE_ANNOUNCE' - self.ecode, self.frontend, self.port = args - self.args = ('Open socket at %s:%d not recognized as mythfrontend'\ - % (self.frontend, self.port),) - elif args[0] == self.FILE_ERROR: - self.ename = 'FILE_ERROR' - self.ecode, self.reason = args - self.args = ("Error accessing file: " % self.reason,) - elif args[0] == self.FILE_FAILED_READ: - self.ename = 'FILE_FAILED_READ' - self.ecode, self.file = args - self.args = ("Error accessing %s" %(self.file,self.mode),) - elif args[0] == self.FILE_FAILED_WRITE: - self.ename = 'FILE_FAILED_WRITE' - self.ecode, self.file, self.reason = args - self.args = ("Error writing to %s, %s" % (self.file, self.reason),) - else: - self.ename = 'GENERIC' - self.ecode = self.GENERIC - self.args = args - self.message = str(self.args[0]) - -class MythDBError( MythError ): pass -class MythBEError( MythError ): pass -class MythFEError( MythError ): pass -class MythFileError( MythError ): pass - -class StorageGroup( DBData ): - """ - StorageGroup(id=None, db=None, raw=None) -> StorageGroup object - Represents a single storage group entry - """ - table = 'storagegroup' - where = 'id=%s' - setwheredat = 'self.id,' - logmodule = 'Python StorageGroup' - - def __str__(self): - return u" Grabber object - - 'path' sets the object to use a path to an external command - 'setting' pulls the external command from a setting in the database - """ - logmodule = 'Python Metadata Grabber' - - def __init__(self, path=None, setting=None, db=None): - MythDBBase.__init__(self, db=db) - self.log = MythLog(self.logmodule, db=self) - self.path = '' - if path is not None: - self.path = path - elif setting is not None: - host = socket.gethostname() - self.path = self.settings[host][setting] - if self.path is None: - raise MythDBError(MythError.DB_SETTING, setting, host) - else: - raise MythError('Invalid input to Grabber()') - self.returncode = 0 - self.stderr = '' - - def __str__(self): - return "<%s '%s' at %s>" % \ - (str(self.__class__).split("'")[1].split(".")[-1], - self.path, hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def append(self, *args): - """ - obj.append(*args) -> None - - Permenantly appends one or more strings to the command - path, separated by spaces. - """ - self.path += ' '+' '.join(['%s' % a for a in args]) - - def command(self, *args): - """ - obj.command(*args) -> output string - - Executes external command, adding one or more strings to the - command during the call. If call exits with a code not - equal to 0, a MythError will be raised. The error code and - stderr will be available in the exception and this object - as attributes 'returncode' and 'stderr'. - """ - if self.path is '': - return '' - arg = ' '+' '.join(['%s' % a for a in args]) - fd = Popen('%s %s' % (self.path, arg), stdout=-1, stderr=-1, - shell=True) - self.returncode = fd.wait() - stdout,self.stderr = fd.communicate() - - if self.returncode: - raise MythError(MythError.SYSTEM,self.returncode,arg,self.stderr) - return stdout - - diff --git a/mythtv/.svn/text-base/MythData.py.svn-base b/mythtv/.svn/text-base/MythData.py.svn-base deleted file mode 100644 index d1e4ac5..0000000 --- a/mythtv/.svn/text-base/MythData.py.svn-base +++ /dev/null @@ -1,1504 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Provides data access classes for accessing and managing MythTV data -""" - -from MythStatic import * -from MythBase import * - -import re, sys, socket, os -import xml.etree.cElementTree as etree -from time import mktime, strftime, strptime -from datetime import date, time, datetime -from socket import gethostbyaddr, gethostname - -#### FILE ACCESS #### - -def ftopen(file, type, forceremote=False, nooverwrite=False, db=None): - """ - ftopen(file, type, forceremote=False, nooverwrite=False, db=None) - -> FileTransfer object - -> file object - Method will attempt to open file locally, falling back to remote access - over mythprotocol if necessary. - 'forceremote' will force a FileTransfer object if possible. - 'file' takes a standard MythURI: - myth://@:/ - 'type' takes a 'r' or 'w' - 'nooverwrite' will refuse to open a file writable, if a local file is found. - """ - db = MythDBBase(db) - log = MythLog('Python File Transfer', db=db) - reuri = re.compile(\ - 'myth://((?P.*)@)?(?P[a-zA-Z0-9\.]*)(:[0-9]*)?/(?P.*)') - reip = re.compile('(?:\d{1,3}\.){3}\d{1,3}') - - if type not in ('r','w'): - raise TypeError("File I/O must be of type 'r' or 'w'") - - # process URI (myth://@[:]/) - match = reuri.match(file) - if match is None: - raise MythError('Invalid FileTransfer input string: '+file) - host = match.group('host') - filename = match.group('file') - sgroup = match.group('group') - if sgroup is None: - sgroup = 'Default' - - # get full system name - if reip.match(host): - c = db.cursor(log) - if c.execute("""SELECT hostname FROM settings - WHERE value='BackendServerIP' - AND data=%s""", host) == 0: - c.close() - raise MythDBError(MythError.DB_SETTING, 'BackendServerIP', backend) - host = c.fetchone()[0] - c.close() - - # user forced to remote access - if forceremote: - if (type == 'w') and (filename.find('/') != -1): - raise MythFileError(MythError.FILE_FAILED_WRITE, file, - 'attempting remote write outside base path') - return FileTransfer(host, filename, sgroup, type, db) - - sgs = db.getStorageGroup(groupname=sgroup) - - if type == 'w': - # prefer local storage always - for i in range(len(sgs)-1,-1,-1): - if not sgs[i].local: - sgs.pop(i) - else: - st = os.statvfs(sgs[i].dirname) - sgs[i].free = st[0]*st[3] - if len(sgs) > 1: - # choose path with most free space - sg = sgs.pop() - while len(sgs): - if sgs[0].free > sg.free: sg = sgs.pop() - else: sgs.pop() - # check that folder exists - if filename.find('/') != -1: - path = sg.dirname+filename.rsplit('/',1)[0] - if not os.access(path, os.F_OK): - os.makedirs(path) - if nooverwrite: - if os.access(sg.dirname+filename, os.F_OK): - raise MythDBError(MythError.FILE_FAILED_WRITE, file, - 'refusing to overwrite existing file') - log(log.FILE, 'Opening local file (w)', sg.dirname+filename) - return open(sg.dirname+filename, 'w') - elif len(sgs) == 1: - if filename.find('/') != -1: - path = sgs[0].dirname+filename.rsplit('/',1)[0] - if not os.access(path, os.F_OK): - os.makedirs(path) - if nooverwrite: - if os.access(sgs[0].dirname+filename, os.F_OK): - raise MythFileError(MythError.FILE_FAILED_WRITE, file, - 'refusing to overwrite existing file') - log(log.FILE, 'Opening local file (w)', sgs[0].dirname+filename) - return open(sgs[0].dirname+filename, 'w') - else: - if filename.find('/') != -1: - raise MythFileError(MythError.FILE_FAILED_WRITE, file, - 'attempting remote write outside base path') - return FileTransfer(host, filename, sgroup, 'w', db) - else: - # search for file in local directories - for sg in sgs: - if sg.local: - if os.access(sg.dirname+filename, os.F_OK): - # file found, open local - log(log.FILE, 'Opening local file (r)', - sg.dirname+filename) - return open(sg.dirname+filename, type) - # file not found, open remote - return FileTransfer(host, filename, sgroup, type, db) - -class FileTransfer( MythBEConn ): - """ - A connection to mythbackend intended for file transfers. - Emulates the primary functionality of the local 'file' object. - """ - logmodule = 'Python FileTransfer' - def __repr__(self): - return "" % \ - (self.sgroup, self.host, self.filename, \ - self.type, hex(id(self))) - - def __init__(self, host, filename, sgroup, type, db=None): - if type not in ('r','w'): - raise MythError("FileTransfer type must be read ('r') "+ - "or write ('w')") - - self.host = host - self.filename = filename - self.sgroup = sgroup - self.type = type - - self.tsize = 2**15 - self.tmax = 2**17 - self.count = 0 - self.step = 2**12 - - self.open = False - # create control socket - self.control = MythBEBase(host, 'Playback', db=db) - self.control.log.module = 'Python FileTransfer Control' - # continue normal Backend initialization - MythBEConn.__init__(self, host, type, db=db) - self.open = True - - def announce(self, type): - # replacement announce for Backend object - if type == 'w': - self.w = True - elif type == 'r': - self.w = False - res = self.backendCommand('ANN FileTransfer %s %d %d %s' \ - % (socket.gethostname(), self.w, False, - BACKEND_SEP.join(['-1',self.filename,self.sgroup]))) - if res.split(BACKEND_SEP)[0] != 'OK': - raise MythError(MythError.PROTO_ANNOUNCE, self.host, self.port, res) - else: - sp = res.split(BACKEND_SEP) - self.sockno = int(sp[1]) - self.pos = 0 - self.size = (int(sp[2]) + (int(sp[3])<0))*2**32 + int(sp[3]) - - def __del__(self): - if not self.open: - return - self.control.backendCommand('QUERY_FILETRANSFER '\ - +BACKEND_SEP.join([str(self.sockno), 'DONE'])) - self.socket.shutdown(1) - self.socket.close() - self.open = False - - def tell(self): - """FileTransfer.tell() -> current offset in file""" - return self.pos - - def close(self): - """FileTransfer.close() -> None""" - self.__del__() - - def rewind(self): - """FileTransfer.rewind() -> None""" - self.seek(0) - - def read(self, size): - """ - FileTransfer.read(size) -> string of characters - Requests over 128KB will be buffered internally. - """ - - def recv(self, size): - buff = '' - while len(buff) < size: - buff += self.socket.recv(size-len(buff)) - return buff - - if self.w: - raise MythFileError('attempting to read from a write-only socket') - if size == 0: - return '' - - final = self.pos+size - if final > self.size: - final = self.size - - buff = '' - while self.pos < final: - self.count += 1 - size = final - self.pos - if size > self.tsize: - size = self.tsize - res = self.control.backendCommand('QUERY_FILETRANSFER '+\ - BACKEND_SEP.join([ str(self.sockno), - 'REQUEST_BLOCK', - str(size)])) - #print '%s - %s' % (int(res), size) - if int(res) == size: - if (self.count == 10) and (self.tsize < self.tmax) : - self.count = 0 - self.tsize += self.step - else: - if int(res) == -1: - self.seek(self.pos) - continue - size = int(res) - self.count = 0 - self.tsize -= self.step - if self.tsize < self.step: - self.tsize = self.step - - buff += recv(self, size) - self.pos += size - return buff - - def write(self, data): - """ - FileTransfer.write(data) -> None - Requests over 128KB will be buffered internally - """ - if not self.w: - raise MythFileError('attempting to write to a read-only socket') - while len(data) > 0: - size = len(data) - if size > self.tsize: - buff = data[self.tsize:] - data = data[:self.tsize] - else: - buff = data - data = '' - self.pos += int(self.socket.send(data)) - self.control.backendCommand('QUERY_FILETRANSFER '+BACKEND_SEP\ - .join([str(self.sockno),'WRITE_BLOCK',str(size)])) - return - - - def seek(self, offset, whence=0): - """ - FileTransfer.seek(offset, whence=0) -> None - Seek 'offset' number of bytes - whence == 0 - from start of file - 1 - from current position - 2 - from end of file - """ - if whence == 0: - if offset < 0: - offset = 0 - elif offset > self.size: - offset = self.size - elif whence == 1: - if offset + self.pos < 0: - offset = -self.pos - elif offset + self.pos > self.size: - offset = self.size - self.pos - elif whence == 2: - if offset > 0: - offset = 0 - elif offset < -self.size: - offset = -self.size - whence = 0 - offset = self.size+offset - - curhigh,curlow = self.control.splitInt(self.pos) - offhigh,offlow = self.control.splitInt(offset) - - res = self.control.backendCommand('QUERY_FILETRANSFER '+BACKEND_SEP\ - .join([str(self.sockno),'SEEK',str(offhigh), - str(offlow),str(whence),str(curhigh), - str(curlow)])\ - ).split(BACKEND_SEP) - self.pos = self.control.joinInt(int(res[0]),int(res[1])) - -class FileOps( MythBEBase ): - __doc__ = MythBEBase.__doc__+""" - getRecording() - return a Program object for a recording - deleteRecording() - notify the backend to delete a recording - forgetRecording() - allow a recording to re-record - deleteFile() - notify the backend to delete a file - in a storage group - getHash() - return the hash of a file in a storage group - reschedule() - trigger a run of the scheduler - """ - logmodule = 'Python Backend FileOps' - - def getRecording(self, chanid, starttime): - """FileOps.getRecording(chanid, starttime) -> Program object""" - res = self.backendCommand('QUERY_RECORDING TIMESLOT %d %d' \ - % (chanid, starttime)).split(BACKEND_SEP) - if res[0] == 'ERROR': - return None - else: - return Program(res[1:], db=self.db) - - def deleteRecording(self, program, force=False): - """ - FileOps.deleteRecording(program, force=False) -> retcode - 'force' will force a delete even if the file cannot be found - retcode will be -1 on success, -2 on failure - """ - command = 'DELETE_RECORDING' - if force: - command = 'FORCE_DELETE_RECORDING' - return self.backendCommand(BACKEND_SEP.join(\ - [command,program.toString()])) - - def forgetRecording(self, program): - """FileOps.forgetRecording(program) -> None""" - self.backendCommand(BACKEND_SEP.join(['FORGET_RECORDING', - program.toString()])) - - def deleteFile(self, file, sgroup): - """FileOps.deleteFile(file, storagegroup) -> retcode""" - return self.backendCommand(BACKEND_SEP.join(\ - ['DELETE_FILE',file,sgroup])) - - def getHash(self, file, sgroup): - """FileOps.getHash(file, storagegroup) -> hash string""" - return self.backendCommand(BACKEND_SEP.join((\ - 'QUERY_FILE_HASH',file, sgroup))) - - def reschedule(self, recordid=-1): - """FileOps.reschedule() -> None""" - self.backendCommand('RESCHEDULE_RECORDINGS '+str(recordid)) - - -class FreeSpace( DictData ): - """Represents a FreeSpace entry.""" - field_order = [ 'host', 'path', 'islocal', - 'disknumber', 'sgroupid', 'blocksize', - 'ts_high', 'ts_low', 'us_high', - 'us_low'] - field_type = [3, 3, 2, 0, 0, 0, 0, 0, 0, 0] - def __str__(self): - return ""\ - % (self.path, self.host, hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def __init__(self, raw): - DictData.__init__(self, raw) - self.totalspace = self.joinInt(self.ts_high, self.ts_low) - self.usedspace = self.joinInt(self.us_high, self.us_low) - self.freespace = self.totalspace - self.usedspace - - -#### RECORDING ACCESS #### - -class Program( DictData ): - """Represents a program with all detail returned by the backend.""" - - # recstatus - TUNERBUSY = -8 - LOWDISKSPACE = -7 - CANCELLED = -6 - DELETED = -5 - ABORTED = -4 - RECORDED = -3 - RECORDING = -2 - WILLRECORD = -1 - UNKNOWN = 0 - DONTRECORD = 1 - PREVIOUSRECORDING = 2 - CURRENTRECORDING = 3 - EARLIERSHOWING = 4 - TOOMANYRECORDINGS = 5 - NOTLISTED = 6 - CONFLICT = 7 - LATERSHOWING = 8 - REPEAT = 9 - INACTIVE = 10 - NEVERRECORD = 11 - - field_order = [ 'title', 'subtitle', 'description', - 'category', 'chanid', 'channum', - 'callsign', 'channame', 'filename', - 'fs_high', 'fs_low', 'starttime', - 'endtime', 'duplicate', 'shareable', - 'findid', 'hostname', 'sourceid', - 'cardid', 'inputid', 'recpriority', - 'recstatus', 'recordid', 'rectype', - 'dupin', 'dupmethod', 'recstartts', - 'recendts', 'repeat', 'programflags', - 'recgroup', 'commfree', 'outputfilters', - 'seriesid', 'programid', 'lastmodified', - 'stars', 'airdate', 'hasairdate', - 'playgroup', 'recpriority2', 'parentid', - 'storagegroup', 'audio_props', 'video_props', - 'subtitle_type','year'] - field_type = [ 3, 3, 3, - 3, 0, 3, - 3, 3, 3, - 0, 0, 4, - 4, 0, 0, - 0, 3, 0, - 0, 0, 0, - 0, 0, 3, - 0, 0, 4, - 4, 0, 3, - 3, 0, 3, - 3, 3, 3, - 1, 3, 0, - 3, 0, 3, - 3, 0, 0, - 0, 0] - def __str__(self): - return u"" % (self.title, - self.starttime.strftime('%Y-%m-%d %H:%M:%S'), hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def __init__(self, raw=None, db=None, etree=None): - if raw: - DictData.__init__(self, raw) - elif etree: - xmldat = etree.attrib - xmldat.update(etree.find('Channel').attrib) - if etree.find('Recording'): - xmldat.update(etree.find('Recording').attrib) - - dat = {} - if etree.text: - dat['description'] = etree.text.strip() - for key in ('title','subTitle','seriesId','programId','airdate', - 'category','hostname','chanNum','callSign','playGroup', - 'recGroup','rectype','programFlags','chanId','recStatus', - 'commFree','stars'): - if key in xmldat: - dat[key.lower()] = xmldat[key] - for key in ('startTime','endTime','lastModified', - 'recStartTs','recEndTs'): - if key in xmldat: - dat[key.lower()] = str(int(mktime(strptime( - xmldat[key], '%Y-%m-%dT%H:%M:%S')))) - if 'fileSize' in xmldat: - dat['fs_high'],dat['fs_low'] = \ - self.splitInt(int(xmldat['fileSize'])) - - raw = [] - defs = (0,0,0,'',0) - for i in range(len(self.field_order)): - if self.field_order[i] in dat: - raw.append(dat[self.field_order[i]]) - else: - raw.append(defs[self.field_type[i]]) - DictData.__init__(self, raw) - else: - raise InputError("Either 'raw' or 'etree' must be provided") - self.db = MythDBBase(db) - self.filesize = self.joinInt(self.fs_high,self.fs_low) - - def toString(self): - """ - Program.toString() -> string representation - for use with backend protocol commands - """ - data = [] - for i in range(0,PROGRAM_FIELDS): - if self.data[self.field_order[i]] == None: - datum = '' - elif self.field_type[i] == 0: - datum = str(self.data[self.field_order[i]]) - elif self.field_type[i] == 1: - datum = locale.format("%0.6f", self.data[self.field_order[i]]) - elif self.field_type[i] == 2: - datum = str(int(self.data[self.field_order[i]])) - elif self.field_type[i] == 3: - datum = self.data[self.field_order[i]] - elif self.field_type[i] == 4: - datum = str(int(mktime(self.data[self.field_order[i]].\ - timetuple()))) - data.append(datum) - return BACKEND_SEP.join(data) - - def delete(self, force=False, rerecord=False): - """ - Program.delete(force=False, rerecord=False) -> retcode - Informs backend to delete recording and all relevent data. - 'force' forces a delete if the file cannot be found. - 'rerecord' sets the file as recordable in oldrecorded - """ - be = FileOps(db=self.db) - res = int(be.deleteRecording(self, force=force)) - if res < -1: - raise MythBEError('Failed to delete file') - if rerecord: - be.forgetRecording(self) - return res - - def getRecorded(self): - """Program.getRecorded() -> Recorded object""" - return Recorded((self.chanid,self.recstartts), db=self.db) - - def open(self, type='r'): - """Program.open(type='r') -> file or FileTransfer object""" - if type != 'r': - raise MythFileError(MythError.FILE_FAILED_WRITE, self.filename, - 'Program () objects cannot be opened for writing') - if not self.filename.startswith('myth://'): - self.filename = 'myth://%s/%s' % (self.hostname, self.filename) - return ftopen(self.filename, 'r') - -class Record( DBDataWrite ): - """ - Record(id=None, db=None, raw=None) -> Record object - """ - - kNotRecording = 0 - kSingleRecord = 1 - kTimeslotRecord = 2 - kChannelRecord = 3 - kAllRecord = 4 - kWeekslotRecord = 5 - kFindOneRecord = 6 - kOverrideRecord = 7 - kDontRecord = 8 - kFindDailyRecord = 9 - kFindWeeklyRecord = 10 - - table = 'record' - where = 'recordid=%s' - setwheredat = 'self.recordid,' - defaults = {'recordid':None, 'type':kAllRecord, 'title':u'Unknown', - 'subtitle':'', 'description':'', 'category':'', - 'station':'', 'seriesid':'', 'search':0, - 'last_record':datetime(1900,1,1), - 'next_record':datetime(1900,1,1), - 'last_delete':datetime(1900,1,1)} - logmodule = 'Python Record' - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - return u"" \ - % (self.title, self.type, hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def __init__(self, id=None, db=None, raw=None): - DBDataWrite.__init__(self, (id,), db, raw) - - def create(self, data=None): - """Record.create(data=None) -> Record object""" - self.wheredat = (DBDataWrite.create(self, data),) - self._pull() - FileOps(db=self.db).reschedule(self.recordid) - return self - - def update(self, *args, **keywords): - DBDataWrite.update(*args, **keywords) - FileOps(db=self.db).reschedule(self.recordid) - -class Recorded( DBDataWrite ): - """ - Recorded(data=None, db=None, raw=None) -> Recorded object - 'data' is a tuple containing (chanid, storagegroup) - """ - table = 'recorded' - where = 'chanid=%s AND starttime=%s' - setwheredat = 'self.chanid,self.starttime' - defaults = {'title':u'Unknown', 'subtitle':'', 'description':'', - 'category':'', 'hostname':'', 'bookmark':0, - 'editing':0, 'cutlist':0, 'autoexpire':0, - 'commflagged':0, 'recgroup':'Default', 'seriesid':'', - 'programid':'', 'lastmodified':'CURRENT_TIMESTAMP', - 'filesize':0, 'stars':0, 'previouslyshown':0, - 'preserve':0, 'bookmarkupdate':0, - 'findid':0, 'deletepending':0, 'transcoder':0, - 'timestretch':1, 'recpriority':0, 'playgroup':'Default', - 'profile':'No', 'duplicate':1, 'transcoded':0, - 'watched':0, 'storagegroup':'Default'} - logmodule = 'Python Recorded' - - class _Cast( DBDataCRef ): - table = 'people' - rtable = 'recordedcredits' - t_ref = 'person' - t_add = ['name'] - r_ref = 'person' - r_add = ['role'] - w_field = ['chanid','starttime'] - def __repr__(self): - if self.data is None: - self._fill() - if len(self.data) == 0: - return 'No cast' - cast = [] - for member in self.data: - cast.append(member.name) - return ', '.join(cast).encode('utf-8') - - class _Seek( DBDataRef ): - table = 'recordedseek' - wfield = ['chanid','starttime'] - - class _Markup( DBDataRef ): - table = 'recordedmarkup' - wfield = ['chanid','starttime'] - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - return u"" % (self.title, - self.starttime.strftime('%Y-%m-%d %H:%M:%S'), hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def __init__(self, data=None, db=None, raw=None): - DBDataWrite.__init__(self, data, db, raw) - if (data is not None) or (raw is not None): - self.cast = self._Cast(self.wheredat, self.db) - self.seek = self._Seek(self.wheredat, self.db) - self.markup = self._Markup(self.wheredat, self.db) - - def create(self, data=None): - """Recorded.create(data=None) -> Recorded object""" - DBDataWrite.create(self, data) - self.wheredat = (self.chanid,self.starttime) - self._pull() - self.cast = self._Cast(self.wheredat, self.db) - self.seek = self._Seek(self.wheredat, self.db) - self.markup = self._Markup(self.wheredat, self.db) - return self - - def delete(self, force=False, rerecord=False): - """ - Recorded.delete(force=False, rerecord=False) -> retcode - Informs backend to delete recording and all relevent data. - 'force' forces a delete if the file cannot be found. - 'rerecord' sets the file as recordable in oldrecorded - """ - return self.getProgram().delete(force, rerecord) - - def open(self, type='r'): - """Recorded.open(type='r') -> file or FileTransfer object""" - return ftopen("myth://%s@%s/%s" % ( self.storagegroup, \ - self.hostname,\ - self.basename), type, db=self.db) - - def getProgram(self): - """Recorded.getProgram() -> Program object""" - be = FileOps(db=self.db) - return be.getRecording(self.chanid, - int(self.starttime.strftime('%Y%m%d%H%M%S'))) - - def getRecordedProgram(self): - """Recorded.getRecordedProgram() -> RecordedProgram object""" - return RecordedProgram((self.chanid,self.progstart), db=self.db) - - def formatPath(self, path, replace=None): - """ - Recorded.formatPath(path, replace=None) -> formatted path string - 'path' string is formatted as per mythrename.pl - """ - for (tag, data) in (('T','title'), ('S','subtitle'), - ('R','description'), ('C','category'), - ('U','recgroup'), ('hn','hostname'), - ('c','chanid') ): - tmp = unicode(self[data]).replace('/','-') - path = path.replace('%'+tag, tmp) - for (data, pre) in ( ('starttime','%'), ('endtime','%e'), - ('progstart','%p'),('progend','%pe') ): - for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'), - ('j','%d'),('d','%d'),('g','%I'),('G','%H'), - ('h','%I'),('H','%H'),('i','%M'),('s','%S'), - ('a','%p'),('A','%p') ): - path = path.replace(pre+tag, self[data].strftime(format)) - for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'), - ('j','%d'),('d','%d')): - path = path.replace('%o'+tag, - self['originalairdate'].strftime(format)) - path = path.replace('%-','-') - path = path.replace('%%','%') - path += '.'+self['basename'].split('.')[-1] - - # clean up for windows - if replace is not None: - for char in ('\\',':','*','?','"','<','>','|'): - path = path.replace(char, replace) - return path - -class RecordedProgram( DBDataWrite ): - """ - RecordedProgram(data=None, db=None, raw=None) -> RecordedProgram object - 'data' is a tuple containing (chanid, storagegroup) - """ - table = 'recordedprogram' - where = 'chanid=%s AND starttime=%s' - setwheredat = 'self.chanid,self.starttime' - defaults = {'title':'', 'subtitle':'', - 'category':'', 'category_type':'', 'airdate':0, - 'stars':0, 'previouslyshown':0, 'title_pronounce':'', - 'stereo':0, 'subtitled':0, 'hdtv':0, - 'partnumber':0, 'closecaptioned':0, 'parttotal':0, - 'seriesid':'', 'originalairdate':'', 'showtype':u'', - 'colorcode':'', 'syndicatedepisodenumber':'', - 'programid':'', 'manualid':0, 'generic':0, - 'first':0, 'listingsource':0, 'last':0, - 'audioprop':u'','videoprop':u'', - 'subtitletypes':u''} - logmodule = 'Python RecordedProgram' - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - return u"" % (self.title, - self.starttime.strftime('%Y-%m-%d %H:%M:%S'), hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def create(self, data=None): - """RecordedProgram.create(data=None) -> RecordedProgram object""" - DBDataWrite.create(self, data) - self.wheredat = (self.chanid, self.starttime) - self._pull() - return self - -class OldRecorded( DBDataWrite ): - """ - OldRecorded(data=None, db=None, raw=None) -> OldRecorded object - 'data' is a tuple containing (chanid, storagegroup) - """ - table = 'oldrecorded' - where = 'chanid=%s AND starttime=%s' - setwheredat = 'self.chanid,self.starttime' - defaults = {'title':'', 'subtitle':'', - 'category':'', 'seriesid':'', 'programid':'', - 'findid':0, 'recordid':0, 'station':'', - 'rectype':0, 'duplicate':0, 'recstatus':-3, - 'reactivate':0, 'generic':0} - logmodule = 'Python OldRecorded' - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - return u"" % (self.title, - self.starttime.strftime('%Y-%m-%d %H:%M:%S'), hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def create(self, data=None): - """OldRecorded.create(data=None) -> OldRecorded object""" - DBDataWrite.create(self, data) - self.wheredat = (self.chanid, self.starttime) - self._pull() - return self - - def setDuplicate(self, record=False): - """ - OldRecorded.setDuplicate(record=False) -> None - Toggles re-recordability - """ - c = self.db.cursor(self.log) - c.execute("""UPDATE oldrecorded SET duplicate=%%s - WHERE %s""" % self.where, \ - tuple([record]+list(self.wheredat))) - FileOps(db=self.db).reschedule(0) - - def update(self, *args, **keywords): - """OldRecorded entries can not be altered""" - return - def delete(self): - """OldRecorded entries cannot be deleted""" - return - -class Job( DBDataWrite ): - """ - Job(id=None, chanid=None, starttime=None, db=None, raw=None) -> Job object - Can be initialized with a Job id, or chanid and starttime. - """ - # types - NONE = 0x0000 - SYSTEMJOB = 0x00ff - TRANSCODE = 0x0001 - COMMFLAG = 0x0002 - USERJOB = 0xff00 - USERJOB1 = 0x0100 - USERJOB2 = 0x0200 - USERJOB3 = 0x0300 - USERJOB4 = 0x0400 - # cmds - RUN = 0x0000 - PAUSE = 0x0001 - RESUME = 0x0002 - STOP = 0x0004 - RESTART = 0x0008 - # flags - NO_FLAGS = 0x0000 - USE_CUTLIST = 0x0001 - LIVE_REC = 0x0002 - EXTERNAL = 0x0004 - # statuses - UNKNOWN = 0x0000 - QUEUED = 0x0001 - PENDING = 0x0002 - STARTING = 0x0003 - RUNNING = 0x0004 - STOPPING = 0x0005 - PAUSED = 0x0006 - RETRY = 0x0007 - ERRORING = 0x0008 - ABORTING = 0x0008 - DONE = 0x0100 - FINISHED = 0x0110 - ABORTED = 0x0120 - ERRORED = 0x0130 - CANCELLED = 0x0140 - - table = 'jobqueue' - logmodule = 'Python Jobqueue' - defaults = {'id': None} - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - return u"" % (self.id, hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def __init__(self, id=None, chanid=None, starttime=None, \ - db=None, raw=None): - self.__dict__['where'] = 'id=%s' - self.__dict__['setwheredat'] = 'self.id,' - - if raw is not None: - DBDataWrite.__init__(self, None, db, raw) - elif id is not None: - DBDataWrite.__init__(self, (id,), db, None) - elif (chanid is not None) and (starttime is not None): - self.__dict__['where'] = 'chanid=%s AND starttime=%s' - DBDataWrite.__init__(self, (chanid,starttime), db, None) - else: - DBDataWrite.__init__(self, None, db, None) - - def create(self, data=None): - """Job.create(data=None) -> Job object""" - id = DBDataWrite.create(self, data) - self.where = 'id=%s' - self.wheredat = (id,) - return self - - def setComment(self,comment): - """Job.setComment(comment) -> None, updates comment""" - self.comment = comment - self.update() - - def setStatus(self,status): - """Job.setStatus(Status) -> None, updates status""" - self.status = status - self.update() - -class Channel( DBDataWrite ): - """Channel(chanid=None, data=None, raw=None) -> Channel object""" - table = 'channel' - where = 'chanid=%s' - setwheredat = 'self.chanid,' - defaults = {'icon':'none', 'videofilters':'', 'callsign':u'', - 'xmltvid':'', 'recpriority':0, 'contrast':32768, - 'brightness':32768, 'colour':32768, 'hue':32768, - 'tvformat':u'Default', 'visible':1, 'outputfilters':'', - 'useonairguide':0, 'atsc_major_chan':0, - 'tmoffset':0, 'default_authority':'', - 'commmethod':-1, 'atsc_minor_chan':0, - 'last_record':datetime(1900,1,1)} - logmodule = 'Python Channel' - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - return u"" % \ - (self.chanid, self.name, hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def __init__(self, chanid=None, db=None, raw=None): - DBDataWrite.__init__(self, (chanid,), db, raw) - - def create(self, data=None): - """Channel.create(data=None) -> Channel object""" - DBDataWrite.create(self, data) - self.wheredat = (self.chanid,) - self._pull() - return self - -class Guide( DBData ): - """ - Guide(data=None, db=None, raw=None) -> Guide object - Data is a tuple of (chanid, starttime). - """ - table = 'program' - where = 'chanid=%s AND starttime=%s' - setwheredat = 'self.chanid,self.starttime' - logmodule = 'Python Guide' - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - return u"" % (self.title, - self.starttime.strftime('%Y-%m-%d %H:%M:%S'), hex(id(self))) - - def __repr__(self): - return str(self).encode('utf-8') - - def __init__(self, data=None, db=None, raw=None, etree=None): - if etree: - db = MythDBBase(db) - dat = {'chanid':etree[0]} - attrib = etree[1].attrib - for key in ('title','subTitle','category','seriesId', - 'hostname','programId','airdate'): - if key in attrib: - dat[key.lower()] = attrib[key] - if 'stars' in attrib: - dat['stars'] = locale.atof(attrib['stars']) - if etree[1].text: - dat['description'] = etree[1].text.strip() - for key in ('startTime','endTime','lastModified'): - if key in attrib: - dat[key.lower()] = datetime.strptime( - attrib[key],'%Y-%m-%dT%H:%M:%S') - - raw = [] - for key in db.tablefields.program: - if key in dat: - raw.append(dat[key]) - else: - raw.append(None) - DBData.__init__(self, db=db, raw=raw) - else: - DBData.__init__(self, data=data, db=db, raw=raw) - - def record(self, type=Record.kAllRecord): - rec = Record(db=self.db) - for key in ('chanid','title','subtitle','description', 'category', - 'seriesid','programid'): - rec[key] = self[key] - - rec.startdate = self.starttime.date() - rec.starttime = self.starttime-datetime.combine(rec.startdate, time()) - rec.enddate = self.endtime.date() - rec.endtime = self.endtime-datetime.combine(rec.enddate, time()) - - rec.station = Channel(self.chanid, db=self.db).callsign - rec.type = type - return rec.create() - - -#### MYTHVIDEO #### - -class Video( DBDataWrite ): - """Video(id=None, db=None, raw=None) -> Video object""" - table = 'videometadata' - where = 'intid=%s' - setwheredat = 'self.intid,' - defaults = {'subtitle':u'', 'director':u'Unknown', - 'rating':u'NR', 'inetref':u'00000000', - 'year':1895, 'userrating':0.0, - 'length':0, 'showlevel':1, - 'coverfile':u'No Cover', 'host':u'', - 'intid':None, 'homepage':u'', - 'watched':False, 'category':'none', - 'browse':True, 'hash':u'', - 'season':0, 'episode':0, - 'releasedate':date(1,1,1), 'childid':-1, - 'insertdate': datetime.now()} - logmodule = 'Python Video' - schema_value = 'mythvideo.DBSchemaVer' - schema_local = MVSCHEMA_VERSION - schema_name = 'MythVideo' - category_map = [{'None':0},{0:'None'}] - - def _fill_cm(self, name=None, id=None): - if name: - if name not in self.category_map[0]: - c = self.db.cursor(self.log) - q1 = """SELECT intid FROM videocategory WHERE category=%s""" - q2 = """INSERT INTO videocategory SET category=%s""" - if c.execute(q1, name) == 0: - c.execute(q2, name) - c.execute(q1, name) - id = c.fetchone()[0] - self.category_map[0][name] = id - self.category_map[1][id] = name - - elif id: - if id not in self.category_map[1]: - c = self.db.cursor(self.log) - if c.execute("""SELECT category FROM videocategory - WHERE intid=%s""", id) == 0: - raise MythDBError('Invalid ID found in videometadata.category') - else: - name = c.fetchone()[0] - self.category_map[0][name] = id - self.category_map[1][id] = name - - def _pull(self): - DBDataWrite._pull(self) - self._fill_cm(id=self.category) - self.category = self.category_map[1][self.category] - - def _push(self): - name = self.category - self._fill_cm(name=name) - self.category = self.category_map[0][name] - DBDataWrite._push(self) - self.category = name - - def __repr__(self): - return str(self).encode('utf-8') - - def __str__(self): - if self.wheredat is None: - return u"" % hex(id(self)) - res = self.title - if self.season and self.episode: - res += u' - %dx%02d' % (self.season, self.episode) - if self.subtitle: - res += u' - '+self.subtitle - return u"