Ticket #6678: pyth.update10.patch

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

     
    1313import shlex
    1414import socket
    1515import code
     16import re
    1617from datetime import datetime
     18from time import mktime
    1719
    1820from MythDB import *
    1921from MythLog import *
     
    8082                        log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.master_host, self.master_port)
    8183                        sys.exit(1)
    8284
     85        def __del__(self):
     86                self.backendCommand('DONE')
     87                self.socket.shutdown(1)
     88                self.socket.close()
     89
    8390        def backendCommand(self, data):
    8491                """
    8592                Sends a formatted command via a socket to the mythbackend.
     
    95102                        try:
    96103                                length = int(data)
    97104                        except:
    98                                 return ''
     105                                return u''
    99106                        data = []
    100107                        while length > 0:
    101108                                chunk = self.socket.recv(length)
    102109                                length = length - len(chunk)
    103110                                data.append(chunk)
    104                         return ''.join(data)
     111                        try:
     112                                return unicode(''.join(data),'utf8')
     113                        except:
     114                                return u''.join(data)
    105115
    106                 command = '%-8d%s' % (len(data), data)
     116                command = u'%-8d%s' % (len(data), data)
    107117                log.Msg(DEBUG, 'Sending command: %s', command)
    108118                self.socket.send(command)
    109119                return recv()
     
    120130                for i in range(num_progs):
    121131                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
    122132                                + PROGRAM_FIELDS]))
    123                 return programs
     133                return tuple(programs)
    124134
    125135        def getScheduledRecordings(self):
    126136                """
     
    133143                for i in range(num_progs):
    134144                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
    135145                                + PROGRAM_FIELDS]))
    136                 return programs
     146                return tuple(programs)
    137147
    138148        def getUpcomingRecordings(self):
    139149                """
     
    155165                        if p.recstatus == RECSTATUS['WillRecord']:
    156166                                programs.append(p)
    157167                programs.sort(sort_programs_by_starttime)
    158                 return programs
     168                return tuple(programs)
    159169
    160170        def getRecorderList(self):
    161171                """
    162172                Returns a list of recorders, or an empty list if none.
    163173                """
    164174                recorders = []
    165                 c = self.db.cursor()
     175                pc = self.db.cursor()
    166176                c.execute('SELECT cardid FROM capturecard')
    167177                row = c.fetchone()
    168178                while row is not None:
     
    220230                else:
    221231                        return False
    222232
     233        def getRecording(self, chanid, starttime):
     234                """
     235                Returns a Program object matching the channel id and start time
     236                """
     237                res = self.backendCommand('QUERY_RECORDING TIMESLOT %d %d' % (chanid, starttime)).split(BACKEND_SEP)
     238                if res[0] == 'ERROR':
     239                        return None
     240                else:
     241                        return Program(res[1:])
     242
     243        def getRecordings(self):
     244                """
     245                Returns a list of all Program objects which have already recorded
     246                """
     247                programs = []
     248                res = self.backendCommand('QUERY_RECORDINGS Play').split('[]:[]')
     249                num_progs = int(res.pop(0))
     250                log.Msg(DEBUG, '%s total recordings', num_progs)
     251                for i in range(num_progs):
     252                        programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS)
     253                                + PROGRAM_FIELDS]))
     254                return tuple(programs)
     255
     256        def getCheckfile(self,program):
     257                """
     258                Returns location of recording in file system
     259                """
     260                res = self.backendCommand('QUERY_CHECKFILE[]:[]1[]:[]%s' % program.toString()).split(BACKEND_SEP)
     261                if res[0] == 0:
     262                        return None
     263                else:
     264                        return res[1]
     265
     266        def deleteRecording(self,program,force=False):
     267                """
     268                Deletes recording, set 'force' true if file is not available for deletion
     269                Returns the number fewer recordings that exist afterwards
     270                """
     271                command = 'DELETE_RECORDING'
     272                if force:
     273                        command = 'FORCE_DELETE_RECORDING'
     274                return self.backendCommand('%s%s%s' % (command,BACKEND_SEP,program.toString()))
     275
     276        def forgetRecording(self,program):
     277                """
     278                Forgets old recording and allows it to be re-recorded
     279                """
     280                self.backendCommand('FORGET_RECORDING%s%s' % (BACKEND_SEP,program.toString()))
     281
     282        def deleteFile(self,file,sgroup):
     283                """
     284                Deletes a file from specified storage group on the connected backend
     285                Takes a relative file path from the root of the storage group, and returns 1 on success
     286                """
     287                return self.backendCommand('DELETE_FILE%s%s%s%s' % (BACKEND_SEP,file,BACKEND_SEP,sgroup))
     288
     289        def getFreeSpace(self,all=False):
     290                """
     291                Returns a tuple of tuples, in the form:
     292                        str   hostname
     293                        str   path
     294                        bool  is_local
     295                        int   drive number
     296                        int   storage group ID
     297                        int   total space (in KB)
     298                        int   used space (in KB)
     299                """
     300                command = 'QUERY_FREE_SPACE'
     301                if all:
     302                        command = 'QUERY_FREE_SPACE_LIST'
     303                res = self.backendCommand(command).split(BACKEND_SEP)
     304                dirs = []
     305                for i in range(0,len(res)/9):
     306                        line = [res[i*9]]
     307                        line.append(res[i*9+1])
     308                        line.append(bool(int(res[i*9+2])))
     309                        line.append(int(res[i*9+3]))
     310                        line.append(int(res[i*9+4]))
     311                        line.append(self.joinInt(int(res[i*9+5]),int(res[i*9+6])))
     312                        line.append(self.joinInt(int(res[i*9+7]),int(res[i*9+8])))
     313                        dirs.append(tuple(line))
     314                return tuple(dirs)
     315
     316        def getFreeSpaceSummary(self):
     317                """
     318                Returns a tuple of total space (in KB) and used space (in KB)
     319                """
     320                res = self.backendCommand('QUERY_FREE_SPACE_SUMMARY').split(BACKEND_SEP)
     321                return (self.joinInt(int(res[0]),int(res[1])),self.joinInt(int(res[2]),int(res[3])))
     322
     323        def getLoad(self):
     324                """
     325                Returns a tuple of the 1, 5, and 15 minute load averages
     326                """
     327                res = self.backendCommand('QUERY_LOAD').split(BACKEND_SEP)
     328                return (float(res[0]),float(res[1]),float(res[2]))
     329
     330        def getSGList(self,host,sg,path):
     331                """
     332                Returns a tuple of directories and files
     333                """
     334                res = self.backendCommand('QUERY_SG_GETFILELIST%s%s%s%s%s%s' % (BACKEND_SEP,host,BACKEND_SEP,sg,BACKEND_SEP,path)).split(BACKEND_SEP)
     335                if res[0] == 'EMPTY LIST':
     336                        return -1
     337                if res[0] == 'SLAVE UNREACHABLE: ':
     338                        return -2
     339                dirs = []
     340                files = []
     341                for entry in res:
     342                        type,name = entry.split('::')
     343                        if type == 'file':
     344                                files.append(name)
     345                        if type == 'dir':
     346                                dirs.append(name)
     347                return (tuple(dirs),tuple(files))
     348               
     349        def getSGFile(self,host,sg,path):
     350                """
     351                Returns a tuple of last modification time and file size
     352                """
     353                res = self.backendCommand('QUERY_SG_FILEQUERY%s%s%s%s%s%s' % (BACKEND_SEP,host,BACKEND_SEP,sg,BACKEND_SEP,path)).split(BACKEND_SEP)
     354                if res[0] == 'EMPTY LIST':
     355                        return -1
     356                if res[0] == 'SLAVE UNREACHABLE: ':
     357                        return -2
     358                return tuple(res[1:3])
     359
     360        def getFrontends(self):
     361                """
     362                Returns a list of Frontend objects for accessible frontends
     363                """
     364                cursor = self.db.db.cursor()
     365                cursor.execute("SELECT DISTINCT hostname FROM settings WHERE hostname IS NOT NULL and value='NetworkControlEnabled' and data=1")
     366                frontends = []
     367                for fehost in cursor.fetchall():
     368                        try:
     369                                frontend = self.getFrontend(fehost[0])
     370                                frontends.append(frontend)
     371                        except:
     372                                print "%s is not a valid frontend" % fehost[0]
     373                cursor.close()
     374                return frontends
     375
     376        def getFrontend(self,host):
     377                """
     378                Returns a Frontend object for the specified host
     379                """
     380                port = self.db.getSetting("NetworkControlPort",host)
     381                return Frontend(host,port)
     382
     383        def joinInt(self,high,low):
     384                """
     385                Returns a single long from a pair of signed integers
     386                """
     387                return (high + (low<0))*2**32 + low
     388
     389        def splitInt(self,integer):
     390                """
     391                Returns a pair of signed integers from a single long
     392                """
     393                return integer/(2**32),integer%2**32 - (integer%2**32 > 2**31)*2**32
     394
     395class FileTransfer:
     396        """
     397        A connection to mythbackend intended for file transfers
     398        """
     399        sockno = None
     400        sockwrite = False
     401        datsock = None
     402        tsize = 2**15
     403        write = False
     404
     405        def __init__(self, file, mode):
     406                regex = re.compile('myth://((?P<group>.*)@)?(?P<host>[0-9\.]*)(:(?P<port>[0-9]*))?/(?P<file>.*)')
     407                self.db = MythDB(sys.argv[1:])
     408                self.comsock = MythTV()
     409                self.sockwrite = write
     410                if isinstance(file, Program):
     411                        match = regex.match(file.filename)
     412                        self.host = match.group('host')
     413                        self.port = int(match.group('port'))
     414                        self.filename = match.group('file')
     415                        self.sgroup = file.storagegroup
     416                        if self.port is None:
     417                                self.port = 6543
     418                elif isinstance(file, tuple):
     419                        if len(file) != 3:
     420                                log.Msg(CRITICAL, 'Incorrect FileTransfer() input size')
     421                                sys.exit(1)
     422                        else:
     423                                self.host = file[0]
     424                                self.port = int(self.db.getSetting('BackendServerPort',self.host))
     425                                self.filename = file[1]
     426                                self.sgroup = file[2]
     427                elif isinstance(file, str):
     428                        match = regex.match(file)
     429                        if match is None:
     430                                log.Msg(CRITICAL, 'Incorrect FileTransfer() input string: %s' % file)
     431                                sys.exit(1)
     432                        self.sgroup = match.group('group')
     433                        self.host = match.group('host')
     434                        self.port = int(match.group('port'))
     435                        self.filename = match.group('file')
     436                        if self.sgroup is None:
     437                                self.sgroup = ''
     438                        if self.port is None:
     439                                self.port = 6543
     440                else:
     441                        log.Msg(CRITICAL, 'Improper input to FileTransfer()')
     442                        sys.exit(1)
     443
     444                try:
     445                        self.datsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     446                        self.datsock.settimeout(10)
     447                        self.datsock.connect((self.host, self.port))
     448                        res = self.send('MYTH_PROTO_VERSION %s' % PROTO_VERSION).split(BACKEND_SEP)
     449                        if res[0] == 'REJECT':
     450                                log.Msg(CRITICAL, 'Backend has version %s and we speak version %s', res[1], PROTO_VERSION)
     451                                sys.exit(1)
     452                        res = self.send('ANN FileTransfer %s %d %d %d%s%s%s%s' % (socket.gethostname(), write, False, -1, BACKEND_SEP, self.filename, BACKEND_SEP, self.sgroup))
     453                        if res.split(BACKEND_SEP)[0] != 'OK':
     454                                log.Msg(CRITICAL, 'Unexpected answer to ANN command: %s', res)
     455                        else:
     456                                log.Msg(INFO, 'Successfully connected mythbackend at %s:%d', self.host, self.port)
     457                                sp = res.split(BACKEND_SEP)
     458                                self.sockno = int(sp[1])
     459                                self.pos = 0
     460                                self.size = (int(sp[2]) + (int(sp[3])<0))*2**32 + int(sp[3])
     461                               
     462                except socket.error, e:
     463                        log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.host, self.port)
     464                        sys.exit(1)
     465
     466        def __del__(self):
     467                if self.sockno:
     468                        self.comsock.backendCommand('QUERY_FILETRANSFER %d%sDONE' % (self.sockno, BACKEND_SEP))
     469                if self.datsock:
     470                        self.datsock.shutdown(1)
     471                        self.datsock.close()
     472
     473        def send(self,data):
     474                command = '%-8d%s' % (len(data), data)
     475                log.Msg(DEBUG, 'Sending command: %s', command)
     476                self.datsock.send(command)
     477                print 'sending command: %s' % command
     478                return self.recv()
     479
     480        def recv(self):
     481                data = self.datsock.recv(8)
     482                try:
     483                        length = int(data)
     484                except:
     485                        return ''
     486                data = []
     487                while length > 0:
     488                        chunk = self.datsock.recv(length)
     489                        length = length - len(chunk)
     490                        data.append(chunk)
     491                return ''.join(data)
     492
     493        def tell(self):
     494                """
     495                Return the current offset from the beginning of the file
     496                """
     497                return self.pos
     498
     499        def close(self):
     500                """
     501                Close the data transfer socket
     502                """
     503                self.__del__()
     504
     505        def rewind(self):
     506                """
     507                Seek back to the start of the file
     508                """
     509                self.seek(0)
     510
     511        def read(self, size):
     512                """
     513                Read a block of data, requests over 64KB will be buffered internally
     514                """
     515                if self.sockwrite:
     516                        print 'Error: attempting to read from a write-only socket'
     517                if size == 0:
     518                        return ''
     519                if size > self.size - self.pos:
     520                        size = self.size - self.pos
     521                csize = size
     522                rsize = 0
     523                if csize > self.tsize:
     524                        csize = self.tsize
     525                        rsize = size - csize
     526                       
     527                res = self.comsock.backendCommand('QUERY_FILETRANSFER %d%sREQUEST_BLOCK%s%d' % (self.sockno,BACKEND_SEP,BACKEND_SEP,csize))
     528                self.pos = self.pos + int(res)
     529#               if int(res) == csize:
     530#                       if csize < size:
     531#                               self.tsize += 8192
     532#               else:
     533#                       self.tsize -= 8192
     534#                       rsize = size - int(res)
     535#               print 'resizing buffer to %d' % self.tsize
     536
     537                return self.datsock.recv(int(res)) + self.read(rsize)
     538
     539        def write(self, data):
     540                """
     541                Write a block of data, requests over 64KB will be buffered internally
     542                """
     543                if not self.sockwrite:
     544                        print 'Error: attempting to write to a read-only socket'
     545                size = len(data)
     546                buff = ''
     547                print 'writing %d bytes to socket' % size
     548                if size == 0:
     549                        return
     550                if size > self.tsize:
     551                        size = self.tsize
     552                        buff = data[size:]
     553                        data = data[:size]
     554                print self.datsock.send(data)
     555                print self.comsock.backendCommand('QUERY_FILETRANSFER %d%sWRITE_BLOCK%s%d' % (self.sockno,BACKEND_SEP,BACKEND_SEP,size))
     556                self.write(buff)
     557                return
     558                       
     559
     560        def seek(self, offset, whence=0):
     561                """
     562                Seek 'offset' number of bytes
     563                   whence==0 - from start of file
     564                   whence==1 - from current position
     565                """
     566                if whence == 0:
     567                        if offset < 0:
     568                                offset = 0
     569                        if offset > self.size:
     570                                offset = self.size
     571                elif whence == 1:
     572                        if offset + self.pos < 0:
     573                                offset = -self.pos
     574                        if offset + self.pos > self.size:
     575                                offset = self.size - self.pos
     576                elif whence == 2:
     577                        if offset > 0:
     578                                offset = 0
     579                        if offset < -self.size:
     580                                offset = -self.size
     581                else:
     582                        log.Msg(CRITICAL, 'Whence can only be 0, 1, or 2')
     583
     584                curhigh,curlow = self.comsock.splitInt(self.pos)
     585                offhigh,offlow = self.comsock.splitInt(offset)
     586
     587                res = self.comsock.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)
     588                self.pos = (int(res[0]) + (int(res[1])<0))*2**32 + int(res[1])
     589
     590
     591class Frontend:
     592        isConnected = False
     593        socket = None
     594        host = None
     595        port = None
     596
     597        def __init__(self, host, port):
     598                self.host = host
     599                self.port = int(port)
     600                self.connect()
     601                self.disconnect()
     602
     603        def __del__(self):
     604                if self.isConnected:
     605                        self.disconnect()
     606
     607        def __repr__(self):
     608                return "%s@%d" % (self.host, self.port)
     609
     610        def __str__(self):
     611                return "%s@%d" % (self.host, self.port)
     612
     613        def connect(self):
     614                self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     615                self.socket.settimeout(10)
     616                self.socket.connect((self.host, self.port))
     617                if self.recv()[:28] != "MythFrontend Network Control":
     618                        self.socket.close()
     619                        self.socket = None
     620                        raise Exception('FrontendConnect','Connected socket does not belong to a mythfrontend')
     621                self.isConnected = True
     622
     623        def disconnect(self):
     624                self.send("exit")
     625                self.socket.close()
     626                self.socket = None
     627                self.isConnected = False
     628
     629        def send(self,command):
     630                if not self.isConnected:
     631                        self.connect()
     632                self.socket.send("%s\n" % command)
     633
     634        def recv(self,curstr=""):
     635                def subrecv(self,curstr=""):
     636                        try:
     637                                curstr += self.socket.recv(100)
     638                        except:
     639                                return None
     640                        if curstr[-4:] != '\r\n# ':
     641                                curstr = subrecv(self,curstr)
     642                        return curstr
     643                return subrecv(self)[:-4]
     644
     645        def sendJump(self,jumppoint):
     646                """
     647                Sends jumppoint to frontend
     648                """
     649                self.send("jump %s" % jumppoint)
     650                if self.recv() == 'OK':
     651                        return 0
     652                else:
     653                        return 1
     654
     655        def getJump(self):
     656                """
     657                Returns a tuple containing available jumppoints
     658                """
     659                self.send("help jump")
     660                res = self.recv().split('\r\n')[3:-1]
     661                points = []
     662                for point in res:
     663                        spoint = point.split(' - ')
     664                        points.append((spoint[0].rstrip(),spoint[1]))
     665                return tuple(points)
     666
     667        def sendKey(self,key):
     668                """
     669                Sends keycode to connected frontend
     670                """
     671                self.send("key %s" % key)
     672                if self.recv() == 'OK':
     673                        return 0
     674                else:
     675                        return 1
     676
     677        def getKey(self):
     678                """
     679                Returns a tuple containing available special keys
     680                """
     681                self.send("help key")
     682                res = self.recv().split('\r\n')[4]
     683                keys = []
     684                for key in res.split(','):
     685                        keys.append(key.strip())
     686                return tuple(keys)
     687
     688        def sendQuery(self,query):
     689                """
     690                Returns query from connected frontend
     691                """
     692                self.send("query %s" % query)
     693                return self.recv()
     694
     695        def getQuery(self):
     696                """
     697                Returns a tuple containing available queries
     698                """
     699                self.send("help query")
     700                res = self.recv().split('\r\n')[:-1]
     701                queries = []
     702                tmpstr = ""
     703                for query in res:
     704                        tmpstr += query
     705                        squery = tmpstr.split(' - ')
     706                        if len(squery) == 2:
     707                                tmpstr = ""
     708                                queries.append((squery[0].rstrip().lstrip('query '),squery[1]))
     709                return tuple(queries)
     710
     711        def sendPlay(self,play):
     712                """
     713                Send playback command to connected frontend
     714                """
     715                self.send("play %s" % play)
     716                if self.recv() == 'OK':
     717                        return 0
     718                else:
     719                        return 1
     720
     721        def getPlay(self):
     722                """
     723                Returns a tuple containing available playback commands
     724                """
     725                self.send("help play")
     726                res = self.recv().split('\r\n')[:-1]
     727                plays = []
     728                tmpstr = ""
     729                for play in res:
     730                        tmpstr += play
     731                        splay = tmpstr.split(' - ')
     732                        if len(splay) == 2:
     733                                tmpstr = ""
     734                                plays.append((splay[0].rstrip().lstrip('play '),splay[1]))
     735                return tuple(plays)
     736                       
     737               
     738
    223739class Recorder:
    224740        """
    225741        Represents a MythTV capture card.
     
    265781                self.callsign = data[6] #chansign
    266782                self.channame = data[7]
    267783                self.filename = data[8] #pathname
    268                 self.fs_high = data[9]
    269                 self.fs_low = data[10]
     784                self.fs_high = int(data[9])
     785                self.fs_low = int(data[10])
    270786                self.starttime = datetime.fromtimestamp(int(data[11])) # startts
    271787                self.endtime = datetime.fromtimestamp(int(data[12])) #endts
    272788                self.duplicate = int(data[13])
     
    303819                self.video_props = data[44]
    304820                self.subtitle_type = data[45]
    305821                self.year = data[46]
     822               
     823                self.filesize = (self.fs_high + (self.fs_low<0))*2**32 + self.fs_low
    306824
     825        def toString(self):
     826                string =            self.title
     827                string += BACKEND_SEP + self.subtitle
     828                string += BACKEND_SEP + self.description
     829                string += BACKEND_SEP + self.category
     830                if self.chanid:
     831                        string += BACKEND_SEP + str(self.chanid)
     832                else:
     833                        string += BACKEND_SEP
     834                string += BACKEND_SEP + self.channum
     835                string += BACKEND_SEP + self.callsign
     836                string += BACKEND_SEP + self.channame
     837                string += BACKEND_SEP + self.filename
     838                string += BACKEND_SEP + str(self.fs_high)
     839                string += BACKEND_SEP + str(self.fs_low)
     840                string += BACKEND_SEP + str(int(mktime(self.starttime.timetuple())))
     841                string += BACKEND_SEP + str(int(mktime(self.endtime.timetuple())))
     842                string += BACKEND_SEP + str(self.duplicate)
     843                string += BACKEND_SEP + str(self.shareable)
     844                string += BACKEND_SEP + str(self.findid)
     845                string += BACKEND_SEP + self.hostname
     846                string += BACKEND_SEP + str(self.sourceid)
     847                string += BACKEND_SEP + str(self.cardid)
     848                string += BACKEND_SEP + str(self.inputid)
     849                string += BACKEND_SEP + str(self.recpriority)
     850                string += BACKEND_SEP + str(self.recstatus)
     851                string += BACKEND_SEP + str(self.recordid)
     852                string += BACKEND_SEP + self.rectype
     853                string += BACKEND_SEP + self.dupin
     854                string += BACKEND_SEP + self.dupmethod
     855                string += BACKEND_SEP + str(int(mktime(self.recstartts.timetuple())))
     856                string += BACKEND_SEP + str(int(mktime(self.recendts.timetuple())))
     857                string += BACKEND_SEP + str(self.repeat)
     858                string += BACKEND_SEP + self.programflags
     859                string += BACKEND_SEP + self.recgroup
     860                string += BACKEND_SEP + str(self.commfree)
     861                string += BACKEND_SEP + self.outputfilters
     862                string += BACKEND_SEP + self.seriesid
     863                string += BACKEND_SEP + self.programid
     864                string += BACKEND_SEP + self.lastmodified
     865                string += BACKEND_SEP + str(self.stars)
     866                string += BACKEND_SEP + self.airdate
     867                string += BACKEND_SEP + str(self.hasairdate)
     868                string += BACKEND_SEP + self.playgroup
     869                string += BACKEND_SEP + str(self.recpriority2)
     870                string += BACKEND_SEP + self.parentid
     871                string += BACKEND_SEP + self.storagegroup
     872                string += BACKEND_SEP + self.audio_props
     873                string += BACKEND_SEP + self.video_props
     874                string += BACKEND_SEP + self.subtitle_type
     875                string += BACKEND_SEP + self.year
     876
     877                return string
     878
    307879if __name__ == '__main__':
    308880        banner = '\'m\' is a MythTV instance.'
    309881        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: