Ticket #6678: pyth.update9.patch

File pyth.update9.patch, 23.6 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 deleteRecording(self,program,force=False):
     266                """
     267                Deletes recording, set 'force' true if file is not available for deletion
     268                Returns the number fewer recordings that exist afterwards
     269                """
     270                command = 'DELETE_RECORDING'
     271                if force:
     272                        command = 'FORCE_DELETE_RECORDING'
     273                return self.backendCommand('%s%s%s' % (command,BACKEND_SEP,program.toString()))
     274
     275        def forgetRecording(self,program):
     276                """
     277                Forgets old recording and allows it to be re-recorded
     278                """
     279                self.backendCommand('FORGET_RECORDING%s%s' % (BACKEND_SEP,program.toString()))
     280
     281        def getFreeSpace(self,all=False):
     282                """
     283                Returns a tuple of tuples, in the form:
     284                        str   hostname
     285                        str   path
     286                        bool  is_local
     287                        int   drive number
     288                        int   storage group ID
     289                        int   total space (in KB)
     290                        int   used space (in KB)
     291                """
     292                command = 'QUERY_FREE_SPACE'
     293                if all:
     294                        command = 'QUERY_FREE_SPACE_LIST'
     295                res = self.backendCommand(command).split(BACKEND_SEP)
     296                dirs = []
     297                for i in range(0,len(res)/9):
     298                        line = [res[i*9]]
     299                        line.append(res[i*9+1])
     300                        line.append(bool(int(res[i*9+2])))
     301                        line.append(int(res[i*9+3]))
     302                        line.append(int(res[i*9+4]))
     303                        line.append(self.joinInt(int(res[i*9+5]),int(res[i*9+6])))
     304                        line.append(self.joinInt(int(res[i*9+7]),int(res[i*9+8])))
     305                        dirs.append(tuple(line))
     306                return tuple(dirs)
     307
     308        def getFreeSpaceSummary(self):
     309                """
     310                Returns a tuple of total space (in KB) and used space (in KB)
     311                """
     312                res = self.backendCommand('QUERY_FREE_SPACE_SUMMARY').split(BACKEND_SEP)
     313                return (self.joinInt(int(res[0]),int(res[1])),self.joinInt(int(res[2]),int(res[3])))
     314
     315        def getLoad(self):
     316                """
     317                Returns a tuple of the 1, 5, and 15 minute load averages
     318                """
     319                res = self.backendCommand('QUERY_LOAD').split(BACKEND_SEP)
     320                return (float(res[0]),float(res[1]),float(res[2]))
     321
     322        def getFrontends(self):
     323                """
     324                Returns a list of Frontend objects for accessible frontends
     325                """
     326                cursor = self.db.db.cursor()
     327                cursor.execute("SELECT DISTINCT hostname FROM settings WHERE hostname IS NOT NULL and value='NetworkControlEnabled' and data=1")
     328                frontends = []
     329                for fehost in cursor.fetchall():
     330                        try:
     331                                frontend = self.getFrontend(fehost[0])
     332                                frontends.append(frontend)
     333                        except:
     334                                print "%s is not a valid frontend" % fehost[0]
     335                cursor.close()
     336                return frontends
     337
     338        def getFrontend(self,host):
     339                """
     340                Returns a Frontend object for the specified host
     341                """
     342                port = self.db.getSetting("NetworkControlPort",host)
     343                return Frontend(host,port)
     344
     345        def joinInt(self,high,low):
     346                """
     347                Returns a single long from a pair of signed integers
     348                """
     349                return (high + (low<0))*2**32 + low
     350
     351        def splitInt(self,integer):
     352                """
     353                Returns a pair of signed integers from a single long
     354                """
     355                return integer/(2**32),integer%2**32 - (integer%2**32 > 2**31)*2**32
     356
     357class FileTransfer:
     358        """
     359        A connection to mythbackend intended for file transfers
     360        """
     361        sockno = None
     362        socket = None
     363        tsize = 2**16
     364
     365        def __init__(self, file, parent=None):
     366                self.db = MythDB(sys.argv[1:])
     367                if not isinstance(parent, MythTV):
     368                        self.parent = MythTV()
     369                else:
     370                        self.parent = parent
     371                if isinstance(file, Program):
     372                        self.master_host, self.master_port = file.filename.split('/')[-2].split(':')
     373                        self.master_port = int(self.master_port)
     374                        self.filename = file.filename.split('/')[-1]
     375                        self.sgroup = file.storagegroup
     376                elif isinstance(file, tuple):
     377                        if len(file) != 3:
     378                                log.Msg(CRITICAL, 'Incorrect FileTransfer() input size')
     379                                sys.exit(1)
     380                        else:
     381                                self.master_host = file[0]
     382                                self.master_port = int(self.db.getSetting('BackendServerPort',self.master_host))
     383                                self.filename = file[1]
     384                                self.sgroup = file[2]
     385                else:
     386                        log.Msg(CRITICAL, 'Improper input to FileTransfer()')
     387                        sys.exit(1)
     388
     389                try:
     390                        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     391                        self.socket.settimeout(10)
     392                        self.socket.connect((self.master_host, self.master_port))
     393                        res = self.send('MYTH_PROTO_VERSION %s' % PROTO_VERSION).split(BACKEND_SEP)
     394                        if res[0] == 'REJECT':
     395                                log.Msg(CRITICAL, 'Backend has version %s and we speak version %s', res[1], PROTO_VERSION)
     396                                sys.exit(1)
     397                       
     398                        res = self.send('ANN FileTransfer %s%s%s%s%s' % (socket.gethostname(), BACKEND_SEP, self.filename, BACKEND_SEP, self.sgroup))
     399                        if res.split(BACKEND_SEP)[0] != 'OK':
     400                                log.Msg(CRITICAL, 'Unexpected answer to ANN command: %s', res)
     401                        else:
     402                                log.Msg(INFO, 'Successfully connected mythbackend at %s:%d', self.master_host, self.master_port)
     403                                sp = res.split(BACKEND_SEP)
     404                                self.sockno = int(sp[1])
     405                                self.pos = 0
     406                                self.size = (int(sp[2]) + (int(sp[3])<0))*2**32 + int(sp[3])
     407                               
     408                except socket.error, e:
     409                        log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.master_host, self.master_port)
     410                        sys.exit(1)
     411
     412        def __del__(self):
     413                if self.sockno:
     414                        self.parent.backendCommand('QUERY_FILETRANSFER %d%sDONE' % (self.sockno, BACKEND_SEP))
     415                if self.socket:
     416                        self.socket.shutdown(1)
     417                        self.socket.close()
     418
     419        def send(self,data):
     420                command = '%-8d%s' % (len(data), data)
     421                log.Msg(DEBUG, 'Sending command: %s', command)
     422                self.socket.send(command)
     423                return self.recv()
     424
     425        def recv(self):
     426                data = self.socket.recv(8)
     427                try:
     428                        length = int(data)
     429                except:
     430                        return ''
     431                data = []
     432                while length > 0:
     433                        chunk = self.socket.recv(length)
     434                        length = length - len(chunk)
     435                        data.append(chunk)
     436                return ''.join(data)
     437
     438        def tell(self):
     439                """
     440                Return the current offset from the beginning of the file
     441                """
     442                return self.pos
     443
     444        def close(self):
     445                """
     446                Close the data transfer socket
     447                """
     448                self.__del__()
     449
     450        def rewind(self):
     451                """
     452                Seek back to the start of the file
     453                """
     454                self.seek(0)
     455
     456        def read(self, size):
     457                """
     458                Read a block of data, requests over 64KB will be buffered internally
     459                """
     460                if size == 0:
     461                        return ''
     462                if size > self.size - self.pos:
     463                        size = self.size - self.pos
     464                csize = size
     465                rsize = 0
     466                if csize > self.tsize:
     467                        csize = self.tsize
     468                        rsize = size - csize
     469                       
     470                res = self.parent.backendCommand('QUERY_FILETRANSFER %d%sREQUEST_BLOCK%s%d' % (self.sockno,BACKEND_SEP,BACKEND_SEP,csize))
     471                self.pos = self.pos + int(res)
     472#               if int(res) == csize:
     473#                       if csize < size:
     474#                               self.tsize += 8192
     475#               else:
     476#                       self.tsize -= 8192
     477#                       rsize = size - int(res)
     478#               print 'resizing buffer to %d' % self.tsize
     479
     480                return self.socket.recv(int(res)) + self.read(rsize)
     481
     482        def seek(self, offset, whence=0):
     483                """
     484                Seek 'offset' number of bytes
     485                   whence==0 - from start of file
     486                   whence==1 - from current position
     487                """
     488                if whence == 0:
     489                        if offset < 0:
     490                                offset = 0
     491                        if offset > self.size:
     492                                offset = self.size
     493                elif whence == 1:
     494                        if offset + self.pos < 0:
     495                                offset = -self.pos
     496                        if offset + self.pos > self.size:
     497                                offset = self.size - self.pos
     498                elif whence == 2:
     499                        if offset > 0:
     500                                offset = 0
     501                        if offset < -self.size:
     502                                offset = -self.size
     503                else:
     504                        log.Msg(CRITICAL, 'Whence can only be 0, 1, or 2')
     505
     506                curhigh,curlow = self.parent.splitInt(self.pos)
     507                offhigh,offlow = self.parent.splitInt(offset)
     508
     509                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)
     510                self.pos = (int(res[0]) + (int(res[1])<0))*2**32 + int(res[1])
     511
     512
     513class Frontend:
     514        isConnected = False
     515        socket = None
     516        host = None
     517        port = None
     518
     519        def __init__(self, host, port):
     520                self.host = host
     521                self.port = int(port)
     522                self.connect()
     523                self.disconnect()
     524
     525        def __del__(self):
     526                if self.isConnected:
     527                        self.disconnect()
     528
     529        def __repr__(self):
     530                return "%s@%d" % (self.host, self.port)
     531
     532        def __str__(self):
     533                return "%s@%d" % (self.host, self.port)
     534
     535        def connect(self):
     536                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     537                self.socket.settimeout(10)
     538                self.socket.connect((self.host, self.port))
     539                if self.recv()[:28] != "MythFrontend Network Control":
     540                        self.socket.close()
     541                        self.socket = None
     542                        raise Exception('FrontendConnect','Connected socket does not belong to a mythfrontend')
     543                self.isConnected = True
     544
     545        def disconnect(self):
     546                self.send("exit")
     547                self.socket.close()
     548                self.socket = None
     549                self.isConnected = False
     550
     551        def send(self,command):
     552                if not self.isConnected:
     553                        self.connect()
     554                self.socket.send("%s\n" % command)
     555
     556        def recv(self,curstr=""):
     557                def subrecv(self,curstr=""):
     558                        try:
     559                                curstr += self.socket.recv(100)
     560                        except:
     561                                return None
     562                        if curstr[-4:] != '\r\n# ':
     563                                curstr = subrecv(self,curstr)
     564                        return curstr
     565                return subrecv(self)[:-4]
     566
     567        def sendJump(self,jumppoint):
     568                """
     569                Sends jumppoint to frontend
     570                """
     571                self.send("jump %s" % jumppoint)
     572                if self.recv() == 'OK':
     573                        return 0
     574                else:
     575                        return 1
     576
     577        def getJump(self):
     578                """
     579                Returns a tuple containing available jumppoints
     580                """
     581                self.send("help jump")
     582                res = self.recv().split('\r\n')[3:-1]
     583                points = []
     584                for point in res:
     585                        spoint = point.split(' - ')
     586                        points.append((spoint[0].rstrip(),spoint[1]))
     587                return tuple(points)
     588
     589        def sendKey(self,key):
     590                """
     591                Sends keycode to connected frontend
     592                """
     593                self.send("key %s" % key)
     594                if self.recv() == 'OK':
     595                        return 0
     596                else:
     597                        return 1
     598
     599        def getKey(self):
     600                """
     601                Returns a tuple containing available special keys
     602                """
     603                self.send("help key")
     604                res = self.recv().split('\r\n')[4]
     605                keys = []
     606                for key in res.split(','):
     607                        keys.append(key.strip())
     608                return tuple(keys)
     609
     610        def sendQuery(self,query):
     611                """
     612                Returns query from connected frontend
     613                """
     614                self.send("query %s" % query)
     615                return self.recv()
     616
     617        def getQuery(self):
     618                """
     619                Returns a tuple containing available queries
     620                """
     621                self.send("help query")
     622                res = self.recv().split('\r\n')[:-1]
     623                queries = []
     624                tmpstr = ""
     625                for query in res:
     626                        tmpstr += query
     627                        squery = tmpstr.split(' - ')
     628                        if len(squery) == 2:
     629                                tmpstr = ""
     630                                queries.append((squery[0].rstrip().lstrip('query '),squery[1]))
     631                return tuple(queries)
     632
     633        def sendPlay(self,play):
     634                """
     635                Send playback command to connected frontend
     636                """
     637                self.send("play %s" % play)
     638                if self.recv() == 'OK':
     639                        return 0
     640                else:
     641                        return 1
     642
     643        def getPlay(self):
     644                """
     645                Returns a tuple containing available playback commands
     646                """
     647                self.send("help play")
     648                res = self.recv().split('\r\n')[:-1]
     649                plays = []
     650                tmpstr = ""
     651                for play in res:
     652                        tmpstr += play
     653                        splay = tmpstr.split(' - ')
     654                        if len(splay) == 2:
     655                                tmpstr = ""
     656                                plays.append((splay[0].rstrip().lstrip('play '),splay[1]))
     657                return tuple(plays)
     658                       
     659               
     660
    223661class Recorder:
    224662        """
    225663        Represents a MythTV capture card.
     
    265703                self.callsign = data[6] #chansign
    266704                self.channame = data[7]
    267705                self.filename = data[8] #pathname
    268                 self.fs_high = data[9]
    269                 self.fs_low = data[10]
     706                self.fs_high = int(data[9])
     707                self.fs_low = int(data[10])
    270708                self.starttime = datetime.fromtimestamp(int(data[11])) # startts
    271709                self.endtime = datetime.fromtimestamp(int(data[12])) #endts
    272710                self.duplicate = int(data[13])
     
    303741                self.video_props = data[44]
    304742                self.subtitle_type = data[45]
    305743                self.year = data[46]
     744               
     745                self.filesize = (self.fs_high + (self.fs_low<0))*2**32 + self.fs_low
    306746
     747        def toString(self):
     748                string =            self.title
     749                string += BACKEND_SEP + self.subtitle
     750                string += BACKEND_SEP + self.description
     751                string += BACKEND_SEP + self.category
     752                if self.chanid:
     753                        string += BACKEND_SEP + str(self.chanid)
     754                else:
     755                        string += BACKEND_SEP
     756                string += BACKEND_SEP + self.channum
     757                string += BACKEND_SEP + self.callsign
     758                string += BACKEND_SEP + self.channame
     759                string += BACKEND_SEP + self.filename
     760                string += BACKEND_SEP + str(self.fs_high)
     761                string += BACKEND_SEP + str(self.fs_low)
     762                string += BACKEND_SEP + str(int(mktime(self.starttime.timetuple())))
     763                string += BACKEND_SEP + str(int(mktime(self.endtime.timetuple())))
     764                string += BACKEND_SEP + str(self.duplicate)
     765                string += BACKEND_SEP + str(self.shareable)
     766                string += BACKEND_SEP + str(self.findid)
     767                string += BACKEND_SEP + self.hostname
     768                string += BACKEND_SEP + str(self.sourceid)
     769                string += BACKEND_SEP + str(self.cardid)
     770                string += BACKEND_SEP + str(self.inputid)
     771                string += BACKEND_SEP + str(self.recpriority)
     772                string += BACKEND_SEP + str(self.recstatus)
     773                string += BACKEND_SEP + str(self.recordid)
     774                string += BACKEND_SEP + self.rectype
     775                string += BACKEND_SEP + self.dupin
     776                string += BACKEND_SEP + self.dupmethod
     777                string += BACKEND_SEP + str(int(mktime(self.recstartts.timetuple())))
     778                string += BACKEND_SEP + str(int(mktime(self.recendts.timetuple())))
     779                string += BACKEND_SEP + str(self.repeat)
     780                string += BACKEND_SEP + self.programflags
     781                string += BACKEND_SEP + self.recgroup
     782                string += BACKEND_SEP + str(self.commfree)
     783                string += BACKEND_SEP + self.outputfilters
     784                string += BACKEND_SEP + self.seriesid
     785                string += BACKEND_SEP + self.programid
     786                string += BACKEND_SEP + self.lastmodified
     787                string += BACKEND_SEP + str(self.stars)
     788                string += BACKEND_SEP + self.airdate
     789                string += BACKEND_SEP + str(self.hasairdate)
     790                string += BACKEND_SEP + self.playgroup
     791                string += BACKEND_SEP + str(self.recpriority2)
     792                string += BACKEND_SEP + self.parentid
     793                string += BACKEND_SEP + self.storagegroup
     794                string += BACKEND_SEP + self.audio_props
     795                string += BACKEND_SEP + self.video_props
     796                string += BACKEND_SEP + self.subtitle_type
     797                string += BACKEND_SEP + self.year
     798
     799                return string
     800
    307801if __name__ == '__main__':
    308802        banner = '\'m\' is a MythTV instance.'
    309803        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.
    39                 # 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                                 ]
     40                # Try to read the config.xml file used by MythTV.
     41                config_files = [ os.path.expanduser('~/.mythtv/config.xml') ]
    4742                if 'MYTHCONFDIR' in os.environ:
    48                         config_locations.append('%s/mysql.txt' % os.environ['MYTHCONFDIR'])
     43                        config_locations.append('%s/config.xml' % os.environ['MYTHCONFDIR'])
    4944
    5045                found_config = False
    5146                for config_file in config_files:
    5247                        try:
    53                                 config = shlex.shlex(open(config_file))
    54                                 config.wordchars += "."
     48                                config = minidom.parse(config_file)
    5549                        except:
    5650                                continue
    5751
     
    5953                        dbconn['name'] = None
    6054                        dbconn['user'] = None
    6155                        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()
     56                        for token in config.getElementsByTagName('Configuration')[0].getElementsByTagName('UPnP')[0].getElementsByTagName('MythFrontend')[0].getElementsByTagName('DefaultBackend')[0].childNodes:
     57                                if token.nodeType == token.TEXT_NODE:
     58                                        continue
     59                                try:
     60                                        if token.tagName == "DBHostName":
     61                                                dbconn['host'] = token.childNodes[0].data
     62                                        elif token.tagName == "DBName":
     63                                                dbconn['name'] = token.childNodes[0].data
     64                                        elif token.tagName == "DBUserName":
     65                                                dbconn['user'] = token.childNodes[0].data
     66                                        elif token.tagName == "DBPassword":
     67                                                dbconn['pass'] = token.childNodes[0].data
     68                                        elif token.tagName == "USN":
     69                                                dbconn['USN'] = token.childNodes[0].data
     70                                        elif token.tagName == "SecurityPin":
     71                                                dbconn['PIN'] = token.childNodes[0].data
     72                                except:
     73                                        pass
     74
    7775                        if dbconn['host'] != None and dbconn['name'] != None and dbconn['user'] != None and dbconn['pass'] != None:
    7876                                log.Msg(INFO, 'Using config %s', config_file)
    7977                                found_config = True
     
    160158                else:
    161159                        return None
    162160
     161        def setSetting(self, value, data, hostname=None):
     162                """
     163                Sets the value for the given MythTV setting.
     164                """
     165                log.Msg(DEBUG, 'Setting %s for host %s to %s', value, hostname, data)
     166                c = self.db.cursor()
     167                ws = None
     168                ss = None
     169
     170                if hostname is None:
     171                        ws = "WHERE value LIKE ('%s') AND hostname IS NULL" % (value)
     172                        ss = "(value,data) VALUES ('%s','%s')" % (value, data)
     173                else:
     174                        ws = "WHERE value LIKE ('%s') AND hostname LIKE ('%s%%')" % (value, hostname)
     175                        ss = "(value,data,hostname) VALUES ('%s','%s','%s')" % (value, data, hostname)
     176
     177                if c.execute("""UPDATE settings SET data %s LIMIT 1""" % ws) == 0:
     178                        c.execute("""INSERT INTO settings %s""" % ss)
     179                c.close()
     180
     181        def getCast(self, chanid, starttime, roles=None):
     182                """
     183                Returns cast members for a recording
     184                A string for 'roles' will return a tuple of members for that role
     185                A tuple of strings will return a touple containing all listed roles
     186                No 'roles' will return a dictionary of tuples
     187                """
     188                if roles is None:
     189                        c = self.db.cursor()
     190                        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))
     191                        if length == 0:
     192                                return ()
     193                        crole = None
     194                        clist = []
     195                        dict = {}
     196                        for name,role in c.fetchall():
     197                                if crole is None:
     198                                        crole = role
     199                                if crole == role:
     200                                        clist.append(name)
     201                                else:
     202                                        dict[crole] = tuple(clist)
     203                                        clist = []
     204                                        clist.append(name)
     205                                        crole = role
     206                        dict[crole] = tuple(clist)
     207                        c.close()
     208                        return dict
     209                elif isinstance(roles,str):
     210                        c = self.db.cursor()
     211                        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))
     212                        if length == 0:
     213                                return ()
     214                        names = []
     215                        for name in c.fetchall():
     216                                names.append(name[0])
     217                        return tuple(names)
     218                elif isinstance(roles,tuple):
     219                        c = self.db.cursor()
     220                        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))
     221                        if length == 0:
     222                                return ()
     223                        names = []
     224                        for name in c.fetchall():
     225                                names.append(name[0])
     226                        return tuple(names)
     227
    163228        def cursor(self):
    164229                return self.db.cursor()
    165230
     231class Job:
     232        jobid = None
     233        chanid = None
     234        starttime = None
     235        host = None
     236        mythdb = None
     237        def __init__(self, *inp):
     238                if len(inp) == 1:
     239                        self.jobid = inp[0]
     240                        self.getProgram()
     241                elif len(inp) == 2:
     242                        self.chanid = inp[0]
     243                        self.starttime = inp[1]
     244                        self.getJobID()
     245                else:
     246                        print("improper input length")
     247                        return None
     248                self.getHost()
     249
     250        def getProgram(self):
     251                if self.mythdb is None:
     252                        self.mythdb = MythDB()
     253                c = self.mythdb.cursor()
     254                c.execute("SELECT chanid,starttime FROM jobqueue WHERE id=%d" % self.jobid)
     255                self.chanid, self.starttime = c.fetchone()
     256                c.close()
     257
     258        def getJobID(self):
     259                if self.mythdb is None:
     260                        self.mythdb = MythDB()
     261                if self.jobid is None:
     262                        c = self.mythdb.cursor()
     263                        c.execute("SELECT id FROM jobqueue WHERE chanid=%d AND starttime=%d" % (self.chanid, self.starttime))
     264                        self.jobid = c.fetchone()[0]
     265                        c.close()
     266                return self.jobid
     267
     268        def getHost(self):
     269                if self.mythdb is None:
     270                        self.mythdb = MythDB()
     271                if self.host is None:
     272                        c = self.mythdb.cursor()
     273                        c.execute("SELECT hostname FROM jobqueue WHERE id=%d" % self.jobid)
     274                        self.host = c.fetchone()[0]
     275                        c.close()
     276                return self.host
     277
     278        def setComment(self,comment):
     279                if self.mythdb is None:
     280                        self.mythdb = MythDB()
     281                c = self.mythdb.cursor()
     282                c.execute("UPDATE jobqueue SET comment='%s' WHERE id=%d" % (comment,self.jobid))
     283                c.close()
     284
     285        def setStatus(self,status):
     286                if self.mythdb is None:
     287                        self.mythdb = MythDB()
     288                c = self.mythdb.cursor()
     289                c.execute("UPDATE jobqueue SET status=%d WHERE id=%d" % (status,self.jobid))
     290                c.close()
     291
    166292if __name__ == '__main__':
    167293        banner = "'mdb' is a MythDB instance."
    168294        try: