Ticket #6678: pyth.update7.patch

File pyth.update7.patch, 18.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

     
    160160                else:
    161161                        return None
    162162
     163        def setSetting(self, value, data, hostname=None):
     164                """
     165                Sets the value for the given MythTV setting.
     166                """
     167                log.Msg(DEBUG, 'Setting %s for host %s to %s', value, hostname, data)
     168                c = self.db.cursor()
     169                ws = None
     170                ss = None
     171
     172                if hostname is None:
     173                        ws = "WHERE value LIKE ('%s') AND hostname IS NULL" % (value)
     174                        ss = "(value,data) VALUES ('%s','%s')" % (value, data)
     175                else:
     176                        ws = "WHERE value LIKE ('%s') AND hostname LIKE ('%s%%')" % (value, hostname)
     177                        ss = "(value,data,hostname) VALUES ('%s','%s','%s')" % (value, data, hostname)
     178
     179                if c.execute("""UPDATE settings SET data %s LIMIT 1""" % ws) == 0:
     180                        c.execute("""INSERT INTO settings %s""" % ss)
     181                c.close()
     182
     183        def getCast(self, chanid, starttime, roles=None):
     184                """
     185                Returns cast members for a recording
     186                A string for 'roles' will return a tuple of members for that role
     187                A tuple of strings will return a touple containing all listed roles
     188                No 'roles' will return a dictionary of tuples
     189                """
     190                if roles is None:
     191                        c = self.db.cursor()
     192                        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))
     193                        if length == 0:
     194                                return ()
     195                        crole = None
     196                        clist = []
     197                        dict = {}
     198                        for name,role in c.fetchall():
     199                                if crole is None:
     200                                        crole = role
     201                                if crole == role:
     202                                        clist.append(name)
     203                                else:
     204                                        dict[crole] = tuple(clist)
     205                                        clist = []
     206                                        clist.append(name)
     207                                        crole = role
     208                        dict[crole] = tuple(clist)
     209                        c.close()
     210                        return dict
     211                elif isinstance(roles,str):
     212                        c = self.db.cursor()
     213                        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))
     214                        if length == 0:
     215                                return ()
     216                        names = []
     217                        for name in c.fetchall():
     218                                names.append(name[0])
     219                        return tuple(names)
     220                elif isinstance(roles,tuple):
     221                        c = self.db.cursor()
     222                        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))
     223                        if length == 0:
     224                                return ()
     225                        names = []
     226                        for name in c.fetchall():
     227                                names.append(name[0])
     228                        return tuple(names)
     229
    163230        def cursor(self):
    164231                return self.db.cursor()
    165232
     233class Job:
     234        jobid = None
     235        chanid = None
     236        starttime = None
     237        host = None
     238        mythdb = None
     239        def __init__(self, *inp):
     240                if len(inp) == 1:
     241                        self.jobid = inp[0]
     242                        self.getProgram()
     243                elif len(inp) == 2:
     244                        self.chanid = inp[0]
     245                        self.starttime = inp[1]
     246                        self.getJobID()
     247                else:
     248                        print("improper input length")
     249                        return None
     250                self.getHost()
     251
     252        def getProgram(self):
     253                if self.mythdb is None:
     254                        self.mythdb = MythDB()
     255                c = self.mythdb.cursor()
     256                c.execute("SELECT chanid,starttime FROM jobqueue WHERE id=%d" % self.jobid)
     257                self.chanid, self.starttime = c.fetchone()
     258                c.close()
     259
     260        def getJobID(self):
     261                if self.mythdb is None:
     262                        self.mythdb = MythDB()
     263                if self.jobid is None:
     264                        c = self.mythdb.cursor()
     265                        c.execute("SELECT id FROM jobqueue WHERE chanid=%d AND starttime=%d" % (self.chanid, self.starttime))
     266                        self.jobid = c.fetchone()[0]
     267                        c.close()
     268                return self.jobid
     269
     270        def getHost(self):
     271                if self.mythdb is None:
     272                        self.mythdb = MythDB()
     273                if self.host is None:
     274                        c = self.mythdb.cursor()
     275                        c.execute("SELECT hostname FROM jobqueue WHERE id=%d" % self.jobid)
     276                        self.host = c.fetchone()[0]
     277                        c.close()
     278                return self.host
     279
     280        def setComment(self,comment):
     281                if self.mythdb is None:
     282                        self.mythdb = MythDB()
     283                c = self.mythdb.cursor()
     284                c.execute("UPDATE jobqueue SET comment='%s' WHERE id=%d" % (comment,self.jobid))
     285                c.close()
     286
     287        def setStatus(self,status):
     288                if self.mythdb is None:
     289                        self.mythdb = MythDB()
     290                c = self.mythdb.cursor()
     291                c.execute("UPDATE jobqueue SET status=%d WHERE id=%d" % (status,self.jobid))
     292                c.close()
     293
    166294if __name__ == '__main__':
    167295        banner = "'mdb' is a MythDB instance."
    168296        try: