mythtv.py :  » Game-2D-3D » XBMC-MythTV » xbmcmythtv » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Game 2D 3D » XBMC MythTV 
XBMC MythTV » xbmcmythtv » mythtv.py
"""
$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()

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.