Ticket #6678: pyth.update8.patch

File pyth.update8.patch, 21.9 KB (added by Raymond Wagner <raymond@…>, 17 years ago)
  • mythtv/bindings/python/MythTV/MythTV.py

     
    1414import socket
    1515import code
    1616from datetime import datetime
     17from time import mktime
    1718
    1819from MythDB import *
    1920from MythLog import *
     
    8081                        log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.master_host, self.master_port)
    8182                        sys.exit(1)
    8283
     84        def __del__(self):
     85                self.backendCommand('DONE')
     86                self.socket.shutdown(1)
     87                self.socket.close()
     88
    8389        def backendCommand(self, data):
    8490                """
    8591                Sends a formatted command via a socket to the mythbackend.
     
    95101                        try:
    96102                                length = int(data)
    97103                        except:
    98                                 return ''
     104                                return u''
    99105                        data = []
    100106                        while length > 0:
    101107                                chunk = self.socket.recv(length)
    102108                                length = length - len(chunk)
    103109                                data.append(chunk)
    104                         return ''.join(data)
     110                        try:
     111                                return unicode(''.join(data),'utf8')
     112                        except:
     113                                return u''.join(data)
    105114
    106                 command = '%-8d%s' % (len(data), data)
     115                command = u'%-8d%s' % (len(data), data)
    107116                log.Msg(DEBUG, 'Sending command: %s', command)
    108117                self.socket.send(command)
    109118                return recv()
     
    120129                for i in range(num_progs):
    121130                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
    122131                                + PROGRAM_FIELDS]))
    123                 return programs
     132                return tuple(programs)
    124133
    125134        def getScheduledRecordings(self):
    126135                """
     
    133142                for i in range(num_progs):
    134143                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
    135144                                + PROGRAM_FIELDS]))
    136                 return programs
     145                return tuple(programs)
    137146
    138147        def getUpcomingRecordings(self):
    139148                """
     
    155164                        if p.recstatus == RECSTATUS['WillRecord']:
    156165                                programs.append(p)
    157166                programs.sort(sort_programs_by_starttime)
    158                 return programs
     167                return tuple(programs)
    159168
    160169        def getRecorderList(self):
    161170                """
    162171                Returns a list of recorders, or an empty list if none.
    163172                """
    164173                recorders = []
    165                 c = self.db.cursor()
     174                pc = self.db.cursor()
    166175                c.execute('SELECT cardid FROM capturecard')
    167176                row = c.fetchone()
    168177                while row is not None:
     
    220229                else:
    221230                        return False
    222231
     232        def getRecording(self, chanid, starttime):
     233                """
     234                Returns a Program object matching the channel id and start time
     235                """
     236                res = self.backendCommand('QUERY_RECORDING TIMESLOT %d %d' % (chanid, starttime)).split(BACKEND_SEP)
     237                if res[0] == 'ERROR':
     238                        return None
     239                else:
     240                        return Program(res[1:])
     241
     242        def getRecordings(self):
     243                """
     244                Returns a list of all Program objects which have already recorded
     245                """
     246                programs = []
     247                res = self.backendCommand('QUERY_RECORDINGS Play').split('[]:[]')
     248                num_progs = int(res.pop(0))
     249                log.Msg(DEBUG, '%s total recordings', num_progs)
     250                for i in range(num_progs):
     251                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
     252                                + PROGRAM_FIELDS]))
     253                return tuple(programs)
     254
     255        def getCheckfile(self,program):
     256                """
     257                Returns location of recording in file system
     258                """
     259                res = self.backendCommand('QUERY_CHECKFILE[]:[]1[]:[]%s' % program.toString()).split(BACKEND_SEP)
     260                if res[0] == 0:
     261                        return None
     262                else:
     263                        return res[1]
     264
     265        def getFrontends(self):
     266                """
     267                Returns a list of Frontend objects for accessible frontends
     268                """
     269                cursor = self.db.db.cursor()
     270                cursor.execute("SELECT DISTINCT hostname FROM settings WHERE hostname IS NOT NULL and value='NetworkControlEnabled' and data=1")
     271                frontends = []
     272                for fehost in cursor.fetchall():
     273                        try:
     274                                frontend = self.getFrontend(fehost[0])
     275                                frontends.append(frontend)
     276                        except:
     277                                print "%s is not a valid frontend" % fehost[0]
     278                cursor.close()
     279                return frontends
     280
     281        def getFrontend(self,host):
     282                """
     283                Returns a Frontend object for the specified host
     284                """
     285                port = self.db.getSetting("NetworkControlPort",host)
     286                return Frontend(host,port)
     287
     288        def splitInt(self,integer):
     289                """
     290                Returns a pair of signed integers from a single long
     291                """
     292                return integer/(2**32),integer%2**32 - (integer%2**32 > 2**31)*2**32
     293
     294class FileTransfer:
     295        """
     296        A connection to mythbackend intended for file transfers
     297        """
     298        sockno = None
     299        socket = None
     300        tsize = 2**16
     301
     302        def __init__(self, file, parent=None):
     303                self.db = MythDB(sys.argv[1:])
     304                if not isinstance(parent, MythTV):
     305                        self.parent = MythTV()
     306                else:
     307                        self.parent = parent
     308                if isinstance(file, Program):
     309                        self.master_host, self.master_port = file.filename.split('/')[-2].split(':')
     310                        self.master_port = int(self.master_port)
     311                        self.filename = file.filename.split('/')[-1]
     312                        self.sgroup = file.storagegroup
     313                elif isinstance(file, tuple):
     314                        if len(file) != 3:
     315                                log.Msg(CRITICAL, 'Incorrect FileTransfer() input size')
     316                                sys.exit(1)
     317                        else:
     318                                self.master_host = file[0]
     319                                self.master_port = int(self.db.getSetting('BackendServerPort',self.master_host))
     320                                self.filename = file[1]
     321                                self.sgroup = file[2]
     322                else:
     323                        log.Msg(CRITICAL, 'Improper input to FileTransfer()')
     324                        sys.exit(1)
     325
     326                try:
     327                        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     328                        self.socket.settimeout(10)
     329                        self.socket.connect((self.master_host, self.master_port))
     330                        res = self.send('MYTH_PROTO_VERSION %s' % PROTO_VERSION).split(BACKEND_SEP)
     331                        if res[0] == 'REJECT':
     332                                log.Msg(CRITICAL, 'Backend has version %s and we speak version %s', res[1], PROTO_VERSION)
     333                                sys.exit(1)
     334                       
     335                        res = self.send('ANN FileTransfer %s%s%s%s%s' % (socket.gethostname(), BACKEND_SEP, self.filename, BACKEND_SEP, self.sgroup))
     336                        if res.split(BACKEND_SEP)[0] != 'OK':
     337                                log.Msg(CRITICAL, 'Unexpected answer to ANN command: %s', res)
     338                        else:
     339                                log.Msg(INFO, 'Successfully connected mythbackend at %s:%d', self.master_host, self.master_port)
     340                                sp = res.split(BACKEND_SEP)
     341                                self.sockno = int(sp[1])
     342                                self.pos = 0
     343                                self.size = (int(sp[2]) + (int(sp[3])<0))*2**32 + int(sp[3])
     344                               
     345                except socket.error, e:
     346                        log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.master_host, self.master_port)
     347                        sys.exit(1)
     348
     349        def __del__(self):
     350                if self.sockno:
     351                        self.parent.backendCommand('QUERY_FILETRANSFER %d%sDONE' % (self.sockno, BACKEND_SEP))
     352                if self.socket:
     353                        self.socket.shutdown(1)
     354                        self.socket.close()
     355
     356        def send(self,data):
     357                command = '%-8d%s' % (len(data), data)
     358                log.Msg(DEBUG, 'Sending command: %s', command)
     359                self.socket.send(command)
     360                return self.recv()
     361
     362        def recv(self):
     363                data = self.socket.recv(8)
     364                try:
     365                        length = int(data)
     366                except:
     367                        return ''
     368                data = []
     369                while length > 0:
     370                        chunk = self.socket.recv(length)
     371                        length = length - len(chunk)
     372                        data.append(chunk)
     373                return ''.join(data)
     374
     375        def tell(self):
     376                """
     377                Return the current offset from the beginning of the file
     378                """
     379                return self.pos
     380
     381        def close(self):
     382                """
     383                Close the data transfer socket
     384                """
     385                self.__del__()
     386
     387        def rewind(self):
     388                """
     389                Seek back to the start of the file
     390                """
     391                self.seek(0)
     392
     393        def read(self, size):
     394                """
     395                Read a block of data, requests over 64KB will be buffered internally
     396                """
     397                if size == 0:
     398                        return ''
     399                if size > self.size - self.pos:
     400                        size = self.size - self.pos
     401                csize = size
     402                rsize = 0
     403                if csize > self.tsize:
     404                        csize = self.tsize
     405                        rsize = size - csize
     406                       
     407                res = self.parent.backendCommand('QUERY_FILETRANSFER %d%sREQUEST_BLOCK%s%d' % (self.sockno,BACKEND_SEP,BACKEND_SEP,csize))
     408                self.pos = self.pos + int(res)
     409#               if int(res) == csize:
     410#                       if csize < size:
     411#                               self.tsize += 8192
     412#               else:
     413#                       self.tsize -= 8192
     414#                       rsize = size - int(res)
     415#               print 'resizing buffer to %d' % self.tsize
     416
     417                return self.socket.recv(int(res)) + self.read(rsize)
     418
     419        def seek(self, offset, whence=0):
     420                """
     421                Seek 'offset' number of bytes
     422                   whence==0 - from start of file
     423                   whence==1 - from current position
     424                """
     425                if whence == 0:
     426                        if offset < 0:
     427                                offset = 0
     428                        if offset > self.size:
     429                                offset = self.size
     430                elif whence == 1:
     431                        if offset + self.pos < 0:
     432                                offset = -self.pos
     433                        if offset + self.pos > self.size:
     434                                offset = self.size - self.pos
     435                elif whence == 2:
     436                        if offset > 0:
     437                                offset = 0
     438                        if offset < -self.size:
     439                                offset = -self.size
     440                else:
     441                        log.Msg(CRITICAL, 'Whence can only be 0, 1, or 2')
     442
     443                curhigh,curlow = self.parent.splitInt(self.pos)
     444                offhigh,offlow = self.parent.splitInt(offset)
     445
     446                res = self.parent.backendCommand('QUERY_FILETRANSFER %d%sSEEK%s%d%s%d%s%d%s%d%s%d' % (self.sockno, BACKEND_SEP,BACKEND_SEP,offhigh,BACKEND_SEP,offlow,BACKEND_SEP,whence,BACKEND_SEP,curhigh,BACKEND_SEP,curlow)).split(BACKEND_SEP)
     447                self.pos = (int(res[0]) + (int(res[1])<0))*2**32 + int(res[1])
     448
     449
     450class Frontend:
     451        isConnected = False
     452        socket = None
     453        host = None
     454        port = None
     455
     456        def __init__(self, host, port):
     457                self.host = host
     458                self.port = int(port)
     459                self.connect()
     460                self.disconnect()
     461
     462        def __del__(self):
     463                if self.isConnected:
     464                        self.disconnect()
     465
     466        def __repr__(self):
     467                return "%s@%d" % (self.host, self.port)
     468
     469        def __str__(self):
     470                return "%s@%d" % (self.host, self.port)
     471
     472        def connect(self):
     473                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     474                self.socket.settimeout(10)
     475                self.socket.connect((self.host, self.port))
     476                if self.recv()[:28] != "MythFrontend Network Control":
     477                        self.socket.close()
     478                        self.socket = None
     479                        raise Exception('FrontendConnect','Connected socket does not belong to a mythfrontend')
     480                self.isConnected = True
     481
     482        def disconnect(self):
     483                self.send("exit")
     484                self.socket.close()
     485                self.socket = None
     486                self.isConnected = False
     487
     488        def send(self,command):
     489                if not self.isConnected:
     490                        self.connect()
     491                self.socket.send("%s\n" % command)
     492
     493        def recv(self,curstr=""):
     494                def subrecv(self,curstr=""):
     495                        try:
     496                                curstr += self.socket.recv(100)
     497                        except:
     498                                return None
     499                        if curstr[-4:] != '\r\n# ':
     500                                curstr = subrecv(self,curstr)
     501                        return curstr
     502                return subrecv(self)[:-4]
     503
     504        def sendJump(self,jumppoint):
     505                """
     506                Sends jumppoint to frontend
     507                """
     508                self.send("jump %s" % jumppoint)
     509                if self.recv() == 'OK':
     510                        return 0
     511                else:
     512                        return 1
     513
     514        def getJump(self):
     515                """
     516                Returns a tuple containing available jumppoints
     517                """
     518                self.send("help jump")
     519                res = self.recv().split('\r\n')[3:-1]
     520                points = []
     521                for point in res:
     522                        spoint = point.split(' - ')
     523                        points.append((spoint[0].rstrip(),spoint[1]))
     524                return tuple(points)
     525
     526        def sendKey(self,key):
     527                """
     528                Sends keycode to connected frontend
     529                """
     530                self.send("key %s" % key)
     531                if self.recv() == 'OK':
     532                        return 0
     533                else:
     534                        return 1
     535
     536        def getKey(self):
     537                """
     538                Returns a tuple containing available special keys
     539                """
     540                self.send("help key")
     541                res = self.recv().split('\r\n')[4]
     542                keys = []
     543                for key in res.split(','):
     544                        keys.append(key.strip())
     545                return tuple(keys)
     546
     547        def sendQuery(self,query):
     548                """
     549                Returns query from connected frontend
     550                """
     551                self.send("query %s" % query)
     552                return self.recv()
     553
     554        def getQuery(self):
     555                """
     556                Returns a tuple containing available queries
     557                """
     558                self.send("help query")
     559                res = self.recv().split('\r\n')[:-1]
     560                queries = []
     561                tmpstr = ""
     562                for query in res:
     563                        tmpstr += query
     564                        squery = tmpstr.split(' - ')
     565                        if len(squery) == 2:
     566                                tmpstr = ""
     567                                queries.append((squery[0].rstrip().lstrip('query '),squery[1]))
     568                return tuple(queries)
     569
     570        def sendPlay(self,play):
     571                """
     572                Send playback command to connected frontend
     573                """
     574                self.send("play %s" % play)
     575                if self.recv() == 'OK':
     576                        return 0
     577                else:
     578                        return 1
     579
     580        def getPlay(self):
     581                """
     582                Returns a tuple containing available playback commands
     583                """
     584                self.send("help play")
     585                res = self.recv().split('\r\n')[:-1]
     586                plays = []
     587                tmpstr = ""
     588                for play in res:
     589                        tmpstr += play
     590                        splay = tmpstr.split(' - ')
     591                        if len(splay) == 2:
     592                                tmpstr = ""
     593                                plays.append((splay[0].rstrip().lstrip('play '),splay[1]))
     594                return tuple(plays)
     595                       
     596               
     597
    223598class Recorder:
    224599        """
    225600        Represents a MythTV capture card.
     
    265640                self.callsign = data[6] #chansign
    266641                self.channame = data[7]
    267642                self.filename = data[8] #pathname
    268                 self.fs_high = data[9]
    269                 self.fs_low = data[10]
     643                self.fs_high = int(data[9])
     644                self.fs_low = int(data[10])
    270645                self.starttime = datetime.fromtimestamp(int(data[11])) # startts
    271646                self.endtime = datetime.fromtimestamp(int(data[12])) #endts
    272647                self.duplicate = int(data[13])
     
    303678                self.video_props = data[44]
    304679                self.subtitle_type = data[45]
    305680                self.year = data[46]
     681               
     682                self.filesize = (self.fs_high + (self.fs_low<0))*2**32 + self.fs_low
    306683
     684        def toString(self):
     685                string =            self.title
     686                string += BACKEND_SEP + self.subtitle
     687                string += BACKEND_SEP + self.description
     688                string += BACKEND_SEP + self.category
     689                if self.chanid:
     690                        string += BACKEND_SEP + str(self.chanid)
     691                else:
     692                        string += BACKEND_SEP
     693                string += BACKEND_SEP + self.channum
     694                string += BACKEND_SEP + self.callsign
     695                string += BACKEND_SEP + self.channame
     696                string += BACKEND_SEP + self.filename
     697                string += BACKEND_SEP + str(self.fs_high)
     698                string += BACKEND_SEP + str(self.fs_low)
     699                string += BACKEND_SEP + str(int(mktime(self.starttime.timetuple())))
     700                string += BACKEND_SEP + str(int(mktime(self.endtime.timetuple())))
     701                string += BACKEND_SEP + str(self.duplicate)
     702                string += BACKEND_SEP + str(self.shareable)
     703                string += BACKEND_SEP + str(self.findid)
     704                string += BACKEND_SEP + self.hostname
     705                string += BACKEND_SEP + str(self.sourceid)
     706                string += BACKEND_SEP + str(self.cardid)
     707                string += BACKEND_SEP + str(self.inputid)
     708                string += BACKEND_SEP + str(self.recpriority)
     709                string += BACKEND_SEP + str(self.recstatus)
     710                string += BACKEND_SEP + str(self.recordid)
     711                string += BACKEND_SEP + self.rectype
     712                string += BACKEND_SEP + self.dupin
     713                string += BACKEND_SEP + self.dupmethod
     714                string += BACKEND_SEP + str(int(mktime(self.recstartts.timetuple())))
     715                string += BACKEND_SEP + str(int(mktime(self.recendts.timetuple())))
     716                string += BACKEND_SEP + str(self.repeat)
     717                string += BACKEND_SEP + self.programflags
     718                string += BACKEND_SEP + self.recgroup
     719                string += BACKEND_SEP + str(self.commfree)
     720                string += BACKEND_SEP + self.outputfilters
     721                string += BACKEND_SEP + self.seriesid
     722                string += BACKEND_SEP + self.programid
     723                string += BACKEND_SEP + self.lastmodified
     724                string += BACKEND_SEP + str(self.stars)
     725                string += BACKEND_SEP + self.airdate
     726                string += BACKEND_SEP + str(self.hasairdate)
     727                string += BACKEND_SEP + self.playgroup
     728                string += BACKEND_SEP + str(self.recpriority2)
     729                string += BACKEND_SEP + self.parentid
     730                string += BACKEND_SEP + self.storagegroup
     731                string += BACKEND_SEP + self.audio_props
     732                string += BACKEND_SEP + self.video_props
     733                string += BACKEND_SEP + self.subtitle_type
     734                string += BACKEND_SEP + self.year
     735
     736                return string
     737
    307738if __name__ == '__main__':
    308739        banner = '\'m\' is a MythTV instance.'
    309740        try:
  • mythtv/bindings/python/MythTV/MythDB.py

     
    55"""
    66import os
    77import sys
    8 import shlex
     8import xml.dom.minidom as minidom
    99import code
    1010import getopt
    1111from datetime import datetime
     
    3232                                'host' : None,
    3333                                'name' : None,
    3434                                'user' : None,
    35                                 'pass' : None
     35                                'pass' : None,
     36                                'USN'  : None,
     37                                'PIN'  : None
    3638                                }
    3739
    38                 # Try to read the mysql.txt file used by MythTV.
     40                # Try to read the config.xml file used by MythTV.
    3941                # Order taken from libs/libmyth/mythcontext.cpp
    40                 config_files = [
    41                                 '/usr/local/share/mythtv/mysql.txt',
    42                                 '/usr/share/mythtv/mysql.txt',
    43                                 '/usr/local/etc/mythtv/mysql.txt',
    44                                 '/etc/mythtv/mysql.txt',
    45                                 os.path.expanduser('~/.mythtv/mysql.txt'),
    46                                 ]
     42                config_files = [ os.path.expanduser('~/.mythtv/config.xml') ]
    4743                if 'MYTHCONFDIR' in os.environ:
    48                         config_locations.append('%s/mysql.txt' % os.environ['MYTHCONFDIR'])
     44                        config_files.append('%s/config.xml' % os.environ['MYTHCONFDIR'])
    4945
    5046                found_config = False
    5147                for config_file in config_files:
    5248                        try:
    53                                 config = shlex.shlex(open(config_file))
    54                                 config.wordchars += "."
     49                                config = minidom.parse(config_file)
    5550                        except:
    5651                                continue
    5752
     
    5954                        dbconn['name'] = None
    6055                        dbconn['user'] = None
    6156                        dbconn['pass'] = None
    62                         token = config.get_token()
    63                         while token != config.eof and not found_config:
    64                                 if token == "DBHostName":
    65                                         if config.get_token() == "=":
    66                                                 dbconn['host'] = config.get_token()
    67                                 elif token == "DBName":
    68                                         if config.get_token() == "=":
    69                                                 dbconn['name'] = config.get_token()
    70                                 elif token == "DBUserName":
    71                                         if config.get_token() == "=":
    72                                                 dbconn['user'] = config.get_token()
    73                                 elif token == "DBPassword":
    74                                         if config.get_token() == "=":
    75                                                 dbconn['pass'] = config.get_token()
    76                                 token = config.get_token()
     57
     58                        for token in config.getElementsByTagName('Configuration')[0].getElementsByTagName('UPnP')[0].getElementsByTagName('MythFrontend')[0].getElementsByTagName('DefaultBackend')[0].childNodes:
     59                                if token.nodeType == token.TEXT_NODE:
     60                                        continue
     61                                try:
     62                                        if token.tagName == "DBHostName":
     63                                                dbconn['host'] = token.childNodes[0].data
     64                                        elif token.tagName == "DBName":
     65                                                dbconn['name'] = token.childNodes[0].data
     66                                        elif token.tagName == "DBUserName":
     67                                                dbconn['user'] = token.childNodes[0].data
     68                                        elif token.tagName == "DBPassword":
     69                                                dbconn['pass'] = token.childNodes[0].data
     70                                        elif token.tagName == "USN":
     71                                                dbconn['USN'] = token.childNodes[0].data
     72                                        elif token.tagName == "SecurityPin":
     73                                                dbconn['PIN'] = token.childNodes[0].data
     74                                except:
     75                                        pass
     76
    7777                        if dbconn['host'] != None and dbconn['name'] != None and dbconn['user'] != None and dbconn['pass'] != None:
    7878                                log.Msg(INFO, 'Using config %s', config_file)
    7979                                found_config = True
     
    9494                except:
    9595                        pass
    9696
     97                # UPnP failover support to be added, no libraries currently available
     98
    9799                if not dbconn['host'] and not found_config:
    98100                        raise MythError('Unable to find MythTV configuration file')
    99101
     
    160162                else:
    161163                        return None
    162164
     165        def setSetting(self, value, data, hostname=None):
     166                """
     167                Sets the value for the given MythTV setting.
     168                """
     169                log.Msg(DEBUG, 'Setting %s for host %s to %s', value, hostname, data)
     170                c = self.db.cursor()
     171                ws = None
     172                ss = None
     173
     174                if hostname is None:
     175                        ws = "WHERE value LIKE ('%s') AND hostname IS NULL" % (value)
     176                        ss = "(value,data) VALUES ('%s','%s')" % (value, data)
     177                else:
     178                        ws = "WHERE value LIKE ('%s') AND hostname LIKE ('%s%%')" % (value, hostname)
     179                        ss = "(value,data,hostname) VALUES ('%s','%s','%s')" % (value, data, hostname)
     180
     181                if c.execute("""UPDATE settings SET data %s LIMIT 1""" % ws) == 0:
     182                        c.execute("""INSERT INTO settings %s""" % ss)
     183                c.close()
     184
     185        def getCast(self, chanid, starttime, roles=None):
     186                """
     187                Returns cast members for a recording
     188                A string for 'roles' will return a tuple of members for that role
     189                A tuple of strings will return a touple containing all listed roles
     190                No 'roles' will return a dictionary of tuples
     191                """
     192                if roles is None:
     193                        c = self.db.cursor()
     194                        length = c.execute("SELECT name,role FROM people,credits WHERE people.person=credits.person AND chanid=%d AND starttime=%d ORDER BY role" % (chanid, starttime))
     195                        if length == 0:
     196                                return ()
     197                        crole = None
     198                        clist = []
     199                        dict = {}
     200                        for name,role in c.fetchall():
     201                                if crole is None:
     202                                        crole = role
     203                                if crole == role:
     204                                        clist.append(name)
     205                                else:
     206                                        dict[crole] = tuple(clist)
     207                                        clist = []
     208                                        clist.append(name)
     209                                        crole = role
     210                        dict[crole] = tuple(clist)
     211                        c.close()
     212                        return dict
     213                elif isinstance(roles,str):
     214                        c = self.db.cursor()
     215                        length = c.execute("SELECT name FROM people,credits WHERE people.person=credits.person AND chanid=%d AND starttime=%d AND role='%s'" % (chanid, starttime, roles))
     216                        if length == 0:
     217                                return ()
     218                        names = []
     219                        for name in c.fetchall():
     220                                names.append(name[0])
     221                        return tuple(names)
     222                elif isinstance(roles,tuple):
     223                        c = self.db.cursor()
     224                        length = c.execute("SELECT name FROM people,credits WHERE people.person=credits.person AND chanid=%d AND starttime=%d AND role IN %s" % (chanid, starttime, roles))
     225                        if length == 0:
     226                                return ()
     227                        names = []
     228                        for name in c.fetchall():
     229                                names.append(name[0])
     230                        return tuple(names)
     231
    163232        def cursor(self):
    164233                return self.db.cursor()
    165234
     235class Job:
     236        jobid = None
     237        chanid = None
     238        starttime = None
     239        host = None
     240        mythdb = None
     241        def __init__(self, *inp):
     242                if len(inp) == 1:
     243                        self.jobid = inp[0]
     244                        self.getProgram()
     245                elif len(inp) == 2:
     246                        self.chanid = inp[0]
     247                        self.starttime = inp[1]
     248                        self.getJobID()
     249                else:
     250                        print("improper input length")
     251                        return None
     252                self.getHost()
     253
     254        def getProgram(self):
     255                if self.mythdb is None:
     256                        self.mythdb = MythDB()
     257                c = self.mythdb.cursor()
     258                c.execute("SELECT chanid,starttime FROM jobqueue WHERE id=%d" % self.jobid)
     259                self.chanid, self.starttime = c.fetchone()
     260                c.close()
     261
     262        def getJobID(self):
     263                if self.mythdb is None:
     264                        self.mythdb = MythDB()
     265                if self.jobid is None:
     266                        c = self.mythdb.cursor()
     267                        c.execute("SELECT id FROM jobqueue WHERE chanid=%d AND starttime=%d" % (self.chanid, self.starttime))
     268                        self.jobid = c.fetchone()[0]
     269                        c.close()
     270                return self.jobid
     271
     272        def getHost(self):
     273                if self.mythdb is None:
     274                        self.mythdb = MythDB()
     275                if self.host is None:
     276                        c = self.mythdb.cursor()
     277                        c.execute("SELECT hostname FROM jobqueue WHERE id=%d" % self.jobid)
     278                        self.host = c.fetchone()[0]
     279                        c.close()
     280                return self.host
     281
     282        def setComment(self,comment):
     283                if self.mythdb is None:
     284                        self.mythdb = MythDB()
     285                c = self.mythdb.cursor()
     286                c.execute("UPDATE jobqueue SET comment='%s' WHERE id=%d" % (comment,self.jobid))
     287                c.close()
     288
     289        def setStatus(self,status):
     290                if self.mythdb is None:
     291                        self.mythdb = MythDB()
     292                c = self.mythdb.cursor()
     293                c.execute("UPDATE jobqueue SET status=%d WHERE id=%d" % (status,self.jobid))
     294                c.close()
     295
    166296if __name__ == '__main__':
    167297        banner = "'mdb' is a MythDB instance."
    168298        try: