Ticket #6885: pyth.updates8.patch
File pyth.updates8.patch, 31.1 KB (added by , 15 years ago) |
---|
-
MythTV/MythVideo.py
9 9 10 10 log = MythLog(CRITICAL, '#%(levelname)s - %(message)s', 'MythVideo') 11 11 12 MVSCHEMA_VERSION = 1028 13 12 14 class MythVideo: 13 15 """ 14 16 Provides convinience methods to access the MythTV MythVideo database. … … 19 21 """ 20 22 self.db = MythDB() 21 23 24 # check schema version 25 sver = int(self.db.getSetting('mythvideo.DBSchemaVer')) 26 if MVSCHEMA_VERSION != sver: 27 log.Msg(CRITICAL, 'DB speaks schema version %d, but we speak version %d', 28 sver, MVSCHEMA_VERSION) 29 raise MythError('Mismatched schema version') 30 22 31 def rtnVideoStorageGroup(self, host=None): 23 32 '''Get the storage group 'Videos' directory for the suppied host name or default to the localhost. 24 33 return None if no Videos Storage Group found … … 27 36 if not host: # If a hostname was not supplied then use the local host name 28 37 host = gethostname() 29 38 30 # Get storagegroup table field names 31 table_names = self.getTableFieldNames(u'storagegroup') 39 ret = [] 40 for i in self.db.getStorageGroup('Videos',host): 41 ret.append(i['dirname']) 32 42 33 cur = self.db.cursor() 34 # Check is there are storage groups for the supplied host or default to the local host 35 try: 36 cur.execute(u"select * from storagegroup") 37 except MySQLdb.Error, e: 38 log.Msg(INFO, u"! Error: Reading storagegroup MythTV table: %d: %s\n" % (e.args[0], e.args[1])) 43 if ret: 44 return ret 45 else: 39 46 return None 40 47 41 videos_dir = []42 while True:43 data_id = cur.fetchone()44 if not data_id:45 break46 record = {}47 i = 048 for elem in data_id:49 if table_names[i] == 'groupname' or table_names[i] == 'hostname' or table_names[i] == 'dirname':50 record[table_names[i]] = elem51 i+=152 if record['hostname'].lower() == host.lower() and record['groupname'] == u'Videos':53 # Add a slash if mussing to any storage group dirname54 if record['dirname'][-1:] == '/':55 videos_dir.append(record['dirname'])56 else:57 videos_dir.append(record['dirname']+u'/')58 continue59 cur.close()60 61 if not len(videos_dir):62 return None63 64 return videos_dir65 # end getStorageGroups66 67 48 def pruneMetadata(self): 68 49 """ 69 50 Removes metadata from the database for files that no longer exist. … … 115 96 This function should have been called getCategoryId 116 97 """ 117 98 c = self.db.cursor() 118 c.execute("SELECT intid FROM videocategory WHERE lower(category) = %s", (genre_name,)) 99 c.execute("""SELECT intid FROM videocategory 100 WHERE lower(category) = %s""", (genre_name,)) 119 101 row = c.fetchone() 120 102 c.close() 121 103 … … 124 106 125 107 # Insert a new genre. 126 108 c = self.db.cursor() 127 c.execute("INSERT INTO videocategory(category) VALUES (%s)", (genre_name.capitalize(),)) 109 c.execute("""INSERT INTO videocategory(category) 110 VALUES (%s)""", (genre_name.capitalize(),)) 128 111 newid = c.lastrowid 129 112 c.close() 130 113 -
MythTV/MythTV.py
53 53 """ 54 54 A connection to a MythTV backend. 55 55 """ 56 def __init__(self, conn_type='Monitor'): 56 57 locked_tuners = [] 58 59 def __init__(self, backend='', conn_type='Monitor'): 57 60 self.db = MythDB(sys.argv[1:]) 58 self.master_host = self.db.getSetting('MasterServerIP')59 self.master_port = int(self.db.getSetting('MasterServerPort'))60 61 61 if not self.master_host: 62 if len(backend) == 0: # use master backend 63 self.host = self.db.getSetting('MasterServerIP') 64 self.port = self.db.getSetting('MasterServerPort') 65 elif re.match('(?:\d{1,3}\.){3}\d{1,3}',backend): # given an ip address 66 self.host = backend 67 c = self.db.cursor() 68 c.execute("""SELECT hostname FROM settings WHERE 69 value='BackendServerIP' AND 70 data=%s""", self.host) 71 backend = c.fetchone()[0] 72 self.port = self.db.getSetting('BackendServerPort',backend) 73 c.close() 74 else: # assume given a hostname 75 self.host = self.db.getSetting('BackendServerIP',backend) 76 if not self.host: # try a truncated hostname 77 backend = backend.split('.')[0] 78 self.host = self.db.getSetting('BackendServerIP',backend) 79 self.port = self.db.getSetting('BackendServerPort',backend) 80 81 if not self.host: 62 82 log.Msg(CRITICAL, 'Unable to find MasterServerIP in database') 63 83 sys.exit(1) 64 if not self. master_port:84 if not self.port: 65 85 log.Msg(CRITICAL, 'Unable to find MasterServerPort in database') 66 86 sys.exit(1) 87 self.port = int(self.port) 67 88 68 89 try: 69 90 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 70 91 self.socket.settimeout(10) 71 self.socket.connect((self. master_host, self.master_port))92 self.socket.connect((self.host, self.port)) 72 93 res = self.backendCommand('MYTH_PROTO_VERSION %s' % PROTO_VERSION).split(BACKEND_SEP) 73 94 if res[0] == 'REJECT': 74 95 log.Msg(CRITICAL, 'Backend has version %s and we speak version %s', res[1], PROTO_VERSION) … … 77 98 if res != 'OK': 78 99 log.Msg(CRITICAL, 'Unexpected answer to ANN command: %s', res) 79 100 else: 80 log.Msg(INFO, 'Successfully connected mythbackend at %s:%d', self. master_host, self.master_port)101 log.Msg(INFO, 'Successfully connected mythbackend at %s:%d', self.host, self.port) 81 102 except socket.error, e: 82 log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self. master_host, self.master_port)103 log.Msg(CRITICAL, 'Couldn\'t connect to %s:%d (is the backend running)', self.host, self.port) 83 104 sys.exit(1) 84 105 85 106 def __del__(self): 107 self.freeTuner() 86 108 self.backendCommand('DONE') 87 109 self.socket.shutdown(1) 88 110 self.socket.close() 89 111 112 def close(self): 113 self.__del__() 114 90 115 def backendCommand(self, data): 91 116 """ 92 117 Sends a formatted command via a socket to the mythbackend. … … 203 228 else: 204 229 return None 205 230 231 def lockTuner(self,id=None): 232 """ 233 Request a tuner be locked from use, optionally specifying which tuner 234 Returns a tuple of ID, video device node, audio device node, vbi device node 235 Returns an ID of -2 if tuner is locked, or -1 if no tuner could be found 236 """ 237 local = True 238 cmd = 'LOCK_TUNER' 239 if id is not None: 240 cmd += ' %d' % id 241 res = self.getRecorderDetails(id).hostname 242 if res != socket.gethostname(): 243 local = False 244 245 res = '' 246 if local: 247 res = self.backendCommand(cmd).split(BACKEND_SEP) 248 else: 249 myth = MythTV(res) 250 res = myth.backendCommand(cmd).split(BACKEND_SEP) 251 myth.close() 252 res[0] = int(res[0]) 253 if res[0] > 0: 254 self.locked_tuners.append(res[0]) 255 return tuple(res) 256 257 258 def freeTuner(self,id=None): 259 """ 260 Frees a requested tuner ID 261 If no ID given, free all tuners listed as used by this class instance 262 """ 263 def free(self,id): 264 res = self.getRecorderDetails(id).hostname 265 if res == socket.gethostname(): 266 self.backendCommand('FREE_TUNER %d' % id) 267 else: 268 myth = MythTV(res) 269 myth.backendCommand('FREE_TUNER %d' % id) 270 myth.close() 271 272 if id is None: 273 for i in range(len(self.locked_tuners)): 274 free(self,self.locked_tuners.pop()) 275 else: 276 try: 277 self.locked_tuners.remove(id) 278 except: 279 pass 280 free(self,id) 281 206 282 def getCurrentRecording(self, recorder): 207 283 """ 208 284 Returns a Program object for the current recorders recording. 209 285 """ 210 res = self.backendCommand('QUERY_RECORDER %s[]:[]GET_CURRENT_RECORDING' % recorder)286 res = self.backendCommand('QUERY_RECORDER '+BACKEND_SEP.join([recorder,'GET_CURRENT_RECORDING'])) 211 287 return Program(res.split(BACKEND_SEP)) 212 288 213 289 def isRecording(self, recorder): 214 290 """ 215 291 Returns a boolean as to whether the given recorder is recording. 216 292 """ 217 res = self.backendCommand('QUERY_RECORDER %s[]:[]IS_RECORDING' % recorder)293 res = self.backendCommand('QUERY_RECORDER '+BACKEND_SEP.join([recorder,'IS_RECORDING'])) 218 294 if res == '1': 219 295 return True 220 296 else: … … 224 300 """ 225 301 Returns a boolean as to whether the given host is an active backend 226 302 """ 227 res = self.backendCommand( 'QUERY_IS_ACTIVE_BACKEND[]:[]%s' % hostname)303 res = self.backendCommand(BACKEND_SEP.join(['QUERY_IS_ACTIVE_BACKEND',hostname])) 228 304 if res == 'TRUE': 229 305 return True 230 306 else: … … 245 321 Returns a list of all Program objects which have already recorded 246 322 """ 247 323 programs = [] 248 res = self.backendCommand('QUERY_RECORDINGS Play').split( '[]:[]')324 res = self.backendCommand('QUERY_RECORDINGS Play').split(BACKEND_SEP) 249 325 num_progs = int(res.pop(0)) 250 326 log.Msg(DEBUG, '%s total recordings', num_progs) 251 327 for i in range(num_progs): … … 253 329 + PROGRAM_FIELDS])) 254 330 return tuple(programs) 255 331 332 def getExpiring(self): 333 """ 334 Returns a tuple of all Program objects nearing expiration 335 """ 336 programs = [] 337 res = self.backendCommand('QUERY_GETEXPIRING').split(BACKEND_SEP) 338 num_progs = int(res.pop(0)) 339 for i in range(num_progs): 340 programs.append(Program(res[i * PROGRAM_FIELDS:(i * PROGRAM_FIELDS) 341 + PROGRAM_FIELDS])) 342 return tuple(programs) 343 256 344 def getCheckfile(self,program): 257 345 """ 258 346 Returns location of recording in file system 259 347 """ 260 res = self.backendCommand( 'QUERY_CHECKFILE[]:[]1[]:[]%s' % program.toString()).split(BACKEND_SEP)348 res = self.backendCommand(BACKEND_SEP.join(['QUERY_CHECKFILE','1',program.toString()])).split(BACKEND_SEP) 261 349 if res[0] == 0: 262 350 return None 263 351 else: … … 271 359 command = 'DELETE_RECORDING' 272 360 if force: 273 361 command = 'FORCE_DELETE_RECORDING' 274 return self.backendCommand( '%s%s%s' % (command,BACKEND_SEP,program.toString()))362 return self.backendCommand(BACKEND_SEP.join([command,program.toString()])) 275 363 276 364 def forgetRecording(self,program): 277 365 """ 278 366 Forgets old recording and allows it to be re-recorded 279 367 """ 280 self.backendCommand( 'FORGET_RECORDING%s%s' % (BACKEND_SEP,program.toString()))368 self.backendCommand(BACKEND_SEP.join(['FORGET_RECORDING',program.toString()])) 281 369 282 370 def deleteFile(self,file,sgroup): 283 371 """ 284 372 Deletes a file from specified storage group on the connected backend 285 373 Takes a relative file path from the root of the storage group, and returns 1 on success 286 374 """ 287 return self.backendCommand( 'DELETE_FILE%s%s%s%s' % (BACKEND_SEP,file,BACKEND_SEP,sgroup))375 return self.backendCommand(BACKEND_SEP.join(['DELETE_FILE',file,sgroup])) 288 376 289 377 def getFreeSpace(self,all=False): 290 378 """ … … 329 417 res = self.backendCommand('QUERY_LOAD').split(BACKEND_SEP) 330 418 return (float(res[0]),float(res[1]),float(res[2])) 331 419 420 def getUptime(self): 421 """ 422 Returns machine uptime in seconds 423 """ 424 return self.backendCommand('QUERY_UPTIME') 425 332 426 def getSGList(self,host,sg,path): 333 427 """ 334 428 Returns a tuple of directories and files 335 429 """ 336 res = self.backendCommand( 'QUERY_SG_GETFILELIST%s%s%s%s%s%s' % (BACKEND_SEP,host,BACKEND_SEP,sg,BACKEND_SEP,path)).split(BACKEND_SEP)430 res = self.backendCommand(BACKEND_SEP.join(['QUERY_SG_GETFILELIST',host,sg,path])).split(BACKEND_SEP) 337 431 if res[0] == 'EMPTY LIST': 338 432 return -1 339 433 if res[0] == 'SLAVE UNREACHABLE: ': … … 352 446 """ 353 447 Returns a tuple of last modification time and file size 354 448 """ 355 res = self.backendCommand( 'QUERY_SG_FILEQUERY%s%s%s%s%s%s' % (BACKEND_SEP,host,BACKEND_SEP,sg,BACKEND_SEP,path)).split(BACKEND_SEP)449 res = self.backendCommand(BACKEND_SEP.join(['QUERY_SG_FILEQUERY',host,sg,path])).split(BACKEND_SEP) 356 450 if res[0] == 'EMPTY LIST': 357 451 return -1 358 452 if res[0] == 'SLAVE UNREACHABLE: ': … … 382 476 port = self.db.getSetting("NetworkControlPort",host) 383 477 return Frontend(host,port) 384 478 479 def getLastGuideData(self): 480 """ 481 Returns the last dat for which guide data is available 482 On error, 0000-00-00 00:00 is returned 483 """ 484 return self.backendCommand('QUERY_GUIDEDATATHROUGH') 485 385 486 def joinInt(self,high,low): 386 487 """ 387 488 Returns a single long from a pair of signed integers … … 458 559 if res[0] == 'REJECT': 459 560 log.Msg(CRITICAL, 'Backend has version %s and we speak version %s', res[1], PROTO_VERSION) 460 561 sys.exit(1) 461 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))562 res = self.send('ANN FileTransfer %s %d %d %s' % (socket.gethostbyname(),write, False, BACKEND_SEP.join(['-1',self.filename,self.sgroup]))) 462 563 if res.split(BACKEND_SEP)[0] != 'OK': 463 564 log.Msg(CRITICAL, 'Unexpected answer to ANN command: %s', res) 464 565 else: … … 474 575 475 576 def __del__(self): 476 577 if self.sockno: 477 self.comsock.backendCommand('QUERY_FILETRANSFER %d%sDONE' % (self.sockno, BACKEND_SEP))578 self.comsock.backendCommand('QUERY_FILETRANSFER '+BACKEND_SEP.join([str(self.sockno), 'JOIN'])) 478 579 if self.datsock: 479 580 self.datsock.shutdown(1) 480 581 self.datsock.close() … … 533 634 csize = self.tsize 534 635 rsize = size - csize 535 636 536 res = self.comsock.backendCommand('QUERY_FILETRANSFER %d%sREQUEST_BLOCK%s%d' % (self.sockno,BACKEND_SEP,BACKEND_SEP,csize))637 res = self.comsock.backendCommand('QUERY_FILETRANSFER '+BACKEND_SEP.join([str(self.sockno),'REQUEST_BLOCK',str(csize)])) 537 638 self.pos += int(res) 538 639 # if int(res) == csize: 539 640 # if csize < size: … … 561 662 buff = data[size:] 562 663 data = data[:size] 563 664 self.pos += int(self.datsock.send(data)) 564 self.comsock.backendCommand('QUERY_FILETRANSFER %d%sWRITE_BLOCK%s%d' % (self.sockno,BACKEND_SEP,BACKEND_SEP,size))665 self.comsock.backendCommand('QUERY_FILETRANSFER '+BACKEND_SEP.join([str(self.sockno),'WRITE_BLOCK',str(size)])) 565 666 self.write(buff) 566 667 return 567 668 … … 593 694 curhigh,curlow = self.comsock.splitInt(self.pos) 594 695 offhigh,offlow = self.comsock.splitInt(offset) 595 696 596 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)697 res = self.comsock.backendCommand('QUERY_FILETRANSFER '+BACKEND_SEP.join([str(self.sockno),'SEEK',str(offhigh),str(offlow),str(whence),str(curhigh),str(curlow)])).split(BACKEND_SEP) 597 698 self.pos = (int(res[0]) + (int(res[1])<0))*2**32 + int(res[1]) 598 699 599 700 … … 885 986 886 987 return string 887 988 989 def setField(self,field,value): 990 if field not in ['basename','hostname','storagegroup']: 991 raise MythError('Invalid field name') 992 db = MythDB() 993 c = db.cursor() 994 c.execute("""UPDATE recorded SET %s = %%s 995 WHERE chanid=%%s and starttime=%%s""" % field, 996 (value,self.chanid,self.starttime)) 997 c.close() 998 999 def setBasename(self,name): 1000 """ 1001 Change the file basename pointed to by the recording 1002 """ 1003 self.setField('basename',name) 1004 1005 def setHostname(self,name): 1006 """ 1007 Change the hostname of the machine which holds the recording 1008 """ 1009 self.setField('hostname',name) 1010 1011 def setSG(self,name): 1012 """ 1013 Change the storagegroup which holds the recording 1014 """ 1015 self.setField('storagegroup',name) 1016 888 1017 if __name__ == '__main__': 889 1018 banner = '\'m\' is a MythTV instance.' 890 1019 try: -
MythTV/MythDB.py
22 22 log.Msg(CRITICAL, "MySQLdb (python-mysqldb) is required but is not found.") 23 23 sys.exit(1) 24 24 25 SCHEMA_VERSION = 1244 26 25 27 class MythDB: 26 28 """ 27 29 A connection to the mythtv database. … … 40 42 # Try to read the config.xml file used by MythTV. 41 43 config_files = [ os.path.expanduser('~/.mythtv/config.xml') ] 42 44 if 'MYTHCONFDIR' in os.environ: 43 config_ locations.append('%s/config.xml' % os.environ['MYTHCONFDIR'])45 config_files.append('%s/config.xml' % os.environ['MYTHCONFDIR']) 44 46 45 47 found_config = False 46 48 for config_file in config_files: … … 96 98 raise MythError('Unable to find MythTV configuration file') 97 99 98 100 try: 99 self.db = MySQLdb.connect(user=dbconn['user'], host=dbconn['host'], passwd=dbconn['pass'], db=dbconn['name'], use_unicode=True, charset='utf8') 100 log.Msg(INFO, 'DB Connection info (host:%s, name:%s, user:%s, pass:%s)', dbconn['host'], dbconn['name'], dbconn['user'], dbconn['pass']) 101 self.db = MySQLdb.connect(user=dbconn['user'], host=dbconn['host'], 102 passwd=dbconn['pass'], db=dbconn['name'], use_unicode=True, 103 charset='utf8') 104 log.Msg(INFO, 'DB Connection info (host:%s, name:%s, user:%s, pass:%s)', 105 dbconn['host'], dbconn['name'], dbconn['user'], dbconn['pass']) 101 106 except: 102 raise MythError('Connection failed for \'%s\'@\'%s\' to database %s using password %s' % (dbconn['user'], dbconn['host'], dbconn['name'], dbconn['pass'])) 107 raise MythError('Connection failed for \'%s\'@\'%s\' to database %s using password %s' % 108 (dbconn['user'], dbconn['host'], dbconn['name'], dbconn['pass'])) 103 109 110 # check schema version 111 sver = int(self.getSetting('DBSchemaVer')) 112 if SCHEMA_VERSION != sver: 113 log.Msg(CRITICAL, 'DB speaks schema version %d, but we speak version %d', 114 sver, SCHEMA_VERSION) 115 raise MythError('Mismatched schema version') 116 117 def __del__(self): 118 self.db.close() 119 120 104 121 def getAllSettings(self, hostname=None): 105 122 """ 106 123 Returns values for all settings. … … 119 136 c.execute(""" 120 137 SELECT value, data 121 138 FROM settings 122 WHERE hostname LIKE( '%s%%')""" %139 WHERE hostname LIKE(%s)""", 123 140 (hostname)) 124 141 rows = c.fetchall() 125 142 c.close() … … 142 159 c.execute(""" 143 160 SELECT data 144 161 FROM settings 145 WHERE value LIKE( '%s') AND hostname IS NULL LIMIT 1""" %162 WHERE value LIKE(%s) AND hostname IS NULL LIMIT 1""", 146 163 (value)) 147 164 else: 165 hostname += '%' 148 166 c.execute(""" 149 167 SELECT data 150 168 FROM settings 151 WHERE value LIKE( '%s') AND hostname LIKE('%s%%') LIMIT 1""" %169 WHERE value LIKE(%s) AND hostname LIKE(%s) LIMIT 1""", 152 170 (value, hostname)) 153 171 row = c.fetchone() 154 172 c.close() … … 166 184 c = self.db.cursor() 167 185 ws = None 168 186 ss = None 187 t = None 169 188 170 189 if hostname is None: 171 ws = "WHERE value LIKE ('%s') AND hostname IS NULL" % (value) 172 ss = "(value,data) VALUES ('%s','%s')" % (value, data) 190 ws = "WHERE value LIKE (%s) AND hostname IS NULL" 191 ss = "(data,value) VALUES (%s,%s)" 192 t = (data,value) 173 193 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) 194 hostname += '%' 195 ws = "WHERE value LIKE (%s) AND hostname LIKE (%s)" 196 ss = "(data,value,hostname) VALUES (%s,%s,%s)" 197 t = (data,value,hostname) 176 198 177 if c.execute("""UPDATE settings SET data %s LIMIT 1""" % ws) == 0:178 c.execute("""INSERT INTO settings %s""" % ss )199 if c.execute("""UPDATE settings SET data=%%s %s LIMIT 1""" % ws, t) == 0: 200 c.execute("""INSERT INTO settings %s""" % ss, t) 179 201 c.close() 180 202 181 203 def getCast(self, chanid, starttime, roles=None): … … 185 207 A tuple of strings will return a touple containing all listed roles 186 208 No 'roles' will return a dictionary of tuples 187 209 """ 210 c = self.db.cursor() 211 if c.execute("""SELECT * FROM recorded WHERE 212 chanid=%s AND starttime=%s""", 213 (chanid,starttim)) == 0: 214 raise MythError('recording does not exist') 188 215 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)) 216 length = c.execute("""SELECT name,role 217 FROM people,credits 218 WHERE people.person=credits.person 219 AND chanid=%s AND starttime=%s 220 ORDER BY role""", (chanid, starttime)) 191 221 if length == 0: 192 222 return () 193 223 crole = None … … 207 237 c.close() 208 238 return dict 209 239 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)) 240 length = c.execute("""SELECT name 241 FROM people,credits 242 WHERE people.person=credits.person 243 AND chanid=%s AND starttime=%s 244 AND role=%s""", (chanid, starttime, roles)) 212 245 if length == 0: 213 246 return () 214 247 names = [] … … 216 249 names.append(name[0]) 217 250 return tuple(names) 218 251 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)) 252 length = c.execute("""SELECT name 253 FROM people,credits 254 WHERE people.person=credits.person 255 AND chanid=%d AND starttime=%d 256 AND role IN %s""" % (chanid, starttime, roles)) 221 257 if length == 0: 222 258 return () 223 259 names = [] 224 260 for name in c.fetchall(): 225 261 names.append(name[0]) 226 262 return tuple(names) 263 else: 264 raise MythError('invalid input format') 227 265 266 def getStorageGroup(self, group=None, host=None): 267 """ 268 Returns tuple of dictionaries containing storage group directories 269 with the fields 'id', 'group', 'host', and 'dirname' 270 Takes an optional group and host for filtering 271 """ 272 c = self.db.cursor() 273 q1 = 'SELECT * FROM storagegroup' 274 q2 = 'ORDER BY id' 275 if host: 276 host += '%' 277 if group and host: 278 c.execute("""%s 279 WHERE groupname=%%s 280 AND hostname like %%s 281 %s""" % (q1,q2), (group, host)) 282 elif group: 283 c.execute("""%s 284 WHERE groupname=%%s 285 %s""" % (q1,q2), (group,)) 286 elif host: 287 c.execute("""%s 288 WHERE hostname like %%s 289 %s""" % (q1,q2), (host,)) 290 else: 291 c.execute("""%s %s""" % (q1,q2)) 292 ret = [] 293 for i in c.fetchall(): 294 if not i[3][-1] == '/': 295 i[3] += '/' 296 ret.append({'id':i[0], 'group':i[1], 'host':i[2], 'dirname':i[3]}) 297 return tuple(ret) 298 299 def getChannels(self): 300 """ 301 Returns a tuple of channel object defined in the database 302 """ 303 channels = [] 304 c = self.db.cursor() 305 c.execute("""SELECT * FROM channel""") 306 for row in c.fetchall(): 307 channels.append(Channel(row)) 308 c.close() 309 return tuple(channels) 310 311 def getChannel(self,chanid): 312 """ 313 Returns a single channel object for the given chanid 314 """ 315 c = self.db.cursor() 316 if c.execute("""SELECT * FROM channel 317 WHERE chanid=%s""", (chanid,)): 318 return Channel(c.fetchone()) 319 else: 320 return None 321 322 def getGuideData(self, chanid, date): 323 """ 324 Returns tuple of guide data for one channel on one date 325 """ 326 guide = [] 327 c = self.db.cursor() 328 c.execute("""SELECT * FROM program 329 WHERE chanid=%s AND DATE(starttime)=%s""", 330 (chanid,date)) 331 for show in c.fetchall(): 332 guide.append(Guide(show)) 333 c.close() 334 return tuple(guide) 335 228 336 def cursor(self): 229 337 return self.db.cursor() 230 338 231 339 class Job: 340 """ 341 Class for managing tasks within mythtv's jobqueue 342 Job(jobid) or Job(chanid,starttime) 343 -- manage existing task 344 Job(dictionary) 345 -- create new task with values in the dictionary 346 """ 347 348 NONE = 0x0000 349 SYSTEMJOB = 0x00ff 350 TRANSCODE = 0x0001 351 COMMFLAG = 0x0002 352 USERJOB = 0xff00 353 USERJOB1 = 0x0100 354 USERJOB2 = 0x0200 355 USERJOB3 = 0x0300 356 USERJOB4 = 0x0400 357 358 RUN = 0x0000 359 PAUSE = 0x0001 360 RESUME = 0x0002 361 STOP = 0x0004 362 RESTART = 0x0008 363 364 NO_FLAGS = 0x0000 365 USE_CUTLIST = 0x0001 366 LIVE_REC = 0x0002 367 EXTERNAL = 0x0004 368 369 UNKNOWN = 0x0000 370 QUEUED = 0x0001 371 PENDING = 0x0002 372 STARTING = 0x0003 373 RUNNING = 0x0004 374 STOPPING = 0x0005 375 PAUSED = 0x0006 376 RETRY = 0x0007 377 ERRORING = 0x0008 378 ABORTING = 0x0008 379 DONE = 0x0100 380 FINISHED = 0x0110 381 ABORTED = 0x0120 382 ERRORED = 0x0130 383 CANCELLED = 0x0140 384 232 385 jobid = None 233 chanid = None234 starttime = None235 386 host = None 236 mythdb = None387 db = None 237 388 def __init__(self, *inp): 238 if len(inp) == 1: 389 if isinstance(inp[0],dict): 390 if self.insert(inp[0]) is None: 391 return none 392 elif len(inp) == 1: 239 393 self.jobid = inp[0] 240 394 self.getProgram() 241 395 elif len(inp) == 2: … … 243 397 self.starttime = inp[1] 244 398 self.getJobID() 245 399 else: 246 print("improper input length") 400 raise MythError('improper input length') 401 self.getInfo() 402 403 def processfields(self,inp): 404 """ 405 Sanitize lists of fields for SQL queries 406 """ 407 fields = ['id','chanid','starttime','inserttime','type','cmds', 408 'flags','status','statustime','hostname','args', 409 'comment','schedruntime'] 410 if isinstance(inp,list): 411 for i in range(len(inp)-1,-1,-1): 412 if inp[i] not in fields: 413 del inp[i] 414 elif isinstance(inp,dict): 415 for i in inp.keys(): 416 if i not in fields: 417 del inp[i] 418 return inp 419 420 def get(self,fields,where): 421 if self.db is None: 422 self.db = MythDB() 423 fields = self.processfields(fields) 424 where = self.processfields(where) 425 if (len(fields)==0) or (len(where)==0): 426 raise MythError('no valid database fields given') 427 428 f = ','.join(fields) 429 w = 'WHERE ' 430 data = [] 431 if isinstance(where,dict): 432 wf = [] 433 for k,v in where.items(): 434 wf.append('%s=%%s' % k) 435 data.append(v) 436 w += ' AND '.join(wf) 437 else: 438 raise MythError("'where' argument must be given as a dictionary") 439 440 c = self.db.cursor() 441 c.execute("""SELECT %s FROM jobqueue %s""" % (f,w), data) 442 443 res = c.fetchone() 444 if len(fields) == 1: 445 return res[0] 446 jdict = {} 447 for i in range(len(fields)): 448 jdict[fields[i]] = res[i] 449 450 return jdict 451 452 def set(self,data): 453 if self.db is None: 454 self.db = MythDB() 455 data = self.processfields(data) 456 if len(data)==0: 457 raise MythError('no valid database fields given') 458 459 ustr = ', '.join(['%s = %%s' % d for d in data]) 460 values = data.values() 461 values.append(self.jobid) 462 463 c = self.db.cursor() 464 c.execute("""UPDATE jobqueue SET %s WHERE id=%%s""" % ustr, values) 465 c.close() 466 467 def insert(self,data): 468 if self.db is None: 469 self.db = MythDB() 470 data = self.processfields(data) 471 if 'id' in data.keys(): 472 del data['id'] 473 if len(data)==0: 247 474 return None 248 self.getHost()249 475 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() 476 # require these three fields for any job creation 477 for field in ['chanid','starttime','type']: 478 if field not in data.keys(): 479 raise MythError("missing fields in JOB creation") 480 481 if 'status' not in data.keys(): 482 data['status'] = 0x0001 # default to queued 483 if 'schedruntime' not in data.keys(): 484 data['schedruntime'] = 'NOW()' # default to now 485 data['inserttime'] = 'NOW()' # override to now 486 487 fields = ', '.join(data.keys()) 488 istr = ', '.join(['%s' for d in data]) 489 490 c = self.db.cursor() 491 c.execute("""INSERT INTO jobqueue(%s) VALUES(%s)""" % (fields,istr), data.values()) 492 self.jobid = c.lastrowid 256 493 c.close() 494 return self.jobid 257 495 496 def getProgram(self): 497 """ 498 Get chanid and starttime from existing job id 499 """ 500 res = self.get(['chanid','starttime'],{'id':self.jobid}) 501 self.chanid = res['chanid'] 502 self.starttime = res['starttime'] 503 258 504 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() 505 """ 506 Get job id from existing chanid and starttime 507 """ 508 self.jobid = self.get(['id'],{'chanid':self.chanid,'starttime':self.starttime}) 266 509 return self.jobid 267 510 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 511 def getInfo(self): 512 """ 513 Get remaining data fields from existing job id 514 """ 515 res = self.get(['inserttime','type','cmds','flags','hostname','args'],{'id':self.jobid}) 516 self.inserttime = res['inserttime'] 517 self.type = res['type'] 518 self.cmds = res['cmds'] 519 self.flags = res['flags'] 520 self.host = res['hostname'] 521 self.args = res['args'] 277 522 523 def getComment(self): 524 """Get comment field from database""" 525 return self.get(['comment'],{'id':self.jobid}) 526 278 527 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() 528 """Set comment field in database""" 529 self.set({'comment':comment}) 284 530 531 def getStatus(self): 532 """Get status field from database""" 533 return self.get(['status'],{'id':self.jobid}) 534 285 535 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() 536 """Set status field in database""" 537 self.set({'status':status}) 291 538 539 def getArgs(self): 540 """Get argument field from database""" 541 self.args = self.get(['args'],{'id':self.jobid}) 542 return self.args 543 544 def setArgs(self,args): 545 """Set argument field in database""" 546 self.set({'args':args}) 547 548 class Channel: 549 """ 550 Represents a single channel from the channel table 551 """ 552 def __str__(self): 553 return "%s (%s)" % (self.chanid, self.name) 554 555 def __repr__(self): 556 return "%s (%s)" % (self.chanid, self.name) 557 558 def __init__(self,data): 559 """ 560 Load data into object 561 """ 562 self.chanid = data[0] 563 self.channum = data[1] 564 self.freqid = data[2] 565 self.sourceid = data[3] 566 self.callsign = data[4] 567 self.name = data[5] 568 self.icon = data[6] 569 self.finetune = data[7] 570 self.videofilters = data[8] 571 self.xmltvid = data[9] 572 self.recpriority = data[10] 573 self.contrast = data[11] 574 self.brightness = data[12] 575 self.colour = data[13] 576 self.hue = data[14] 577 self.tvformat = data[15] 578 self.visible = data[16] 579 self.outputfiters = data[17] 580 self.useonairguide = data[18] 581 self.mplexid = data[19] 582 self.serviceid = data[20] 583 self.tmoffset = data[21] 584 self.atsc_major_chan = data[22] 585 self.atsc_minor_chan = data[23] 586 self.last_record = data[24] 587 self.default_authority = data[25] 588 self.commmethod = data[26] 589 590 class Guide: 591 """ 592 Represents a single program from the program guide 593 """ 594 def __str__(self): 595 return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S')) 596 597 def __repr__(self): 598 return "%s (%s)" % (self.title, self.starttime.strftime('%Y-%m-%d %H:%M:%S')) 599 600 def __init__(self,data): 601 """ 602 Load data into the object 603 """ 604 self.chanid = data[0] 605 self.starttime = data[1] 606 self.endtime = data[2] 607 self.title = data[3] 608 self.subtitle = data[4] 609 self.description = data[5] 610 self.category = data[6] 611 self.category_type = data[7] 612 self.airdate = data[8] 613 self.stars = data[9] 614 self.previouslyshown = data[10] 615 self.title_pronounce = data[11] 616 self.stereo = data[12] 617 self.subtitled = data[13] 618 self.hdtv = data[14] 619 self.closecaptioned = data[15] 620 self.partnumber = data[16] 621 self.parttotal = data[17] 622 self.seriesid = data[18] 623 self.originalairdate = data[19] 624 self.showtype = data[20] 625 self.colorcode = data[21] 626 self.syndicatedepisodenumber = data[22] 627 self.programid = data[23] 628 self.manualid = data[24] 629 self.generic = data[25] 630 self.listingsource = data[26] 631 self.first = data[27] 632 self.last = data[28] 633 self.audioprop = data[29] 634 self.subtitletypes = data[30] 635 self.videoprop = data[31] 636 292 637 if __name__ == '__main__': 293 638 banner = "'mdb' is a MythDB instance." 294 639 try: