Skip to content
Browse files

Reworks internal structure for databaseSearch decorator. Adds ParseEn…

…um and ParseSet classes for handling such data over protocol and database, respectively.

git-svn-id: http://svn.mythtv.org/svn/trunk@26888 7dbf422c-18fa-0310-86e9-fd20926502f2
  • Loading branch information...
1 parent 21c7ea0 commit de679e435c1a451a5c9688769c89fc77154d78a9 @wagnerrp wagnerrp committed Oct 19, 2010
View
69 mythtv/bindings/python/MythTV/database.py
@@ -10,7 +10,7 @@
from altdict import OrdDict, DictData
from logging import MythLog
from msearch import MSearch
-from utility import datetime
+from utility import datetime, _donothing
from exceptions import MythError, MythDBError
from connections import DBConnection, LoggedCursor, XMLConnection
@@ -68,18 +68,24 @@ def _setClassDefs(cls, db=None):
db = DBCache(db)
log = MythLog('DBData Setup (%s)' % cls.__name__)
if cls._table is None:
+ # pull table name from class name
cls._table = cls.__name__.lower()
log(log.DATABASE|log.EXTRA,
'set _table to %s' % cls._table)
+
if cls._logmodule is None:
+ # pull log module from class name
cls._logmodule = 'Python %s' % cls.__name__
log(log.DATABASE|log.EXTRA,
'set _logmodule to %s' % cls._logmodule)
+
if cls._field_order == []:
+ # pull field order from database
cls._field_order = db.tablefields[cls._table]
if (cls._setwheredat is None) or (cls._where is None):
if cls._key is None:
+ # pull primary key fields from database
with db.cursor(log) as cursor:
cursor.execute("""SHOW KEYS FROM %s
WHERE Key_name='PRIMARY'""" \
@@ -96,6 +102,9 @@ def _setClassDefs(cls, db=None):
log(log.DATABASE|log.EXTRA,
'set _setwheredat to %s' % cls._setwheredat)
+ # class has been processed, turn method into no-op
+ cls._setClassDefs = classmethod(_donothing)
+
@classmethod
def getAllEntries(cls, db=None):
"""cls.getAllEntries() -> tuple of DBData objects"""
@@ -458,18 +467,24 @@ def __and__(self, other):
data.append(dat)
return self.fromCopy(data)
+ @classmethod
+ def _setClassDefs(cls, db=None):
+ db = DBCache(db)
+ fields = list(db.tablefields[cls._table])
+ for f in cls._ref:
+ if f in fields:
+ fields.remove(f)
+ cls._datfields = fields
+
+ cls._setClassDefs = classmethod(_donothing)
+
def __init__(self, where, db=None, bypass=False):
list.__init__(self)
- if bypass: return
self._db = DBCache(db)
- self._refdat = where
+ self._setClassDefs(self._db)
+ if bypass: return
- fields = list(self._db.tablefields[self._table])
- for f in self._ref:
- if f in fields:
- fields.remove(f)
- self._datfields = fields
- self._data = None
+ self._refdat = where
self._populated = False
def _populate(self, force=False, data=None):
@@ -582,7 +597,7 @@ def _picklelist(self):
class DBDataCRef( DBDataRef ):
"""
- DBDataRef.__init__(where, db=None) --> DBDataRef object
+ DBDataCRef.__init__(where, db=None) --> DBDataRef object
Class for managing lists of referenced data, such as recordedmarkup
Subclasses must provide:
@@ -597,23 +612,31 @@ class DBDataCRef( DBDataRef ):
class SubData( DBDataRef.SubData ):
_localvars = DBDataRef.SubData._localvars+['_cref']
- def __init__(self, where, db=None, bypass=False):
- list.__init__(self)
- if bypass: return
- self._db = DBCache(db)
- self._refdat = list(where)
+ @classmethod
+ def _setClassDefs(cls, db=None):
+ db = DBCache(db)
+ rfields = list(db.tablefields[cls._table[0]])
+ crfields = list(db.tablefields[cls._table[1]])
- rfields = list(self._db.tablefields[self._table[0]])
- crfields = list(self._db.tablefields[self._table[1]])
- for f in self._ref+self._cref[:1]:
+ for f in cls._ref+[cls._cref[-1]]:
if f in rfields:
rfields.remove(f)
- if self._cref[-1] in crfields:
- crfields.remove(self._cref[-1])
+ if cls._cref[-1] in crfields:
+ crfields.remove(cls._cref[-1])
- self._rdatfields = rfields
- self._crdatfields = crfields
- self._datfields = crfields+rfields
+ cls._rdatfields = rfields
+ cls._crdatfields = crfields
+ cls._datfields = crfields+rfields
+
+ cls._setClassDefs = classmethod(_donothing)
+
+ def __init__(self, where, db=None, bypass=False):
+ list.__init__(self)
+ self._db = DBCache(db)
+ self._setClassDefs(self._db)
+ if bypass: return
+
+ self._refdat = list(where)
self._populated = False
def _populate(self, force=False, data=None):
View
19 mythtv/bindings/python/MythTV/dataheap.py
@@ -12,7 +12,7 @@
from database import *
from system import Grabber, InternetMetadata, VideoMetadata
from mythproto import ftopen, FileOps, Program
-from utility import CMPRecord, CMPVideo, MARKUPLIST, datetime
+from utility import CMPRecord, CMPVideo, MARKUPLIST, datetime, ParseSet
import re
import locale
@@ -198,6 +198,10 @@ def _postinit(self):
def fromProgram(cls, program):
return cls((program.chanid, program.recstartts), program._db)
+ @classmethod
+ def fromJob(cls, job):
+ return cls((job.chanid, job.starttime), job._db)
+
def _push(self):
DBDataWrite._push(self)
self.cast.commit()
@@ -309,7 +313,6 @@ def _allow_change(self, tag, overwrite):
be.downloadTo(image.url, group, image.filename)
exists[image.type] = True
-
self.update()
def __getstate__(self):
@@ -366,6 +369,11 @@ def __init__(self, data=None, db=None):
data = [data[0], datetime.duck(data[1])]
DBDataWrite.__init__(self, data, db)
+ def _postinit(self):
+ self.AudioProp = ParseSet(self, 'audioprop')
+ self.VideoProp = ParseSet(self, 'videoprop')
+ self.SubtitleTypes = ParseSet(self, 'subtitletypes')
+
@classmethod
def fromRecorded(cls, recorded):
return cls((recorded.chanid, recorded.progstart), recorded._db)
@@ -485,7 +493,12 @@ def getRecStatus(self):
(prog.starttime == self.starttime):
return prog.recstatus
return 0
-
+
+ def _postinit(self):
+ self.AudioProp = ParseSet(self, 'audioprop', False)
+ self.VideoProp = ParseSet(self, 'videoprop', False)
+ self.SubtitleTypes = ParseSet(self, 'subtitletypes', False)
+
@classmethod
def fromEtree(cls, etree, db=None):
dat = {'chanid':etree[0]}
View
121 mythtv/bindings/python/MythTV/methodheap.py
@@ -606,12 +606,19 @@ def searchRecorded(self, init=False, key=None, value=None):
if init:
# table and join descriptor
- return ('recorded', Recorded, ('livetv',),
- ('recordedprogram','recorded',('chanid','starttime')), #1
- ('recordedcredits','recorded',
- ('chanid','starttime'),
- ('chanid','progstart')), #2
- ('people','recordedcredits',('person',))) #4
+ init.table = 'recorded'
+ init.handler = Recorded
+ init.required = ('livetv,')
+ init.joins = (init.Join(table='recordedprogram',
+ tableto='recorded',
+ fields=('chanid','starttime')),
+ init.Join(table='recordedcredits',
+ tableto='recorded',
+ fieldsfrom=('chanid','starttime'),
+ fieldsto=('chanid','progstart')),
+ init.Join(table='people',
+ tableto='recordedcredits',
+ fields=('person',)))
# local table matches
if key in ('title','subtitle','chanid',
@@ -658,7 +665,9 @@ def searchOldRecorded(self, init=False, key=None, value=None):
"""
if init:
- return ('oldrecorded', OldRecorded, ())
+ init.table = 'recorded'
+ init.handler = Recorded
+
if key in ('title','subtitle','chanid',
'category','seriesid','programid','station',
'duplicate','generic','recstatus'):
@@ -678,8 +687,12 @@ def searchJobs(self, init=False, key=None, value=None):
title, subtitle, flags, olderthan, newerthan
"""
if init:
- return ('jobqueue', Job, (),
- ('recorded','jobqueue',('chanid','starttime')))
+ init.table = 'jobqueue'
+ init.handler = Job
+ init.joins = (init.Join(table='recorded',
+ tableto='jobqueue',
+ fields=('chanid','starttime')),)
+
if key in ('chanid','type','status','hostname'):
return ('jobqueue.%s=%%s' % key, value, 0)
if key in ('title','subtitle'):
@@ -710,9 +723,15 @@ def searchGuide(self, init=False, key=None, value=None):
endbefore, endafter
"""
if init:
- return ('program', Guide, (),
- ('credits','program',('chanid','starttime')),
- ('people','credits',('person',)))
+ init.table = 'program'
+ init.handler = Guide
+ init.joins = (init.Join(table='credits',
+ tableto='program',
+ fields=('chanid','starttime')),
+ init.Join(table='people',
+ tableto='credits',
+ fields=('person',)))
+
if key in ('chanid','title','subtitle',
'category','airdate','stars','previouslyshown','stereo',
'subtitled','hdtv','closecaptioned','partnumber',
@@ -754,7 +773,9 @@ def searchRecord(self, init=False, key=None, value=None):
recgroup, station, seriesid, programid, playgroup
"""
if init:
- return ('record', Record, ())
+ init.table = 'record'
+ init.handler = Record
+
if key in ('type','chanid','starttime','startdate','endtime','enddate',
'title','subtitle','category','profile','recgroup',
'station','seriesid','programid','playgroup'):
@@ -774,7 +795,9 @@ def searchInternetContent(self, init=False, key=None, value=None):
country, description
"""
if init:
- return ('internetcontentarticles', InternetContentArticles, ())
+ init.table = 'internetcontentarticles'
+ init.handler = InternetContentArticles
+
if key in ('feedtitle','title','subtitle','season','episode','url',
'type','author','rating','player','width','height',
'language','podcast','downloadable', 'description'):
@@ -1070,21 +1093,37 @@ def searchVideos(self, init=False, key=None, value=None):
"""
if init:
- return ('videometadata', Video, (),
- ('videometadatacast','videometadata',
- ('idvideo',),('intid',)), #1
- ('videocast','videometadatacast',
- ('intid',),('idcast',)), #2
- ('videometadatagenre','videometadata',
- ('idvideo',),('intid',)), #4
- ('videogenre','videometadatagenre',
- ('intid',),('idgenre',)), #8
- ('videometadatacountry','videometadata',
- ('idvideo',),('intid',)), #16
- ('videocountry','videometadatacountry',
- ('intid',),('idcountry',)), #32
- ('videocategory','videometadata',
- ('intid',),('category',))) #64
+ init.table = 'videometadata'
+ init.handler = Video
+ init.joins = (init.Join(table='videometadatacast',
+ tableto='videometadata',
+ fieldsfrom=('idvideo',),
+ fieldsto=('intid',)),
+ init.Join(table='videocast',
+ tableto='videometadatacast',
+ fieldsfrom=('intid',),
+ fieldsto=('idcast',)),
+ init.Join(table='videometadatagenre',
+ tableto='videometadata',
+ fieldsfrom=('idvideo',),
+ fieldsto=('intid',)),
+ init.Join(table='videogenre',
+ tableto='videometadatagenre',
+ fieldsfrom=('intid',),
+ fieldsto=('idgenre',)),
+ init.Join(table='videometadatacountry',
+ tableto='videometadata',
+ fieldsfrom=('idvideo',),
+ fieldsto=('intid',)),
+ init.Join(table='videocountry',
+ tableto='videometadatacountry',
+ fieldsfrom=('intid',),
+ fieldsto=('idcountry',)),
+ init.Join(table='videocategory',
+ tableto='videometadata',
+ fieldsfrom=('intid',),
+ fieldsto=('category',)))
+
if key in ('title','subtitle','season','episode','host',
'director','year'):
return('videometadata.%s=%%s' % key, value, 0)
@@ -1106,14 +1145,6 @@ def searchVideos(self, init=False, key=None, value=None):
return ('videometadata.insertdate>%s', value, 0)
return None
- def getVideo(self, **kwargs):
- """legacy - do not use"""
- videos = self.searchVideos(**kwargs)
- try:
- return videos.next()
- except StopIteration:
- return None
-
class MythMusic( MusicSchema, DBCache ):
"""
Provides convenient methods to access the MythTV MythMusic database.
@@ -1128,10 +1159,18 @@ def searchMusic(self, init=False, key=None, value=None):
genre, rating, format, sample_rate, bitrate
"""
if init:
- return ('music_songs', Song, (),
- ('music_artists','music_songs',('artist_id',)), #1
- ('music_albums', 'music_songs',('album_id',)), #2
- ('music_genres', 'music_songs',('genre_id',))) #4
+ init.table = 'music_songs'
+ init.handler = Song
+ init.joins = (init.Join(table='music_artists',
+ tableto='music_songs',
+ fields=('artist_id',)),
+ init.Join(table='music_albums',
+ tableto='music_songs',
+ fields=('album_id',)),
+ init.Join(table='music_genres',
+ tableto='music_songs',
+ fields=('genre_id',)))
+
if key in ('name','track','disc_number','rating',
'format','sample_rate','bitrate'):
return ('music_songs.%s=%%s' % key, value, 0)
View
19 mythtv/bindings/python/MythTV/mythproto.py
@@ -13,7 +13,7 @@
from altdict import DictData
from connections import BEConnection
from database import DBCache
-from utility import SplitInt, CMPRecord, datetime
+from utility import SplitInt, CMPRecord, datetime, ParseEnum
from datetime import date
from time import sleep
@@ -158,16 +158,14 @@ def findfile(filename, sgroup, db=None):
db = DBCache(db)
for sg in db.getStorageGroup(groupname=sgroup):
# search given group
- if not sg.local:
- continue
- if os.access(sg.dirname+filename, os.F_OK):
- return sg
+ if sg.local:
+ if os.access(sg.dirname+filename, os.F_OK):
+ return sg
for sg in db.getStorageGroup():
# not found, search all other groups
- if not sg.local:
- continue
- if os.access(sg.dirname+filename, os.F_OK):
- return sg
+ if sg.local:
+ if os.access(sg.dirname+filename, os.F_OK):
+ return sg
return None
def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
@@ -780,6 +778,9 @@ def __repr__(self):
def __init__(self, raw, db=None):
DictData.__init__(self, raw)
self._db = db
+ self.AudioProps = ParseEnum(self, 'audio_props', AUDIO_PROPS, False)
+ self.VideoProps = ParseEnum(self, 'video_props', VIDEO_PROPS, False)
+ self.SubtitleType = ParseEnum(self, 'subtitle_type', SUBTITLE_TYPES, False)
@classmethod
def fromEtree(cls, etree, db=None):
View
158 mythtv/bindings/python/MythTV/utility.py
@@ -12,6 +12,8 @@
from datetime import datetime as _pydatetime
from datetime import tzinfo as _pytzinfo
from datetime import timedelta
+from itertools import imap
+import weakref
import socket
import re
@@ -113,6 +115,30 @@ class databaseSearch( object ):
4-field -- Special response consisting of:
(see example in methodheap.py:MythDB.searchRecorded)
"""
+ class Join( object ):
+ def __init__(self, table=None, tableto=None, fields=None, \
+ fieldsto=None, fieldsfrom=None):
+ if (table is None) or (tableto is None) or \
+ ((fields is None) and \
+ ((fieldsto is None) or (fieldsfrom is None))):
+ raise MythDBError('Invalid input to databaseSearch.Join.')
+ self.table = table
+ self.tableto = tableto
+ if fields:
+ self.fieldsfrom = fields
+ self.fieldsto = fields
+ else:
+ self.fieldsfrom = fieldsfrom
+ self.fieldsto = fieldsto
+
+ def buildWhere(self):
+ s = '%s.%%s=%s.%%s' % (self.table, self.tableto)
+ return ' AND '.join([s % (f,t) \
+ for f,t in zip(self.fieldsfrom,self.fieldsto)])
+
+ def buildJoin(self):
+ return 'JOIN %s ON %s' % (self.table, self.buildWhere())
+
def __init__(self, func):
# set function and update strings
self.func = func
@@ -121,14 +147,8 @@ def __init__(self, func):
self.__module__ = self.func.__module__
# process joins
- res = list(self.func(self, True))
- self.table = res[0]
- self.dbclass = res[1]
- self.require = res[2]
- if len(res) > 3:
- self.joins = res[3:]
- else:
- self.joins = ()
+ self.require = ()
+ self.func(self, self)
def __get__(self, inst, own):
# set instance and return self
@@ -147,7 +167,7 @@ def __call__(self, **kwargs):
cursor.execute(query)
for row in cursor:
- yield self.dbclass.fromRaw(row, self.inst)
+ yield self.handler.fromRaw(row, db=self.inst)
def parseInp(self, kwargs):
where = []
@@ -187,10 +207,10 @@ def parseInp(self, kwargs):
joinbit = joinbit|res[2]
elif len(res) == 4:
# special format for crossreferenced data
- lval = val.split(',')
+ lval = [f.strip() for f in val.split(',')]
where.append('(%s)=%d' %\
(self.buildQuery(
- ( self.buildJoinOn(res[3]),
+ ( self.joins[res[3]].buildWhere(),
'(%s)' % \
' OR '.join(['%s=%%s' % res[0] \
for f in lval])),
@@ -228,14 +248,12 @@ def buildJoinOn(self, i):
self.joins[i][3])])
return on
- def buildJoin(self, joinbit):
- join = ''
- if joinbit:
- for i,v in enumerate(self.joins):
- if (2**i)&joinbit:
- join += ' JOIN %s ON %s' % \
- (v[0], self.buildJoinOn(i))
- return join
+ def buildJoin(self, joinbit=0):
+ join = []
+ for i,v in enumerate(self.joins):
+ if (2**i)&joinbit:
+ join.append(v.buildJoin())
+ return ' '.join(join)
def buildQuery(self, where, select=None, tfrom=None, joinbit=0):
sql = 'SELECT '
@@ -252,7 +270,7 @@ def buildQuery(self, where, select=None, tfrom=None, joinbit=0):
else:
sql += self.table
- sql += self.buildJoin(joinbit)
+ sql += ' '+self.buildJoin(joinbit)
if len(where):
sql += ' WHERE '
@@ -522,3 +540,103 @@ def timestamp(self):
def rfcformat(self):
return self.strftime('%a, %d %b %Y %H:%M:%S %Z')
+class ParseEnum( object ):
+ _static = None
+ def __str__(self):
+ return str([k for k,v in self.iteritems() if v==True])
+ def __repr__(self): return str(self)
+ def __init__(self, parent, field_name, enum, editable=True):
+ self._parent = weakref.proxy(parent)
+ self._field = field_name
+ self._enum = enum
+ self._static = not editable
+
+ def __getattr__(self, name):
+ if name in self.__dict__:
+ return self.__dict__[name]
+ return bool(self._parent[self._field]&getattr(self._enum, name))
+
+ def __setattr__(self, name, value):
+ if self._static is None:
+ object.__setattr__(self, name, value)
+ return
+ if self._static:
+ raise AttributeError("'%s' cannot be edited." % name)
+ self.__setitem__(name, value)
+
+ def __getitem__(self, key):
+ return bool(self._parent[self._field]&getattr(self._enum, key))
+
+ def __setitem__(self, key, value):
+ if self._static:
+ raise KeyError("'%s' cannot be edited." % name)
+ val = getattr(self._enum, key)
+ if value:
+ self._parent[self._field] |= val
+ else:
+ self._parent[self._field] -= self._parent[self._field]&val
+
+ def keys(self):
+ return [key for key in self._enum.__dict__ if key[0] != '_']
+
+ def values(self):
+ return list(self.itervalues())
+
+ def items(self):
+ return list(self.iteritems())
+
+ def __iter__(self):
+ return self.itervalues()
+
+ def iterkeys(self):
+ return iter(self.keys())
+
+ def itervalues(self):
+ return imap(self.__getitem__, self.keys())
+
+ def iteritems(self):
+ for key in self.keys():
+ yield (key, self[key])
+
+class ParseSet( ParseEnum ):
+ def __init__(self, parent, field_name, editable=True):
+ self._parent = weakref.proxy(parent)
+ self._field = field_name
+ field = parent._db.tablefields[parent._table][self._field].type
+ if field[:4] != 'set(':
+ raise MythDBError("ParseSet error. "+\
+ "Field '%s' not of type 'set()'" % self._field)
+ self._enum = dict([(t,2**i) for i,t in enumerate([type.strip("'")\
+ for type in field[4:-1].split(',')])])
+ self._static = not editable
+
+ def __getattr__(self, name):
+ if name in self.__dict__:
+ return self.__dict__[name]
+ return self.__getitem__(name)
+
+ def __setattr__(self, name, value):
+ if self._static is None:
+ object.__setattr__(self, name, value)
+ return
+ if self._static:
+ raise AttributeError("'%s' cannot be edited." % name)
+ self.__setitem__(name, value)
+
+ def __getitem__(self, key):
+ return key in self._parent[self._field].split(',')
+
+ def __setitem__(self, key, value):
+ if self._static:
+ raise KeyError("'%s' cannot be edited." % name)
+ if self[key] == value:
+ return
+ tmp = self._parent[self._field].split(',')
+ if value:
+ tmp.append(key)
+ else:
+ tmp.remove(key)
+ self._parent[self._field] = ','.join(tmp)
+
+ def keys(self):
+ return self._enum.keys()

0 comments on commit de679e4

Please sign in to comment.
Something went wrong with that request. Please try again.