"""
$Id: mythtv.py,v 1.108 2007/05/18 22:11:06 frooby Exp $
Pure python Myth TV library.
"""
from mythtvutil import getcwd
from singleton import getInstance
from xml.dom import minidom,Node
import codecs
import datetime
import httplib
import mysql
import mythtvstruct
import mythtvutil
import nmb
import os
import singleton
import socket
import sre, re
import string
import time
import xbmc
import traceback
import locale
def debug( str ):
mythtvutil.debug( mythtvutil.DEBUG_MYTH, str )
## Used so we can 'share' the ringbuf between xbox clients
## - If it's True WE created the ringbuf, else someelse did!
ourConnection = False
## Host to use in the mythconverg settings table
ourHost = xbmc.getIPAddress()
global chainid
global chainpos
SEP = "[]:[]"
CHANGE_CHANNEL_TRIES = 60 #Number of tries to wait before
#timeing out on changing channels
global chainCnt
global mythProtocolVersion
global supportedProtocol
supportedProtocol = 34 # 0.20 SVN
mythProtocolVersion = str(supportedProtocol)
recordSize = 20
socket.setdefaulttimeout(15)
def convToGUIEncoding( s ):
"""
Function to convert utf-8 byte strings (as retrieved from MySQL database
and Myth TV backend) to strings in XBMC GUI encoding.
"""
retStr = s
try:
if s and len( s ) > 0:
us = unicode( s, 'utf-8' )
langInfo = getInstance( mythtvutil.XBMCLangInfo )
(e,d,r,w) = codecs.lookup( langInfo.getSetting( \
'language.charsets.gui' ) )
(retStr,n) = e( us )
return retStr
except Exception, ex:
traceback.print_exc()
return s
def sql2MythTime( str ):
return \
str[0:4] + '-' + str[4:6] + '-' + str[6:8] + 'T' + \
str[8:10] + ':' + str[10:12] + ':' + str[12:14]
def stripSQLTablePrefix( row ):
for k,v in row.iteritems():
del row[k]
k = sre.sub('^([^\.]+\.)','', k)
row[k] = v
return row
def roundTime30( rawTime ):
if (rawT.tm_min < 30):
return time.struct_time((rawTime.tm_year, rawTime.tm_mon, rawTime.tm_mday, rawTime.tm_hour, 0, 0, rawTime.tm_wday, rawTime.tm_yday, rawTime.tm_isdst))
else:
return time.struct_time((rawTime.tm_year, rawTime.tm_mon, rawTime.tm_mday, rawTime.tm_hour, 30, 0, rawTime.tm_wday, rawTime.tm_yday, rawTime.tm_isdst))
def formatSize( sizeKB, gb=False ):
size = float(sizeKB)
if size > 1024*1024 and gb:
value = str("%.2f %s"% (size/(1024.0*1024.0),mythtvutil.getLocalizedString( 63 )))
elif size > 1024:
value = str("%.2f %s"% (size/(1024.0),mythtvutil.getLocalizedString( 62 )))
else:
value = str("%.2f %s"% (size,mythtvutil.getLocalizedString( 61 )))
return re.sub(r'(?<=\d)(?=(\d\d\d)+\.)', ',', value)
def reverseArray( theArray ):
debug("> reverseArray(%s)" % str(theArray) )
newArray = []
for x in range(len(theArray),0,-1):
newArray.append(theArray[x-1])
debug("< reverseArray (%s)" % str(newArray) )
return newArray
class ProtocolVersionException( Exception ): pass
class ClientException( Exception ): pass
class ServerException( Exception ): pass
class SettingsException( Exception ): pass
class StatusException( Exception ): pass
class Connection( object ):
maxTransferBufferSize = 1*1024*1024 # 1MB
def __del__( self ):
self.close()
def __init__( self ):
debug( "> mythtv.Connection.__init__()" )
if not 'isConnected' in self.__dict__:
self.isConnected = 0
if self.isConnected == 0:
self.initialise()
debug( "< mythtv.Connection.__init__()" )
def initialise( self ):
try:
s = getInstance( Settings )
# XBMC DNS resolution in socket class is broken. Therefore
# we need to use NetBIOS to determine host IP address.
self.host = str(s.getSetting( "mythtv_host" ))
self.port = int( s.getSetting( "mythtv_port" ) )
self.liveEnabled = False
self.ourConnection = True
self.dataSock = None
self.cmdSock = None
self.cmdSock = self.connect( self.host, False, True )
mythtvutil.debug( mythtvutil.DEBUG_ALL, ">> Backend Protocol: %s <<"%str(self.protocol))
if self.protocol <= int(globals()['supportedProtocol']):
globals()['mythProtocolVersion'] = str(self.protocol)
else:
self.close()
raise ProtocolVersionException, "Protocol " + str(self.protocol) + " is currently not Supported"
# if self.isConnected:
# self.cmdSock = self.annPlayback( self.cmdSock )
# self.isActive()
except Exception, ex:
traceback.print_exc()
self.isConnected = 0
## If we loose our connection reconnect
def reConnect( self ):
debug( "> mythtv.Connected.reConnect()" )
self.cmdSock = self.connect( self.host, False, True )
if not self.isConnected:
self.close()
raise ProtocolVersionException, "Unable to connect to backend"
debug( "< mythtv.Connected.reConnect()" )
def annPlayback( self, s ):
reply = self.sendRequest( s, ["ANN Playback %s 0" % ourHost] )
if not self.isOk( reply ):
raise ServerException, "backend playback refused: " + str(reply)
return s
def annMonitor( self, s ):
reply = self.sendRequest( s, ["ANN Monitor %s 0" % ourHost] )
if not self.isOk( reply ):
raise ServerException, "backend monitor refused: " + str(reply)
return s
def annFileTransfer( self, theHost, filePath ):
s = self.connect( theHost, False, False )
self.sendMsg( \
s, ["ANN FileTransfer %s"%ourHost,filePath] )
reply = self.readMsg( s )
if not self.isOk( reply ):
raise ServerException, "backend filetransfer refused: " + str(reply)
del reply[0] # remove OK
return [reply,s]
def checkFile( self, rec ):
rc = -1
msg = rec.data()[:]
msg.insert(0,'QUERY_CHECKFILE')
reply = self.sendRequest( msg )
return reply[0]
def getEncoder( self, showName ):
encoders = getInstance( Database ).getCaptureCards()
for card in encoders:
theState = self.sendRequest( self.cmdSock, ["QUERY_REMOTEENCODER " + str(card), "GET_STATE" ] )
curRecording = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(card), "GET_RECORDING" ] )
if theState[0] == "0":
break
elif theState[0] == "1":
if showName == curRecording[0]:
return int(card)
elif theState[0] == "4":
if showName == curRecording[0]:
return int(card)
else:
break
return -1
def getEncoderStatus( self, encoder="All" ):
if string.upper(encoder) == "ALL":
curStatus = []
encoders = getInstance( Database ).getCaptureCards()
for card in encoders:
debug("card: %s" %str(card) )
curStatus.append( self.getEncoderStatus(str(card)) )
else:
theState = self.sendRequest( self.cmdSock, ["QUERY_REMOTEENCODER " + encoder, "GET_STATE" ] )
curRecording = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + encoder, "GET_RECORDING" ] )
if theState[0] == "0":
curStatus = "Encoder %s is not recording" % str(encoder)
elif theState[0] == "1":
curStatus = "Encoder %s is Currently Watching %s on %s. Show will end at %s." % ( str(encoder), curRecording[0], curRecording[7], time.strftime("%I:%M%p", time.localtime(float(curRecording[27])) ) )
elif theState[0] == "4":
curStatus = "Encoder %s is Currently Recording %s on %s. Recording will finish at %s" % ( str(encoder), curRecording[0], curRecording[7], time.strftime("%I:%M%p", time.localtime(float(curRecording[27])) ) )
elif theState[0] == "-1":
curStatus = "Encoder %s is Currently not Connected" % str(encoder)
else:
curStatus = "Encoder %s has an Unknown State: %s." % ( str(encoder), str(theState[0]))
return curStatus
#remoteutil.h ->internal mythtv functions
#info here programs/mythbackend/mainserver.cpp
## Created this so it's more picky about which encoder to use
## Checks for things like lost connections, other clients etc
def getFreeEncoder( self, theChannel ):
debug( "> mythtv.Connection.getFreeEncoder(%s)" % theChannel )
global retryCnt
## Here we check what encoders are used by OUR clients
## Then check what the states of these are.
try:
self.d = getInstance( Database )
theCards = self.d.getCaptureCards()
debug("*** REVERSING CARD LIST ***")
theCards = reverseArray(theCards)
debug("*** REVERSED! ***")
freeCard = -1
ourCard = -1
changeChannel = True
isOn = False
for theCardID in theCards:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + theCardID, "CHECK_CHANNEL", theChannel] )
## If the encoder doesn't have the channel just move on!
if int(reply[0]) != 1:
continue
theState = self.sendRequest( self.cmdSock, ["QUERY_REMOTEENCODER " + theCardID, "GET_STATE" ] )
if str(theState[0]) == "0":
isPending = self.isPendingRecording( theCardID, 5 )
if isPending == None:
# If state is '0' (Available) then make our client '99999' (to make sure atleast ONE record get changed)
reply = self.d.setMythSetting("XBMCClientStatus_" + theCardID, ourHost, "99999")
reply = self.d.setMythSettingRow("XBMCClientStatus_" + theCardID, theState[0])
# Use this card only if we don't have another free card (ie already watching tv)
if freeCard == -1:
freeCard = theCardID
isOn = False
self.ourConnection = True
changeChannel = True
elif str(theState[0]) == "1":
# if this one is watching live TV find all clients that are also watching it - Including ourselves...
# We need to check that there is noone else watching it...
# general gets here if our connection dies and we start livetv again
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + theCardID, "GET_RECORDING" ] )
if len( reply ) < recordSize:
raise ServerException, "Unexpected response from server:" + str(reply)
cardChannel = reply[5]
ClientStatus = str(self.d.getMythSetting("XBMCClientStatus_" + theCardID, ourHost))
if str(cardChannel) == str(theChannel):
# Just return this card
isOn = True
# Use Status + 10 to denote it's not our connection!
if ClientStatus != "1":
self.ourConnection = False
else:
self.ourConnection = True
freeCard = theCardID
changeChannel = False
else:
## Generally gets here if we are starting to watch a channel not already tuned on this encoder
## If we started the connection on this encoder we should check noone else is watching it!
if ClientStatus == "1":
## 1 would indicate its OUR connection so we can kill it if we want...
## But first just check everyone else, and make sure noone else is watching it (status 1 or 11)
rows = self.d.getMythSettingRow("XBMCClientStatus_" + theCardID)
foundOth = False
for row in rows:
theState = str(row["settings.data"])
othHost = str(row["settings.hostname"])
if othHost != ourHost and ( theState == "11" or theState == "1" ):
foundOth = True
## Make them the new 'OWNER'
## Not sure if we need to do this here (to make sure atleast 1 record gets changed)???
reply = self.d.setMythSetting("XBMCClientStatus_" + theCardID, othHost, "9999")
reply = self.d.setMythSetting("XBMCClientStatus_" + theCardID, othHost, "1")
## Only use THIS encoder if noone else is!
if foundOth == False:
freeCard = theCardID
self.ourConnection = True
isOn = True
changeChannel = True
elif str(theState[0]) == "4":
pass
elif str(theState[0]) == "5":
## The state is changing so lets sleep a little and try again!
## But only try 5 times...
if retryCnt < 5:
time.sleep( 1 )
retryCnt += 1
return self.getFreeEncoder( theChannel )
else:
retryCnt = 0
pass
else:
pass
debug( "< mythtv.Connection.getFreeEncoder [%s, %s, %s]" % ( str(freeCard),str(isOn),str(changeChannel)) )
return [int(freeCard), isOn, changeChannel]
except Exception, ex:
traceback.print_exc()
def finishRecording( self, encID ):
try:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(encID), "FINISH_RECORDING" ] )
debug( "FINISH RECORDING: " + str(reply) )
if string.upper(reply[0]) == "OK":
return True
else:
return False
except Exception, ex:
debug( str(ex) )
def cancelNextRecording( self, encID ):
try:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(encID), "CANCEL_NEXT_RECORDING" ] )
debug ( "CANCEL NEXT RECORDING: " + str(reply) )
if reply == "ok":
return True
else:
return False
except Exception, ex:
debug ( str(ex) )
## Check if there is a Recording Pending on this encoder (Used by OSD)
def isPendingRecording( self, encID, minutesNotice=1 ):
try:
## Get the seconds till the NEXT recording on this Encoder in the next 5 minutes!
recordings = self.getPendingRecordings()
for r in recordings:
# Might be inputid() or sourceid() ???
if str(r.cardid()) == str(encID):
stTime = r.recstarttime()
## If the rec start time isn't set use the normal start time!
if int(stTime) == 0:
stTime = r.starttimets()
nxtTime = int(stTime) - int(time.time())
if nxtTime > ( 60 * minutesNotice ) :
return None
if nxtTime > 0:
if nxtTime < ( 60 * minutesNotice ) :
debug("Found pending recording for Encoder %s and it starts in %s seconds" % ( str(encID), str(nxtTime) ) )
return [r.title(),r.subtitle(),nxtTime]
except Exception, ex:
debug ( str(ex) )
return None
## added so it uses the available values to create the path to the ringbuffer
## useful if there is more than one backend... or is it?!?
def liveRequest( self, channel ):
debug( "> mythtv.Connection.liveRequest(%s)" % str(channel) )
if self.liveEnabled:
raise ServerException, "Live TV alerady enabled"
try:
## If for some reason we lost our connection reconnect!
if self.cmdSock == None:
self.reConnect()
## Get a new encoder or one watching this channel!
reply = self.getFreeEncoder( channel)
if int(reply[0]) < 0 :
return [ None, None, None, None ]
self.ourRecorder = str(reply[0])
isOn = reply[1]
changeChannel = reply[2]
self.liveEnabled=True
bufferLocation = self.setupLiveTV( isOn, channel, changeChannel )
d = getInstance( Database )
## If we created the ringbuf then set our status to 1 else 11
if self.ourConnection:
reply = d.setMythSetting("XBMCClientStatus_" + self.ourRecorder, ourHost, "1")
else:
reply = d.setMythSetting("XBMCClientStatus_" + self.ourRecorder, ourHost, "11")
reply = d.setMythSetting("XBMCClientEncoder", ourHost, self.ourRecorder)
debug( "< mythtv.Connection.liveRequest [%s,%s,%s,%s]" % (str(bufferLocation),str(isOn),str(changeChannel),str(self.ourRecorder) ) )
return [ bufferLocation, isOn, changeChannel, self.ourRecorder ]
except Exception, ex:
traceback.print_exc()
def setupLiveTV ( self, isOn, channel, changeChannel ):
debug( "> mythtv.Connection.setupLiveTV(%s,%s)" % ( str(isOn),str(channel) ) )
try:
cardid = str(self.ourRecorder)
s = getInstance( Settings )
d = getInstance( Database )
theCardHost = d.getCaptureCardHost( cardid )
backendIP = d.getMythSetting("BackendServerIP", theCardHost)
backendPort = d.getMythSetting("BackendServerPort", theCardHost)
backendPath = d.getMythSetting("LiveBufferDir", theCardHost)
channelID = d.getChanID(channel)
if int(globals()["mythProtocolVersion"]) >= 26:
if isOn:
if self.ourConnection:
## if it's not our connect then we can get chains using channel id!
globals()["chainid"] = d.getMythSetting("XBMCClientChainID", str(ourHost))
debug("Current ChainID: %s" %str(globals()["chainid"]))
else:
globals()["chainid"] = None
else:
globals()["chainid"] = "XBMCMythTV-" + str(ourHost) #" + "-" + time.strftime("%Y%m%d%H%M%s", time.localtime())
reply = d.setMythSetting("XBMCClientChainID", str(ourHost), globals()["chainid"])
d.clearChainFiles(globals()["chainid"])
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "SPAWN_LIVETV", globals()["chainid"], "0" ] )
inputType = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "GET_INPUT"] )
# self.sendRequestAndCheckReply( ["QUERY_RECORDER " + cardid, "PAUSE"] )
# self.sendRequestAndCheckReply( ["QUERY_RECORDER " + cardid, "SET_CHANNEL", channel] )
if not self.isOk( reply ):
self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "DONE_RINGBUF" ] )
raise ServerException, "Cannot start live TV: " + str(reply)
isRecording = 0
while isRecording != 1:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "IS_RECORDING" ] )
debug("isrecording: %s" % str(reply[0]))
isRecording = int(reply[0])
time.sleep(0.5)
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "FRONTEND_READY" ] )
debug("frontend ready: %s" % str(reply))
globals()["chainCnt"] = 0
## Need to sleep for a bit as I always seem to get crap for the first few seconds
## Gives the Backend extra ime to start the recording and update db records etc...
## Some backends need more time than others so changed via settings
# sleepTime = getInstance( Settings ).getSetting( "mythtv_tunewait" )
# debug("Time to Sleep: %s" % str(sleepTime) )
# time.sleep(int(sleepTime))
## Get the latest recorded file for playback (only want the latest as the others are probably crap anyway
smbbufferLocation = d.getChainFiles( globals()["chainid"], None, -1, 1 )
## if changeChannel:
## self.changeLiveTV( channel )
else:
ringBuf = "ringbuf" + cardid + ".nuv"
ringbufferLocation = "rbuf://" + backendIP + ":" + backendPort + backendPath + "/" + ringBuf
smbPath = s.getSetting( "paths_livetvprefix" )
smbbufferLocation = ringBuf
if isOn == False:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "SETUP_RING_BUFFER", "0" ] )
ringbufferLocation = reply[0]
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "SPAWN_LIVETV" ] )
if not self.isOk( reply ):
self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + cardid, "DONE_RINGBUF" ] )
raise ServerException, "Cannot start live TV: " + str(reply)
debug( "< mythtv.Connection.setupLiveTV [%s]" % str(smbbufferLocation) )
return smbbufferLocation
except Exception, ex:
traceback.print_exc()
raise ServerException, "Cannot setup live TV: " + str(ex)
def throwIfLiveTVNotEnabled( self ):
if not self.liveEnabled:
raise ServerException, "Live TV is not enabled."
def changeLiveTV( self, number):
if not self.liveEnabled:
return ""
if int(globals()["mythProtocolVersion"]) < 26:
self.sendRequestAndCheckReply( ["QUERY_RECORDER " + str(self.ourRecorder), "PAUSE"] )
self.setRingBufferPosition( 0 )
self.sendRequestAndCheckReply( ["QUERY_RECORDER " + str(self.ourRecorder), "SET_CHANNEL", number] )
self.waitForChannelChange()
ringBuf = "ringbuf" + str(self.ourRecorder) + ".nuv"
smbbufferLocation = ringBuf
else:
d = getInstance( Database )
changed = False
try:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(self.ourRecorder), "CHECK_CHANNEL_PREFIX", number] )
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(self.ourRecorder), "CHECK_CHANNEL", number] )
if int(reply[0]) == 1:
# inputType = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(self.ourRecorder), "GET_INPUT"] )
# debug(" ** ENCODER INPUT IS %s" %str(inputType) )
self.sendRequestAndCheckReply( ["QUERY_RECORDER " + str(self.ourRecorder), "PAUSE"] )
self.sendRequestAndCheckReply( ["QUERY_RECORDER " + str(self.ourRecorder), "SET_CHANNEL", number] )
isRecording = 0
while isRecording != 1:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(self.ourRecorder), "IS_RECORDING" ] )
debug("changelivetv - isrecording: %s" % str(reply[0]))
isRecording = int(reply[0])
changed = True
smbbufferLocation = d.getChainFiles( globals()["chainid"], number, -1, 1 )
except:
pass
return smbbufferLocation
def getRingBufferCurrentPosition( self ):
self.throwIfLiveTVNotEnabled()
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(self.ourRecorder), "GET_FILE_POSITION"] )
if len( reply ) != 2:
raise ServerException, "Unexpected response from server:" + str(reply)
return int(reply[0]) + int(reply [1])
def setRingBufferPosition( self, newPosition=0 ):
pos = self.getRingBufferCurrentPosition()
self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + str(self.ourRecorder), "SEEK_RINGBUF",\
str(newPosition) , "NOT_USED", "1", str(pos) ] )
#always returns error....
def waitForChannelChange( self ):
#Wait for the ringbuffer size to get
# larger than the xbmc cache size
minBufSize = getInstance( Settings ).getXBMCCache()
if minBufSize == "":
minBufSize = int(getInstance( Settings ).getSetting( "mythtv_minlivebufsize" ))
else:
minBufSize = ( int(minBufSize) * 1024 ) + ( 1024 * 512 ) # add another 512k for good measure!
changed = False
iterations = 0
while not changed and iterations < CHANGE_CHANNEL_TRIES:
iterations += 1
ringbufferSize = self.getRingBufferCurrentPosition()
if ringbufferSize >= minBufSize:
changed = True
else:
time.sleep( 0.25 )
return changed
def closeLive( self ):
self.throwIfLiveTVNotEnabled()
## Instead of just closing live tv check no other Clients are watching the ringbuf
d = getInstance( Database )
rows = d.getMythSettingRow("XBMCClientStatus_" + self.ourRecorder)
foundOth = False
for row in rows:
theState = str(row["settings.data"])
othHost = str(row["settings.hostname"])
if othHost != ourHost and ( theState == "11" or theState == "1" ):
foundOth = True
## Make them the new 'OWNER'
## Not sure if we need to do this here (to make sure atleast 1 record gets changed)???
reply = d.setMythSetting("XBMCClientStatus_" + self.ourRecorder, othHost, "9999")
reply = d.setMythSetting("XBMCClientStatus_" + self.ourRecorder, othHost, "1")
## If this connection is OURS then close it
if foundOth == False:
if int(globals()["mythProtocolVersion"]) >= 26:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + self.ourRecorder, "STOP_LIVETV" ] )
d.clearChainFiles(globals()["chainid"])
globals()["chainid"] = None
else:
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + self.ourRecorder, "STOP_LIVETV" ] )
reply = self.sendRequest( self.cmdSock, ["QUERY_RECORDER " + self.ourRecorder, "DONE_RINGBUF" ] )
## Update OUR Status - Used to check for crashes!
reply = d.setMythSetting("XBMCClientStatus_" + self.ourRecorder, ourHost, "0")
reply = d.setMythSetting("XBMCClientChainID", ourHost, "-1")
reply = d.setMythSetting("XBMCClientStatus", ourHost, "0")
reply = d.setMythSetting("XBMCClientEncoder", ourHost, "-1")
self.ourRecorder="-1"
self.liveEnabled=False
def buildMsg( self, msg ):
msg = string.join( msg, SEP )
return "%-8d%s" % (len(msg), msg)
def close( self ):
if self.liveEnabled:
self.closeLive()
if self.cmdSock:
if int(self.isConnected) > 0:
self.sendMsg( self.cmdSock, ['DONE'] )
self.isConnected=0
self.cmdSock.shutdown(2)
self.cmdSock.close()
self.cmdSock = None
if self.dataSock:
self.dataSock.shutdown(2)
self.dataSock.close()
self.dataSock = None
def connect( self, theHost, Playback=False, Monitor=True ):
try:
debug("> mythtv.connection.connect(%s,%s,%s)"%(theHost,str(Playback),str(Monitor)))
s = None
s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
s.connect((theHost, self.port))
if Monitor or Playback:
self.protocol = int(self.getProtocol(s, theHost))
globals()['mythProtocolVersion'] = str(self.protocol)
# if self.protocol <= 31 and Monitor:
# Monitor = False
# Playback = True
if Monitor:
s = self.annMonitor( s )
elif Playback:
s = self.annPlayback( s )
if s != None:
self.isConnected = 1
debug("< mythtv.connection.connect")
return s
except Exception, ex:
raise ServerException, "Unable to Connect to Backend. Is it Running?"
return None
def deleteRecording( self, rec ):
debug("> deleteRecording(%s)"%rec.title());
msg = rec.data()[:]
msg.insert( 0, 'DELETE_RECORDING' )
msg.append( '0' )
reply = self.sendRequest( self.cmdSock, msg )
if sre.match( '^-?\d+$', reply[0] ):
rc = int(reply[0])
else:
raise ServerException, reply[0]
debug("< deleteRecording [%s]" %str(rc))
return rc # number deleted
def rerecordRecording( self, rec ):
debug("> deleteRecording(%s)"%rec.title());
msg = rec.data()[:]
msg.insert( 0, 'DELETE_RECORDING' )
msg.append( '0' )
reply = self.sendRequest( self.cmdSock, msg )
if sre.match( '^-?\d+$', reply[0] ):
rc = int(reply[0])
else:
raise ServerException, reply[0]
msg = rec.data()[:]
msg.insert( 0, 'FORGET_RECORDING' )
msg.append( '0' )
reply = self.sendRequest( self.cmdSock, msg )
if sre.match( '^-?\d+$', reply[0] ):
rc = int(reply[0])
else:
raise ServerException, reply[0]
debug("< deleteRecording [%s]" %str(rc))
return rc # number deleted
def genPixmap( self, rec, theHost ):
rc = -1
msg = rec.data()[:]
# clear out fields - this is based on what mythweb does
if int(mythProtocolVersion) < 8:
# mythtv-0.14, pre 0.15 CVS builds
msg[0] = ' ' # title
msg[1] = ' ' # subtitle
msg[2] = ' ' # description
msg[3] = ' ' # category
# chanid
msg[5] = ' ' # chanstr
msg[6] = ' ' # chansign
msg[7] = ' ' # channame
# filename
msg[9] = ' ' # upper 32 bits
msg[10] = ' ' # lower 32 bits
# starttime
# endtime
msg[13] = '0' # conflicting
msg[14] = '1' # recording
msg[15] = '0' # duplicate
# hostname
msg[17] = '-1' # sourceid
msg[18] = '-1' # cardid
msg[19] = '-1' # inputid
msg[20] = ' ' # recpriority
msg[21] = ' ' # recstatus
msg[22] = ' ' # recordid
msg[23] = ' ' # rectype
msg[24] = ' ' # recdups
# recstarttime
msg[26] = ' ' # recendtime
msg[27] = ' ' # repeat
# program flags
else:
# mythtv-0.16
msg[0] = ' ' # title
msg[1] = ' ' # subtitle
msg[2] = ' ' # description
msg[3] = ' ' # category
# chanid
msg[5] = ' ' # channum
msg[6] = ' ' # chansign
msg[7] = ' ' # channame
# filename
msg[9] = '0' # upper 32 bits
msg[10] = '0' # lower 32 bits
# starttime
# endtime
msg[13] = '0' # conflicting
msg[14] = '1' # recording
msg[15] = '0' # duplicate
# hostname
msg[17] = '-1' # sourceid
msg[18] = '-1' # cardid
msg[19] = '-1' # inputid
msg[20] = ' ' # recpriority
msg[21] = ' ' # recstatus
msg[22] = ' ' # recordid
msg[23] = ' ' # rectype
msg[24] = '15' # dupin
msg[25] = '6' # dupmethod
# recstarttime
# recendtime
msg[28] = ' ' # repeat
msg[29] = ' ' # program flags
msg[30] = ' ' # recgroup
msg[31] = ' ' # commfree
msg[32] = ' ' # chanoutputfilters
# seriesid
# programid
# dummy lastmodified
if int(mythProtocolVersion) >= 14:
msg[36] = '0' # dummy stars
# dummy org airdate
if int(mythProtocolVersion) >= 32:
msg[38] = '0' # hasAirDate
msg[39] = '0' # playgroup
msg[40] = '0' # recpriority2
msg[41] = '0' # parentid
# storagegroup
msg.append('') # trailing separator
msg.insert(0,'QUERY_GENPIXMAP')
s = self.connect( theHost, False, True )
reply = self.sendRequest( s, msg )
if self.isOk( reply ):
rc = 0
s.shutdown(2)
s.close()
s = None
return rc
def getPendingRecordings( self, cnt="ALL", query='QUERY_GETALLPENDING' ):
debug( "> mythtv.Connection.getPendingRecordings(%s)" % query )
retRows = []
offset = 0
reply = self.sendRequest( self.cmdSock, [query,'2'] )
# determine how many rows were retrieved
numRows = int( reply[1] )
offset += 2
if mythProtocolVersion == "1":
recordSize = 29
elif mythProtocolVersion == "4":
recordSize = 32
elif mythProtocolVersion == "8": # 0.15.1
recordSize = 35
elif mythProtocolVersion == "13": # 0.16
recordSize = 38
elif mythProtocolVersion == "14": # 0.17
recordSize = 38
elif mythProtocolVersion == "15": # 0.18
recordSize = 39
elif mythProtocolVersion == "17": # ???
recordSize = 39
elif mythProtocolVersion == "19": # ???
recordSize = 40
elif mythProtocolVersion == "26": # 0.19
recordSize = 41
elif mythProtocolVersion == "27": # ??
recordSize = 41
elif mythProtocolVersion == "29": # 0.19 SVN
recordSize = 41
elif mythProtocolVersion == "30": # 0.19 SVN
recordSize = 41
elif mythProtocolVersion == "31": # 0.20 SVN
recordSize = 42
elif mythProtocolVersion == "32": # Not tested
recordSize = 43
elif mythProtocolVersion == "33": # Not tested
recordSize = 43
elif mythProtocolVersion == "34": # Not tested
recordSize = 43
else:
raise \
ProtocolVersionException, \
"unsupported call for protocol version"
if string.upper(str(cnt)) != "ALL":
numRows = int(cnt)
for i in range(0,numRows):
retRows.append( \
mythtvstruct.ProgramFromRecordings( \
reply[offset:offset+recordSize] ) )
offset += recordSize
debug( "< mythtv.Connection.getPendingRecordings" )
return retRows
def getRecordings( self, recgroup='default', title='all shows' ):
query='QUERY_RECORDINGS Delete'
debug( "> mythtv.Connection.getRecordings(%s, %s, %s)" % ( query, recgroup, title) )
try:
retRows = []
offset = 0
reply = self.sendRequest( self.cmdSock, [query] )
# determine how many rows were retrieved
numRows = int( reply[0] )
offset += 1
if mythProtocolVersion == "1":
recordSize = 29
elif mythProtocolVersion == "4":
recordSize = 32
elif mythProtocolVersion == "8": # 0.15.1
recordSize = 35
elif mythProtocolVersion == "13": # 0.16
recordSize = 38
elif mythProtocolVersion == "14": # 0.17
recordSize = 38
elif mythProtocolVersion == "15": # 0.18.1
recordSize = 39
elif mythProtocolVersion == "17": # ???
recordSize = 39
elif mythProtocolVersion == "19": # ???
recordSize = 40
elif mythProtocolVersion == "26": # 0.19
recordSize = 41
elif mythProtocolVersion == "27": # ??
recordSize = 41
elif mythProtocolVersion == "29": # 0.19 SVN
recordSize = 41
elif mythProtocolVersion == "30": # 0.20
recordSize = 41
elif mythProtocolVersion == "31": # 0.20 SVN
recordSize = 42
elif mythProtocolVersion == "32": # Not tested
recordSize = 43
elif mythProtocolVersion == "33": # Not tested
recordSize = 43
elif mythProtocolVersion == "34": # Not tested
recordSize = 43
else:
return retRows
for i in range(0,numRows):
tmpArray = reply[offset:offset+recordSize]
if (( string.upper(tmpArray[30]) == string.upper(recgroup) or string.upper(recgroup) == "ALL GROUPS" )
and ( string.upper(tmpArray[0]) == string.upper(title) or string.upper(title) == "ALL SHOWS" )):
retRows.append( mythtvstruct.ProgramFromRecordings( tmpArray ) )
offset += recordSize
except Exception, ex:
debug ("ERROR IN RECORDINGS - %s" % str(ex))
traceback.print_exc()
debug( "< mythtv.Connection.getRecordings" )
return retRows
def getSingleProgram( self, chanid, starttime, endtime ):
debug( "> mythtv.getSingleProgram( " + \
"chanid=[%s] starttime=[%s] endtime=[%s])"% \
(chanid,starttime,endtime) )
shows = self.getRecordings("ALL GROUPS","ALL SHOWS")
for s in shows:
debug( "comparing chanid=[%s] starttime=[%s] endtime=[%s]"% \
(s.chanid(), s.starttime(), s.endtime()) )
if s.chanid() == chanid and \
s.starttime()[:-4] == starttime[:-4] and \
s.endtime() == endtime:
debug( \
"< mythtv.getSingleProgram()" +\
" => [%s]"%s )
return s
debug( "< mythtv.getSingleProgram - Unable to find selected show." )
return None
def isAccept( self, msg, protocolVersion ):
return msg[0] == "ACCEPT" and msg[1] == protocolVersion
def isActive( self ):
return True
## reply = self.sendRequest( self.cmdSock, ['','QUERY_IS_ACTIVE_BACKEND', self.host] )
## if reply[0] != 'TRUE':
## raise ServerException, "backend is not active: " + str(reply)
def isOk( self, msg ):
debug ("isOK: %s" % str(msg) )
if msg == None or len(msg) == 0:
return False
else:
return string.upper(msg[0]) == "OK"
def getProtocol( self , s, theHost ):
## Get the Current Protocol and Add us as a client
reply = self.sendRequest( s, ["MYTH_PROTO_VERSION " + str(globals()['mythProtocolVersion'])])
if reply:
curPro = reply[1]
curRes = reply[0]
if curRes != "ACCEPT":
# s = self.connect( theHost, False, True )
reply = self.sendRequest(s, ["MYTH_PROTO_VERSION " + str(curPro)] )
return curPro
else:
return "0"
def getFreeSpace( self ):
space = []
try:
if int(globals()["mythProtocolVersion"]) >= 26:
reply = self.sendRequest( self.cmdSock, ["QUERY_FREE_SPACE"] )
if len(reply) < 5:
tspace = reply[1]
uspace = reply[3]
else:
tspace = reply[5]
uspace = reply[7]
else:
reply = self.sendRequest( self.cmdSock, ["QUERY_FREESPACE"] )
tspace = reply[0]
uspace = reply[1]
freeSpace = int(tspace) - int(uspace)
space.append( formatSize(freeSpace,True) )
space.append( formatSize(tspace,True) )
space.append( formatSize(uspace,True) )
except Exception, ex:
debug("Myth Protocol %s Doesn't support QUERY_FREESPACE OR QUERY_FREE_SPACE" % str(globals()["mythProtocolVersion"]) )
space.append("")
space.append("")
space.append("")
return space
def getLoad( self ):
reply = self.sendRequest( self.cmdSock, ["QUERY_LOAD"] )
debug ( str(reply) )
load = []
load.append(reply[0])
load.append(reply[1])
load.append(reply[2])
return load
def getUptime( self ):
reply = self.sendRequest( self.cmdSock, ["QUERY_UPTIME"] )
debug ( str(reply) )
return reply
def getMythFillStatus( self ):
myDB = getInstance( Database )
start = myDB.getMythSetting( "mythfilldatabaseLastRunStart" )
end = myDB.getMythSetting( "mythfilldatabaseLastRunEnd" )
status = myDB.getMythSetting( "mythfilldatabaseLastRunStatus" )
fillStatus = "Last mythfilldatabase run "
fillStatus += "started on %s" % str(start)
if end > start:
fillStatus += " and ended on %s" % str(end)
fillStatus += ". %s" % str(status)
return fillStatus
def getGuideData( self ):
lastShow = getInstance( Database ).getLastShow()
dataStatus = ""
if lastShow == None:
dataStatus = "There's no guide data available! Have you run mythfilldatabase?"
else:
timeDelt = lastShow - datetime.datetime.now()
daysOfData = timeDelt.days + 1
debug("days of data: %s" % str(daysOfData) )
debug ( "End Date: %s Now: %s Diff: %s" % ( str(lastShow), str(datetime.datetime.now()), str(lastShow - datetime.datetime.now()) ) )
dataStatus = "There's guide data until %s (%s" % ( lastShow.strftime("%Y-%m-%d %H:%M"), str(daysOfData) )
if daysOfData == 1:
dataStatus += "day"
else:
dataStatus += "days"
dataStatus += ")."
if daysOfData <= 3:
dataStatus += "WARNING: is mythfilldatabase running?"
return dataStatus
def readMsg( self, s ):
## Was getting errors with this (s.recv was string that couldn't be converted to int!)
debug( "> readMsg()" )
retMsg = ""
try:
retMsg = s.recv( 8 )
reply = ""
if string.upper(retMsg) == "OK":
return "OK"
n = int( retMsg )
debug( "reply len: %d" % n )
i = 0
while i < n:
debug ( " i=%d n=%d " % ( i,n) )
reply += s.recv( n - i )
i = len( reply )
debug( "< readMsg: [%s]" % reply)
return reply.split( SEP )
except Exception, ex:
debug ("READ MSG ERROR: %s" %str(ex) )
debug ("RETURNED: %s" %str(retMsg) )
traceback.print_exc()
def sendMsg( self, s, req ):
debug( "> sendMsg" )
try:
msg = self.buildMsg( req )
debug( "msg=[%s]" % msg )
s.send( msg )
except Exception, ex:
debug(" Socket not connected - %s" % str(ex))
debug( "< sendMsg" )
def sendRequest( self, s, msg ):
debug( "> sendRequest" ) # : " + str(msg))
try:
if s == None:
s = self.connect( self.host, False, False )
self.sendMsg( s, msg )
reply = self.readMsg( s )
debug( "< sendRequest" ) # :" + str(reply))
return reply
except Exception, ex:
traceback.print_exc()
return ""
def sendRequestAndCheckReply( self, msg, errorMsg="Unexpected reply from server: " ):
reply = self.sendRequest( msg )
if not self.isOk( reply ):
raise ServerException, errorMsg + str(reply)
def getFileSize( self, backendPath, theHost ):
"""
Method to retrieve remote file size. The backendPath is in the format
described by the transferFile method.
"""
debug( "> getFileSize" )
rc = 0
# thisDataSock = self.connect(theHost)
ft,s = self.annFileTransfer( theHost, backendPath )
debug( "ft=<%s>" % str(ft) )
rc = long( ft[2] )
s.shutdown(2)
s.close()
s = None
debug( "< getFileSize rc=%d" % rc )
return rc
def rescheduleNotify( self, schedule = None ):
"""
Method to instruct the backend to reschedule recordings. If the
schedule is not specified, all recording schedules will be rescheduled
by the backend.
"""
debug( "> rescheduleNotify( schedule=[%s] )"%schedule )
s = self.connect( self.host, False, True )
recordId = None
if schedule:
recordId = schedule.recordid()
if recordId == None:
recordId = 0
reply = self.sendRequest( s, ["RESCHEDULE_RECORDINGS %d" % recordId] )
if int( reply[0] ) < 0:
raise ServerException, "Reschedule notify failed: " + str(reply)
debug( "< rescheduleNotify" )
def transferFile( self, backendPath, destPath, theHost ):
"""
Transfer a file from the remote backendPath to destPath. The
format of backendPath is:
myth://<host>:<port><path>
Where:
<host> The server on which the file resides.
<port> The port associated with the server.
<path> The path on the server. '/' is not the root of the
filesystem, but rather the root of the filesystem that
myth was configured with. E.g. Myth records to /myth/tv/
so the path for the file in '/myth/tv/test.png' would be
'/test.png'.
"""
debug( "> transferFile" )
rc = 0
# thisDataSock = self.connect( theHost, False )
thisCmdSock = self.connect( theHost, False, True )
ft,s = self.annFileTransfer( theHost, backendPath )
debug( "ft=<%s>" % str(ft) )
n = long( ft[2] )
if n > 0:
msg = [ 'QUERY_FILETRANSFER ' + ft[0],
'REQUEST_BLOCK',
ft[2] ]
self.sendMsg( thisCmdSock, msg )
i = 0
tries = 0
response = 0
fh = file( destPath, "w+b" )
while i < n:
debug( "waiting for %d bytes"%(n-i) )
sz = n - i
if sz > self.maxTransferBufferSize:
sz = self.maxTransferBufferSize
data = s.recv( sz )
sz = len(data)
debug( "received %d bytes"%sz )
i += sz
if sz > 0:
fh.write( data )
debug( "wrote %d bytes" % sz )
else:
tries += 1
if tries >= 3:
rc = -1 # premature EOF
break
else:
reply = self.readMsg( thisCmdSock )
response = 1
debug( "reply=[%s]"%reply )
if not response:
reply = self.readMsg( thisCmdSock )
debug( "reply=[%s]"%reply )
fh.close()
if rc < 0:
os.remove( destPath )
else:
rc = -1
s.shutdown(2)
s.close()
s = None
thisCmdSock.shutdown(2)
thisCmdSock.close()
thisCmdSock = None
debug( "< transferFile rc=%d" % rc )
return rc
def testActive( self ):
active = 0
try:
self.isActive()
active = 1
except:
self.isConnected = 0
return active
class Database( object ):
def __del__( self ):
debug( "> mythtv.Database.__del__()" )
self.close()
debug( "< mythtv.Database.__del__()" )
def __init__( self ):
debug( "> mythtv.Database.__init__()" )
if not 'isConnected' in self.__dict__:
self.isConnected = 0
if self.isConnected == 0:
self.initialise()
debug( "< mythtv.Database.__init__()" )
def initialise( self ):
s = getInstance( Settings )
debug( "initializing database connection" )
# XBMC DNS resolution in socket class is broken. Therefore
# we need to use NetBIOS to determine host IP address.
host = str(s.getSetting( "mysql_host" ))
isEnabled = mythtvutil.debugGetLevel() & mythtvutil.DEBUG_DATABASE
try:
self.conn = mysql.Connection( \
host, \
str(s.getSetting( "mysql_database" )), \
str(s.getSetting( "mysql_user" )), \
str(s.getSetting( "mysql_password" )), \
int(s.getSetting( "mysql_port" )), \
debug=isEnabled )
self.isConnected = 1
except:
self.isConnected = 0
# change default encoding
try:
self.conn.executeSQL( \
"set names %s"%str(s.getSetting( "mysql_encoding_override" )) )
except:
pass
def close( self ):
debug( "> mythtv.Database.close()" )
if self.isConnected:
self.conn.close()
del self.conn
self.isConnected = 0
debug( "< mythtv.Database.close()" )
## New LiveTV Queries
def clearChainFiles( self, chainid ):
"""
Deletes all tvchain records for TVChain
"""
try:
sql = "DELETE FROM tvchain WHERE tvchain.chainid = '%s'" % str(chainid)
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error Deleting TVChains: " + self.conn.getErrorMsg()
return False
return True
except Exception, ex:
debug ( str(ex) )
return False
def getChainFiles( self, chainid, chanid, lastChainPos, limit="1", tryOnce=False):
"""
Returns an Array for all the Files Associated with a TV Chain or ChanID
"""
debug( "> mythtv.Database.getChainFiles(%s,%s,%s,%s)" % ( str(chainid), str(chanid), str(lastChainPos),str(limit) ) )
tvChains = []
lastFile = ""
lastChain = lastChainPos
sql = """
SELECT
recorded.basename,
tvchain.chainpos,
tvchain.chainid,
tvchain.channame
FROM
tvchain
LEFT JOIN recorded ON
recorded.chanid = tvchain.chanid
AND recorded.starttime = tvchain.starttime
WHERE tvchain.chainpos > %s """ % str(lastChainPos)
if chanid != None:
sql += " AND tvchain.channame = '%s'" % str(chanid)
if chainid != None:
sql += " AND tvchain.chainid = '%s'" % str(chainid)
sql += " ORDER BY tvchain.chainpos DESC LIMIT %s" % str(limit)
debug("SQL: %s" % str(sql) )
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error Retrieving TV Chains: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
debug(" basename: %s" % row["recorded.basename"] )
if chainid == None:
chainid = str(row["tvchain.chainid"])
globals()["chainid"] = chainid
lastChain = row["tvchain.chainpos"]
if self.waitForBigFile(row["recorded.basename"]):
tvChains.append(row)
if len(tvChains) == 0 and not tryOnce:
debug(" PAUSING FOR 2 SECOND TO GET MORE CHAINS")
time.sleep(1)
globals()["chainCnt"] += 1
if globals()["chainCnt"] > 10:
globals()["chainCnt"] = 0
return tvChains
return self.getChainFiles( chainid, chanid, lastChain, 1)
return tvChains
def waitForBigFile( self, basename):
debug("> waitForBigFile(%s)" % basename)
try:
## If we can't get the XBMC Cache for some reason then fall back to xbmcmythtv cache setting
minBufSize = getInstance( Settings ).getXBMCCache()
if minBufSize == "":
minBufSize = int(getInstance( Settings ).getSetting( "mythtv_minlivebufsize" ))
else:
minBufSize = ( int(minBufSize) * 1024 ) + ( 512 * 1024 )
curSize = -1
zeroCnt = 0
lastSize = 0
cnt = 0
while curSize < minBufSize:
time.sleep(1)
sql = "SELECT recorded.filesize FROM recorded WHERE recorded.basename = '%s' LIMIT 1" % str(basename)
debug ( sql )
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error Retrieving Filesize: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
curSize = int(row["recorded.filesize"])
if lastSize == curSize:
if curSize == 0 and zeroCnt <= 5:
zeroCnt += 1
else:
zeroCnt = 0
debug ("*** Current Size of %s is %s, wanted %s" % ( str(basename), str(curSize), str(minBufSize) ))
return False
lastSize = curSize
if curSize < minBufSize:
time.sleep(1)
cnt += 1
debug("*** [%s] waitForBigFile: Current Size of %s is %s, waiting for %s" % ( str(cnt), str(basename), str(curSize), str(minBufSize) ))
except Exception, ex:
traceback.print_exc()
debug ("*** Error while waiting for big file: [%s]" % str(ex) )
debug("< waitForBigFile [%s,%s]" % ( str(basename),str(curSize) ) )
return True
def setMythSettingRow ( self, value, data):
"""
Sets a Value in the Settings table for All Hosts
"""
sql = """
UPDATE
settings
SET
settings.data="%s"
WHERE
settings.value="%s"
""" % ( str(data), str(value) )
if not self.conn.executeSQL( sql ):
return False
return True
def delMythSetting ( self, value, host ):
"""
Deletes a Value in the Settings table for Host
"""
sql = """
DELETE
FROM
settings
WHERE
settings.hostname = "%s"
AND settings.value = "%s"
""" % ( str(host), str(value) )
if not self.conn.executeSQL( sql ):
return False
return True
def setMythSetting ( self, value, host, data):
"""
Sets a Value in the Settings table for Host
"""
sql = """
SELECT
settings.data
FROM
settings
WHERE
settings.hostname = "%s"
AND settings.value = "%s"
""" % ( str(host), str(value) )
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error setting value: " + self.conn.getErrorMsg()
# retrieve the current setting
curValue = "--NO VALUE--"
rowIter = self.conn.dictRowIterator()
for row in rowIter:
curValue = str(row["settings.data"])
break
if str(curValue) == str(data):
return True
if curValue == "--NO VALUE--":
sql = """
INSERT INTO
settings
(
settings.value,
settings.hostname,
settings.data
)
VALUES
(
"%s",
"%s",
"%s"
)
""" % ( str(value), str(host), str(data) )
else:
sql = """
UPDATE
settings
SET
settings.data="%s"
WHERE
settings.value="%s" AND
settings.hostname="%s"
""" % ( str(data), str(value), str(host) )
if not self.conn.executeSQL( sql ):
return False
return True
def getMythSettingRow( self, val ):
"""
Returns the Value from the Settings Table for Host
"""
sql = """
SELECT
settings.data,
settings.hostname
FROM
settings
WHERE
settings.value = "%s"
""" % ( str(val) )
if not self.conn.executeSQL( sql):
raise Exception, \
"Error retrieving setting: " + self.conn.getErrorMsg()
# retrieve the setting
rowIter = self.conn.dictRowIterator()
return rowIter
def getMythSetting( self, val, hst=None ):
"""
Returns the Value from the Settings Table for Host
"""
debug(">getMythSetting(%s,%s)"%(str(val), str(hst)))
sql = """
SELECT
settings.data
FROM
settings
WHERE
"""
if hst != None:
sql += """
settings.hostname = "%s"
AND settings.value = "%s"
""" % ( str(hst), str(val) )
else:
sql += """
settings.value = "%s"
""" % ( str(val) )
retryCnt = 0
result = -1
try:
if not self.conn.executeSQL( sql):
raise Exception, \
"Error retrieving setting: " + self.conn.getErrorMsg()
# retrieve the setting
rowIter = self.conn.dictRowIterator()
for row in rowIter:
result = str(row["settings.data"])
except Exception, ex:
if retryCnt < 5:
retryCnt += 1
debug( '***ERROR GETSETTING*** %s - Retry %s' %(str(ex), str(retryCnt)) )
time.sleep( 0.5 )
result = self.getMythSetting( val, hst )
else:
retryCnt = 0
traceback.print_exc()
debug( '***ERROR GETSETTING*** %s' %str(ex) )
debug("<getMythSetting [%s]"%str(result))
return result
def getCaptureCardHost( self , cardid):
sql = """
SELECT
capturecard.hostname
FROM
capturecard
WHERE
capturecard.cardid = '%s'
""" % ( str(cardid) )
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error getting card host: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
return( str(row["capturecard.hostname"]) )
return -1
def getChanCallsign( self, channum ):
sql = "SELECT channel.callsign FROM channel WHERE channel.channum='%s' LIMIT 1" % str(channum)
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error getting Callsign: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
return str(row["channel.callsign"])
def getChanID( self, channum ):
sql = "SELECT channel.chanid FROM channel WHERE channel.channum='%s' LIMIT 1" % str(channum)
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error getting encoders: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
return str(row["channel.chanid"])
def getCaptureCards( self ):
debug("> getCaptureCards ()" )
cards = []
# if str(globals()['mythProtocolVersion']) == "31":
# return cards
try:
sql = "SELECT capturecard.cardid FROM capturecard"
if str(globals()['mythProtocolVersion']) >= "31":
sql += " WHERE capturecard.parentid = 0"
sql += " ORDER BY capturecard.cardid"
debug( sql )
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error getting encoders: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
cardID = row["capturecard.cardid"]
cards.append(cardID)
except:
debug(" ** Failed to get capture cards **" )
debug("< getCaptureCards (%s)" %str(cards) )
return cards
def getClientStatus( self, hst ):
sql = """
SELECT
settings.data
FROM
settings
WHERE
settings.value BEGINS "%s" AND
setting.hostname = "%s" AND
settings.data = "1"
""" % ( "XBMCClientStatus", str(hst) )
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error getting encoders: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
if len(rowIter) == 0:
isOn = False
else:
isOn = True
return isOn
def getLastShow(self):
sql = "SELECT program.endtime FROM program ORDER BY endtime DESC LIMIT 1"
## sql = "SELECT max(endtime) FROM program"
debug( "sql=[%s]"%sql )
if not self.conn.executeSQL( sql ):
raise ServerException, \
"Error retrieving last show time: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
debug ("LAST SHOW ROW: %s" % str(row) )
endtime = row["program.endtime"]
if str(endtime[4:5]) == "-":
return datetime.datetime(int(endtime[0:4]), int(endtime[5:7]), int(endtime[8:10]), int(endtime[11:13]), int(endtime[14:16]) )
else:
return datetime.datetime(int(endtime[0:4]), int(endtime[4:6]), int(endtime[6:8]), int(endtime[8:10]), int(endtime[10:12]) )
return None
def getRecordingGroups(self):
sql = "SELECT DISTINCT recorded.recgroup, count(recorded.recgroup) as cnt FROM recorded GROUP BY recorded.recgroup ASC"
debug( "sql=[%s]"%sql )
if not self.conn.executeSQL( sql ):
debug("Error retrieving recording groups: " + self.conn.getErrorMsg())
raise ServerException, \
"Error retrieving recording groups: " + self.conn.getErrorMsg()
recgroups = []
rowIter = self.conn.dictRowIterator()
recgroups.append(["All Groups",0])
grpcnt = 0
for row in rowIter:
thisRow = ["",0]
for k in row.keys():
if k.find("cnt") >= 0:
grpcnt += int(row[k])
thisRow[1] = int(row[k])
else:
thisRow[0] = str(row[k])
recgroups.append(thisRow)
recgroups[0][1] = grpcnt
return recgroups
def getRecordingTitles(self, recgroup):
sql = "SELECT DISTINCT recorded.title, COUNT(recorded.title) AS cnt FROM recorded"
if string.upper(recgroup) != "ALL GROUPS":
sql += " WHERE recorded.recgroup=\"%s\"" % str(recgroup)
sql += " GROUP BY recorded.title ASC"
debug( "sql=[%s]"%sql )
if not self.conn.executeSQL( sql ):
raise ServerException, \
"Error retrieving recording titles: " + self.conn.getErrorMsg()
titlegroups = []
rowIter = self.conn.dictRowIterator()
titlegroups.append(["All Shows",0])
grpcnt = 0
for row in rowIter:
thisRow = ["",0]
for k in row.keys():
if k.find("cnt") >= 0:
grpcnt += int(row[k])
thisRow[1] = int(row[k])
else:
thisRow[0] = str(row[k])
titlegroups.append(thisRow)
titlegroups[0][1] = grpcnt
return titlegroups
def programInfo(self, startTime, endTime, channelID):
try:
strStartTime = startTime.strftime( "%Y%m%d%H%M%S" )
strEndTime = endTime.strftime( "%Y%m%d%H%M%S" )
sql = """
select
channel.chanid,
channel.channum,
channel.callsign,
program.starttime,
program.endtime,
program.title,
program.subtitle,
program.description,
program.showtype,
program.originalairdate,
program.category,
0 as isduplicate
from
channel,
program
where
channel.visible = 1
and channel.channum = '%s'
and program.chanid = channel.chanid
and program.starttime != program.endtime
and
(
(
program.endtime > %s
and program.endtime <= %s
) or
(
program.starttime >= %s
and program.starttime < %s
) or
(
program.starttime < %s
and program.endtime > %s
) or
(
program.starttime = %s
and program.endtime = %s
)
)
"""%( str(channelID),
strStartTime,strEndTime,
strStartTime,strEndTime,
strStartTime,strEndTime,
strStartTime,strEndTime )
sql += " order by channel.chanid"
debug(sql)
if not self.conn.executeSQL( sql ):
raise ServerException, \
"Error retrieving program info: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
for row in rowIter:
return row
except Exception, ex:
traceback.print_exc()
def getRecordListings(
self,
startTime, endTime,
startChanId=None, endChanId=None ):
debug( '> mythtv.getRecordListings' )
strStartTime = startTime.strftime( "%Y%m%d%H%M%S" )
strEndTime = endTime.strftime( "%Y%m%d%H%M%S" )
sql = """
select
channel.chanid,
channel.channum,
channel.callsign,
program.starttime,
program.endtime,
program.title,
program.subtitle,
program.description,
program.showtype,
program.originalairdate,
program.category,
record.startoffset,
record.endoffset,
0 as isduplicate
from
channel,
program,
recordmatch,
record
where
channel.visible = 1
and channel.chanid = program.chanid
and recordmatch.chanid = channel.chanid
and program.starttime != program.endtime
and
(
(
program.endtime > %s
and program.endtime <= %s
) or
(
program.starttime >= %s
and program.starttime < %s
) or
(
program.starttime < %s
and program.endtime > %s
) or
(
program.starttime = %s
and program.endtime = %s
)
)
and recordmatch.starttime = program.starttime
and record.recordid = recordmatch.recordid
and record.type != 0
""" % ( strStartTime,strEndTime,
strStartTime,strEndTime,
strStartTime,strEndTime,
strStartTime,strEndTime )
if startChanId and endChanId:
sql += """
and channel.chanid >= '%d'
and channel.chanid <= '%d'
"""%(startChanId, endChanId)
#sql += " order by channel.chanid, program.starttime"
debug( "sql=[%s]"%sql )
if not self.conn.executeSQL( sql ):
raise ServerException, \
"Error retrieving recording listings: " + self.conn.getErrorMsg()
recprograms = []
rowIter = self.conn.dictRowIterator()
for row in rowIter:
recprogram = mythtvstruct.ProgramFromQuery( stripSQLTablePrefix(
row ) )
recprograms.append(recprogram)
debug( '< mythtv.getRecordListings' )
return recprograms
def deleteProgram( self, recordId ):
"""
Deletes the program created when a manual schedule is created
"""
debug( "> deletePrograms '%d'"%recordId )
sql = """
DELETE FROM program
WHERE manualid=%d
"""%recordId
debug(sql)
if not self.conn.executeSQL( sql ):
debug("Error deleting manual program %d: %s"%(recordId, self.conn.getErrorMsg()))
return 0
else:
return 1
def deleteSchedule( self, schedule ):
"""
Deletes the specified recording schedule. Returns 1 on success.
Raises ServerException on error.
"""
recordId = int(schedule.recordid())
debug( "> deleteSchedule '%d'"%recordId )
sql = """
DELETE FROM record
WHERE recordid = %d
"""%recordId
debug(sql)
if not self.conn.executeSQL( sql ):
debug("Error deleting recording schedule %d: %s"%(recordId, self.conn.getErrorMsg()))
return 0
else:
return 1
def getChannelList( self ):
"""
Returns a list of channels the backend knows about
"""
debug( '> mythtv.getChannelList' )
sql = """
SELECT
c.chanid as chanid,
c.channum as channum,
c.callsign as callsign,
c.name as name,
c.icon as icon
FROM
channel c
WHERE
c.channum IS NOT NULL
AND c.channum != ""
AND c.visible = 1
ORDER BY
c.chanid
"""
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error retrieving channels: " + self.conn.getErrorMsg()
# retrieve all recorded shows and store them in an array
rowIter = self.conn.dictRowIterator()
channelInfo = []
for row in rowIter:
channelInfo.append(
mythtvstruct.ChannelFromQuery( stripSQLTablePrefix(row) ) )
debug( '< mythtv.getChannelList' )
return channelInfo
def getCommMarkup( self, chanid, starttime ):
"""
Retrieves the commercial markup info for the specified recorded show.
"""
debug("> getCommMarkup (%s, %s)"%(str(chanid),str(starttime)))
sql = """
SELECT
mark,
type
FROM
recordedmarkup
WHERE
chanid = %s
and starttime = %s
and type in (4, 5) -- 4=comm start, 5=comm stop
ORDER BY
mark
"""%(chanid, starttime)
debug(" SQL: [%s]"%sql)
if not self.conn.executeSQL( sql ):
raise Exception, \
"Error retrieving cutlist: " + self.conn.getErrorMsg()
rowIter = self.conn.dictRowIterator()
markup = []
for row in rowIter:
markup.append( stripSQLTablePrefix( row ) )
debug("< getCommMarkup [%s]"%str(markup))
return markup
def getProgramListings(
self,
startTime, endTime,
startChanId=None, endChanId=None ):
debug( '> mythtv.getProgramListings' )
strStartTime = startTime.strftime( "%Y%m%d%H%M%S" )
strEndTime = endTime.strftime( "%Y%m%d%H%M%S" )
sql = """
select
channel.chanid,
channel.channum,
channel.callsign,
program.starttime,
program.endtime,
program.title,
program.subtitle,
program.description,
program.showtype,
program.originalairdate,
program.category,
program.seriesid,
program.programid,
channel.icon,
channel.name as channame,
0 as isduplicate
from
channel,
program
where
channel.visible = 1
and channel.chanid = program.chanid
and program.starttime != program.endtime
and
(
(
program.endtime > %s
and program.endtime <= %s
) or
(
program.starttime >= %s
and program.starttime < %s
) or
(
program.starttime < %s
and program.endtime > %s
) or
(
program.starttime = %s
and program.endtime = %s
)
)
""" % ( strStartTime,strEndTime,
strStartTime,strEndTime,
strStartTime,strEndTime,
strStartTime,strEndTime )
if startChanId and endChanId:
sql += """
and channel.chanid >= '%d'
and channel.chanid <= '%d'
"""%(startChanId, endChanId)
sql += " order by channel.chanid, program.starttime"
# debug( "sql=[%s]"%sql )
if not self.conn.executeSQL( sql ):
raise ServerException, \
"Error retrieving program listing: " + self.conn.getErrorMsg()
programs = []
rowIter = self.conn.dictRowIterator()
for row in rowIter:
program = mythtvstruct.ProgramFromQuery( stripSQLTablePrefix(
row ) )
programs.append( program )
debug( '< mythtv.getProgramListings' )
return programs
def getSchedule( self, chanId="",recordId=-1 ):
"""
Returns an array of recording schedules. If recordId is specified,
only the matching schedule is returned. Otherwise, all schedules are
returned.
"""
debug( "> getSchedule (%s)"%(str(recordId)) )
sql = """
SELECT
r.recordid,
r.type,
r.chanid,
r.starttime,
r.startdate,
r.endtime,
r.enddate,
r.title,
r.subtitle,
r.description,
r.category,
r.profile,
r.recpriority,
r.autoexpire,
r.maxepisodes,
r.maxnewest,
r.startoffset,
r.endoffset,
r.recgroup,
r.dupmethod,
r.dupin,
r.station,
r.seriesid,
r.programid,
r.search,
r.autotranscode,
r.autocommflag,
r.autouserjob1,
r.autouserjob2,
r.autouserjob3,
r.autouserjob4,
r.findday,
r.findtime,
r.findid,
r.inactive,
r.parentid,
c.channum,
c.callsign,
c.name as channame,
c.icon
FROM
record r
LEFT JOIN channel c ON r.chanid = c.chanid
"""
if chanId != "":
sql += "WHERE r.chanid = '%s' "%chanId
if recordId != -1:
if chanId == "":
sql+="WHERE "
else:
sql +="AND "
sql += "r.recordid = %d "%recordId
sql += """
ORDER BY
r.recordid
DESC
"""
debug(sql)
if not self.conn.executeSQL( sql ):
raise ServerException, \
"Error retrieving recording schedule: " + \
self.conn.getErrorMsg()
schedules = []
rowIter = self.conn.dictRowIterator()
for row in rowIter:
sched = mythtvstruct.ScheduleFromQuery(
stripSQLTablePrefix( row ) )
schedules.append(sched)
debug( "< getSchedule" )
return schedules
def getCurrentSchedule( self, chanid, endtime=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) ):
"""
Returns an array of recording schedules for the chanid and time.
"""
debug( "> getCurrentSchedule (%s, %s)"%(chanid, endtime) )
sql = """
SELECT
r.starttime,
r.endtime,
r.title,
r.subtitle,
r.description,
r.category,
r.chanid,
r.hostname,
r.basename,
c.channum,
r.originalairdate,
c.callsign,
r.seriesid,
r.programid,
c.icon,
c.name as channame,
r.duplicate,
r.recordid,
m.manualid
FROM
recorded r
LEFT JOIN channel c ON r.chanid = c.chanid
LEFT JOIN recordmatch m ON r.recordid = m.recordid
INNER JOIN record s ON s.recordid = r.recordid
"""
curtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
curdate = time.strftime("%Y-%m-%d", time.localtime())
# first just get all schedules that started before now
sql += "WHERE "
if chanid != None:
sql += "c.channum='%s' AND "%chanid
sql += "r.starttime<='%s' AND r.endtime>='%s'"%(endtime,curtime)
sql += """
ORDER BY
m.manualid
"""
debug(sql)
if not self.conn.executeSQL( sql ):
raise ServerException, \
"Error retrieving current schedule: " + \
self.conn.getErrorMsg()
schedules = []
rowIter = self.conn.dictRowIterator()
for row in rowIter:
sched = mythtvstruct.ProgramFromQuery(
stripSQLTablePrefix( row ) )
schedules.append(sched)
debug( "< getCurrentSchedule" )
return schedules
def notifyChange( self ):
"""
Method to notify the mythbackend of a change to a record.
This is how mythweb notifies the backend after changes to schedules,
deleting a recorded program, etc..
"""
debug( "> mythtv.Database.notifyChange()" )
rc = self.conn.executeSQL("""
update settings set data='yes' where value='RecordChanged'
""" )
if not rc:
raise ClientException, self.conn.getErrorMsg()
return 0
debug( "< mythtv.Database.notifyChange()" )
def saveSchedule( self, s ):
"""
Method to save a schedule to the database. If recordid() is None in
passed schedule, then it will be populated with id returned from
database (i.e. a new one will be created).
Returns:
0 on success
ClientException on error
Note:
Connection.rescheduleNotify() must be called after scheduling changes
have been made so that the backend will apply the changes.
"""
programid = s.programid()
if not programid:
programid = ""
seriesid = s.seriesid()
if not seriesid:
seriesid = ""
sql = """
REPLACE INTO
record
( recordid, type,
chanid, starttime,
startdate, endtime,
enddate, title,
subtitle, description,
category, profile,
recpriority, autoexpire,
maxepisodes, maxnewest,
startoffset, endoffset,
recgroup, dupmethod,
dupin, station,
seriesid, programid,
search, autotranscode,
autocommflag, autouserjob1,
autouserjob2, autouserjob3,
autouserjob4, findday,
findtime, findid,
inactive, parentid
) VALUES (
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s,
%s, %s
)
""" % (
mysql.escape( s.recordid() ), mysql.escape( s.type() ),
mysql.escape( s.chanid() ), mysql.escape( s.starttime() ),
mysql.escape( s.startdate() ), mysql.escape( s.endtime() ),
#
# NOTE:
#
# s.title(), s.subtitle(), and s.description() were not
# used because they return the data in GUI encoding whereas
# the database is in UTF-8. This may need to be rethought to
# make things cleaner but for now this was the easiest. TODO
#
mysql.escape( s.enddate() ), mysql.escape( s.data()['title'] ),
mysql.escape( s.data()['subtitle'] ),
mysql.escape( s.data()['description'] ),
mysql.escape( s.category() ), mysql.escape( s.profile() ),
mysql.escape( s.recpriority() ), mysql.escape( s.autoexpire() ),
mysql.escape( s.maxepisodes() ), mysql.escape( s.maxnewest() ),
mysql.escape( s.startoffset() ), mysql.escape( s.endoffset() ),
mysql.escape( s.recgroup() ), mysql.escape( s.dupmethod() ),
mysql.escape( s.dupin() ), mysql.escape( s.station() ),
mysql.escape( seriesid ), mysql.escape( programid ),
mysql.escape( s.search() ), mysql.escape( s.autotranscode() ),
mysql.escape( s.autocommflag() ), mysql.escape( s.autouserjob1() ),
mysql.escape( s.autouserjob2() ), mysql.escape( s.autouserjob3()),
mysql.escape( s.autouserjob4() ), mysql.escape( s.findday() ),
mysql.escape( s.findtime() ), mysql.escape( s.findid() ),
mysql.escape( s.inactive() ), mysql.escape( s.parentid() ) )
debug( "sql=[%s]"%sql )
rc = self.conn.executeSQL( sql )
if rc < 1:
raise ClientException, self.conn.getErrorMsg()
if not s.recordid():
s.data()['recordid'] = self.conn.getInsertId()
return s.recordid()
def setRecordedAutoexpire( self, chanid, starttime, shouldExpire = 0 ):
"""
Method to change the autoexpire setting for a recorded program.
Note:
notifyChange() must be called afterwards to signal the backend
to make the change. (only needed for protocol < 14)
"""
debug( "> mythtv.Database.setRecordedAutoexpire( " + \
"chanid=[%s], starttime=[%s], shouldExpire=[%d] )" )
sql = """
update recorded set
autoexpire = "%d",
starttime = '%s'
where
chanid = '%s'
and starttime = '%s'
"""%(shouldExpire, starttime, chanid, starttime)
debug( "sql=[%s]"%sql )
rc = self.conn.executeSQL( sql )
if rc != 1:
raise ClientException, self.conn.getErrorMsg()
debug( "< mythtv.Database.setRecordedAutoexpire()" )
def testActive( self ):
active = 0
try:
self.conn.executeSQL( "select sysdate()" )
active = 1
except:
self.isConnected = 0
del self.conn
self.conn = None
return active
class NMBService( nmb.NetBIOS ):
def __init__( self ):
if not "isInitialized" in self.__dict__:
debug( "initializing NetBIOS" )
self.isInitialized = 1
nmb.NetBIOS.__init__( self )
def gethostipbyname( self, host ):
ip = host
if not sre.match('^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$', host):
hostEnt = self.gethostbyname( host )
ip = hostEnt[0].get_ip()
return ip
class Settings( object ):
settingsPath = getcwd() + os.sep + "settings.xml"
settingsTags = [\
'mythtv_host','mythtv_port','mythtv_statusport','mythtv_protocol',\
'mythtv_minlivebufsize', 'mythtv_tunewait','mythtv_startpage',\
'mythtv_liveplayer', 'mythtv_recplayer','mythtv_recordlive','mythtv_recordlength',\
'mysql_host', 'mysql_port','mysql_database','mysql_user',\
'mysql_password', 'mysql_encoding_override',\
'paths_recordedprefix','paths_livetvprefix','paths_localcopypath',\
'recorded_view_by', 'upcoming_view_by']
def __init__( self ):
debug( "> mythtv.Settings [%d]"%id(self) )
try:
self.loadSettings()
except SettingsException, ex:
self.dom = self.loadDefaults()
debug( "< mythtv.Settings [%d]"%id(self) )
## Get the size of the cache from the XBMC settings.xml file
def getXBMCCache( self ):
debug( "> XBMC Lan Cache")
userData="Q:\\UserData\\guisettings.xml"
filePath="E:\\TDATA\\0face008\\settings.xml"
if os.path.exists( userData ):
xbmcdom = minidom.parse( userData )
## Get the settings node
xbmcSettings = xbmcdom.getElementsByTagName("settings")[0]
debug( "settings tag ok" )
## Get the Cache Node
xbmcCache = xbmcSettings.getElementsByTagName("cachevideo")[0]
debug( "cache video ok" )
## Get the LAN Node
xbmcLan = xbmcCache.getElementsByTagName("lan")[0]
debug( "lan tag ok" )
else:
xbmcdom = minidom.parse( filePath )
## Get the settings node
xbmcSettings = xbmcdom.getElementsByTagName("settings")[0]
debug( "settings tag ok" )
## Get the Cache Node
xbmcCache = xbmcSettings.getElementsByTagName("CacheVideo")[0]
debug( "cache video ok" )
## Get the LAN Node
xbmcLan = xbmcCache.getElementsByTagName("LAN")[0]
debug( "lan tag ok" )
value = ""
for n in xbmcLan.childNodes:
value += n.nodeValue
debug( "< XBMC Lan Cache => [%s]"%(value) )
return value
def getSetting( self, tag, dom=None ):
debug( "> mythtv.Settings.getSetting( tag=[%s] )"%tag )
value = ""
if not dom:
dom = self.dom
tmpNode = dom.getElementsByTagName( tag )[0]
for n in tmpNode.childNodes:
value += n.nodeValue
debug( "< mythtv.Settings.getSetting( tag=[%s] ) => [%s]"%(tag,value) )
return value
def loadDefaults( self ):
debug( "> mythtv.Settings.loadDefaults()" )
# default configuration when it doesn't exist
dom = minidom.parseString( \
"""
<mythtv>
<mythtv_host></mythtv_host>
<mythtv_port>6543</mythtv_port>
<mythtv_statusport>6544</mythtv_statusport>
<mythtv_protocol>30</mythtv_protocol>
<mythtv_minlivebufsize>524288</mythtv_minlivebufsize>
<mythtv_tunewait>3</mythtv_tunewait>
<mythtv_startpage>0</mythtv_startpage>
<mythtv_liveplayer>0</mythtv_liveplayer>
<mythtv_recplayer>0</mythtv_recplayer>
<mythtv_recordlive>1</mythtv_recordlive>
<mythtv_recordlength>120</mythtv_recordlength>
<mysql_host></mysql_host>
<mysql_port>3306</mysql_port>
<mysql_database>mythconverg</mysql_database>
<mysql_user>mythtv</mysql_user>
<mysql_password>mythtv</mysql_password>
<mysql_encoding_override>latin1</mysql_encoding_override>
<paths_recordedprefix>smb://%h/mythtv</paths_recordedprefix>
<paths_livetvprefix>smb://%h/mythtv</paths_livetvprefix>
<paths_localcopypath>e:\\videos\\</paths_localcopypath>
<recorded_view_by>2</recorded_view_by>
<upcoming_view_by>2</upcoming_view_by>
</mythtv>
""" )
debug( "< mythtv.Settings.loadDefaults()" )
return dom
def loadMergedDefaults( self, filePath=settingsPath ):
dom = self.loadDefaults()
if os.path.exists( filePath ):
for tag in self.settingsTags:
try:
value = self.getSetting( tag )
except IndexError, ie:
value = ""
pass
if len( value ) == 0:
self.setSetting( \
tag, self.getSetting( tag, dom ), shouldCreate=1 )
def loadSettings( self, filePath=settingsPath ):
debug( "> mythtv.Settings.loadSettings( filePath=[%s] )"%filePath )
if not os.path.exists( filePath ):
raise SettingsException, mythtvutil.getLocalizedString( 35 )
else:
# use existing configuration
self.dom = minidom.parse( filePath )
debug( "< mythtv.Settings.loadSettings()" )
def saveSettings( self, filePath=settingsPath ):
debug( "> mythtv.Settings.saveSettings( filePath=[%s] )"%filePath )
if self.dom:
debug( "opening file [%s] for writing"%filePath )
fh = file( filePath, 'w' )
fh.write( self.dom.toxml() )
fh.close()
debug( "< mythtv.Settings.saveSettings( filePath=[%s] )"%filePath )
def setSetting( self, tag, value, shouldCreate=0, dom=None ):
debug( \
"> mythtv.Settings.setSetting( tag=[%s], value[%s], shouldCreate=%d)"%\
(tag,value,shouldCreate) )
tmpNode = None
if not dom:
dom = self.dom
try:
tmpNode = dom.getElementsByTagName( tag )[0]
except IndexError, ie:
if shouldCreate != 1:
raise
if not tmpNode:
tmpNode = dom.getElementsByTagName( "mythtv" )[0]
n = dom.createElement( tag )
tmpNode.appendChild( n )
tmpNode = n
if not tmpNode.firstChild:
n = dom.createTextNode( value )
tmpNode.appendChild( n )
else:
tmpNode.firstChild.nodeValue = value
debug( \
"< mythtv.Settings.setSetting( tag=[%s], value[%s], shouldCreate=%d)"% \
(tag,value,shouldCreate) )
def verifySettings( self ):
debug( "> mythtv.Settings.verifySettings()" )
for tag in self.settingsTags:
try:
self.getSetting( tag )
except IndexError, ex:
raise SettingsException, mythtvutil.getLocalizedString( 34 )%tag
debug( "< mythtv.Settings.verifySettings()" )
if __name__ == "__main__":
import unittest
import xbmcgui
mythtvutil.debugSetLevel(
mythtvutil.DEBUG_MISC | mythtvutil.DEBUG_GUI | mythtvutil.DEBUG_MYTH )
#mythtvutil.DEBUG_ALL )
mythtvutil.initialize()
class PublicInterfaceTest( unittest.TestCase ):
def testSettingsReturnsMythTVHost( self ):
s = getInstance( Settings )
h = s.getSetting( "mythtv_host" )
self.failIf( len( h ) == 0 )
def testBackendConnection( self ):
try:
getInstance( Connection )
# uncomment to test reconnect logic
#print "got connection - restart mythbackend now"
#time.sleep( 20 )
getInstance( Connection ).close()
except Exception, ex:
traceback.print_exc( ex )
raise
def testDatabaseConnection( self ):
try:
getInstance( Database )
# uncomment to test reconnect logic
#print "got connection - restart database now"
#time.sleep( 20 )
getInstance( Database ).close()
except Exception, ex:
traceback.print_exc( ex )
raise
def testGetChannels( self ):
try:
db = getInstance( Database )
channels = db.getChannelList()
for c in channels:
debug( c.chanid() )
except Exception, ex:
traceback.print_exc( ex )
raise
def testGetSchedule( self ):
try:
db = getInstance( Database )
schedules = db.getSchedule()
for s in schedules:
debug(s)
except Exception, ex:
traceback.print_exc( ex )
raise
def testSaveSchedule( self ):
db = getInstance( Database )
s = mythtvstruct.ScheduleFromQuery( dict(
{ 'recordid' : 9999, 'type' : 3,
'chanid' : 1002, 'starttime' : '03:00:00',
'startdate' : '20050712', 'endtime' : '04:00:00',
'enddate' : '20050712', 'title' : 'Tom\'s test',
'subtitle' : 'Wass\'up', 'description' : 'This is a test.',
'category' : 'Sitcom', 'profile' : 'Default',
'recpriority' : 5, 'autoexpire' : 1,
'maxepisodes' : 3, 'maxnewest' : 0,
'startoffset' : 0, 'endoffset' : 0,
'recgroup' : 'Default', 'dupmethod' : 6,
'dupin' : 15, 'station' : 'CFRN',
'seriesid' : 'SH999999', 'programid' : 'EP9999999999',
'search' : 0, 'autotranscode' : 0,
'autocommflag' : 1, 'autouserjob1' : 0,
'autouserjob2' : 0, 'autouserjob3' : 0,
'autouserjob4' : 0, 'findday' : '0',
'findtime' : '0', 'findid' : 0,
'inactive' : 0, 'parentid' : 0
} ) )
db.saveSchedule( s )
debug( s )
def testStatusRetrieval( self ):
s = getInstance( Status )
s.refresh()
encoderStatus = s.encoderStatus()
debug( "encoderStatus=[%s]"%encoderStatus )
self.failIf( len( encoderStatus ) == 0 )
debug ( s.diskSpaceFree() )
debug ( s.diskTotalSpace() )
debug ( s.diskSpaceUsed() )
debug ( s.loadAvg1Min() )
debug ( s.loadAvg5Min() )
debug ( s.loadAvg15Min() )
debug ( s.mythFillDatabase() )
debug ( s.guideData() )
debug ( s.subscription() )
debug ( s.schedule() )
#def testMythFileTransfer( self ):
# remotePath = \
# "myth://banshee.corenet.core:6543/1006_20050203220000_20050203230000.nuv.png"
# localPath = "./test.png"
# rc = getInstance( Connection ).transferFile( remotePath, localPath )
# self.failIf( rc == -1 )
xbmcgui.Dialog().ok( "Info", "You might want to run mythtvmain.py instead." )
unittest.main()
|