All pastes #473514 Raw Edit

Someone

public text v1 · immutable
·published 2007-05-06 09:19 UTC
rendered paste body
freevo recordserver start

Traceback (most recent call last):
  File "/var/lib/python-support/python2.5/freevo/helpers/recordserver.py", line 51, in <module>
    from twisted.internet.app import Application
ImportError: No module named app

recordserver.py
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# record_server.py - A network aware TV recording server.
# -----------------------------------------------------------------------
# $Id: recordserver.py 9401 2007-03-27 20:14:37Z duncan $
#
# -----------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2002 Krister Lagerstrom, et al.
# Please see the file freevo/Docs/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------


import sys, string, random, time, os, re, pwd, stat, threading, pickle, md5, datetime, copy
import config
from util import vfs

appname = os.path.splitext(os.path.basename(sys.argv[0]))[0]
appconf = appname.upper()

# change uid
if __name__ == '__main__':
    uid='config.'+appconf+'_UID'
    gid='config.'+appconf+'_GID'
    try:
        if eval(uid) and os.getuid() == 0:
            os.setgid(eval(gid))
            os.setuid(eval(uid))
            os.environ['USER'] = pwd.getpwuid(os.getuid())[0]
            os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
    except Exception, e:
        print e

from twisted.web import xmlrpc, server
from twisted.internet.app import Application
from twisted.internet import reactor
from twisted.python import log
from util.marmalade import jellyToXML, unjellyFromXML
from video.commdetectclient import initCommDetectJob
from video.commdetectclient import queueIt
from video.commdetectclient import listJobs
from video.commdetectclient import connectionTest

import rc
rc_object = rc.get_singleton(use_pylirc=0, use_netremote=0)

from tv.record_types import TYPES_VERSION
from tv.record_types import ScheduledRecordings

import tv.record_types
import tv.epg_xmltv
import util.tv_util as tv_util
import plugin
import util.popen3
from tv.channels import FreevoChannels
from util.videothumb import snapshot
from event import *


DEBUG = hasattr(config, appconf+'_DEBUG') and eval('config.'+appconf+'_DEBUG') or config.DEBUG

logfile = '%s/%s-%s.log' % (config.LOGDIR, appname, os.getuid())
log.startLogging(open(logfile, 'a'))

def _debug_(text, level=1):
    if DEBUG >= level:
        try:
            log.debug(String(text))
        except:
            print String(text)

_debug_('PLUGIN_RECORD: %s' % config.plugin_record)

plugin.init_special_plugin(config.plugin_record)

def print_plugin_warning():
    print '*************************************************'
    print '**  Warning: No recording plugin registered.  **'
    print '**           Check your local_conf.py for a   **'
    print '**           bad "plugin_record =" line or    **'
    print '**           this log for a plugin failure.   **'
    print '**           Recordings will fail!            **'
    print '*************************************************'


if not plugin.getbyname('RECORD'):
    print_plugin_warning()


class RecordServer(xmlrpc.XMLRPC):

    def __init__(self):
        self.lock = threading.Lock()
        self.fc = FreevoChannels()
        # XXX: In the future we should have one lock per VideoGroup.
        self.tv_lock_file = None
        self.vg = None
        self.previouslyRecordedShows = None


    def isRecording(self):
        _debug_('in isRecording', 3)
        return glob.glob(config.FREEVO_CACHEDIR + '/record.*') and TRUE or FALSE


    def progsTimeCompare(self, first, second):
        t1 = first.split(':')[-1]
        t2 = second.split(':')[-1]
        try:
            return int(float(t1)) - int(float(t2))
        except ArithmeticError:
            pass
        return 0


    def findOverlaps(self, scheduledRecordings):
        _debug_('in findOverlaps', 3)
        progs = scheduledRecordings.getProgramList()
        proglist = list(progs)
        proglist.sort(self.progsTimeCompare)
        for progitem in proglist:
            progs[progitem].overlap = 0
        for i in range(0, len(proglist)-1):
            thisprog = progs[proglist[i]]
            nextprog = progs[proglist[i+1]]
            if thisprog.stop > nextprog.start:
                thisprog.overlap = 1
                nextprog.overlap = 1
                _debug_('Overlap:\n%s\n%s' % (thisprog, nextprog), 3)


    def findNextProgram(self):
        _debug_('in findNextProgram', 3)

        next_program = None
        progs = self.getScheduledRecordings().getProgramList()
        proglist = list(progs)
        proglist.sort(self.progsTimeCompare)
        now = time.time()
        timenow = time.localtime(now)
        for progitem in proglist:
            prog = progs[progitem]
            _debug_('%s' % (prog), 2)

            try:
                recording = prog.isRecording
            except:
                recording = FALSE
            _debug_('%s: recording=%s' % (prog.title, recording), 2)

            endtime = time.localtime(prog.stop+config.TV_RECORD_PADDING_POST)
            if now > prog.stop + config.TV_RECORD_PADDING_POST:
                _debug_('%s: prog.stop=%s, now=%s' % (prog.title, endtime, timenow), 2)
                continue
            _debug_('%s: endtime=%s' % (prog.title, endtime), 2)

            if not recording:
                next_program = prog
                break

        self.next_program = next_program
        if next_program == None:
            _debug_('No program scheduled to record', 3)
            return None

        _debug_('%s' % (next_program), 3)
        return next_program


    def isPlayerRunning(self):
        '''
        returns the state of a player, mplayer, xine, etc.
        TODO:
            real player running test, check /dev/videoX.
            this could go into the upsoon client
        '''
        _debug_('in isPlayerRunning', 3)
        return (os.path.exists(config.FREEVO_CACHEDIR + '/playing'))


    # note: add locking and r/rw options to get/save funs
    def getScheduledRecordings(self):
        file_ver = None
        scheduledRecordings = None

        if os.path.isfile(config.TV_RECORD_SCHEDULE):
            _debug_('GET: reading cached file (%s)' % config.TV_RECORD_SCHEDULE, 5)
            if hasattr(self, 'scheduledRecordings_cache'):
                mod_time, scheduledRecordings = self.scheduledRecordings_cache
                try:
                    if os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME] == mod_time:
                        _debug_('Return cached data', 5)
                        return scheduledRecordings
                except OSError:
                    pass

            f = open(config.TV_RECORD_SCHEDULE, 'r')
            scheduledRecordings = unjellyFromXML(f)
            f.close()

            try:
                file_ver = scheduledRecordings.TYPES_VERSION
            except AttributeError:
                _debug_('The cache does not have a version and must be recreated.', 1)

            if file_ver != TYPES_VERSION:
                _debug_(('ScheduledRecordings version number %s is stale (new is %s), must ' +
                        'be reloaded') % (file_ver, TYPES_VERSION), 1)
                scheduledRecordings = None
            else:
                _debug_('Got ScheduledRecordings (version %s).' % file_ver, 5)

        if not scheduledRecordings:
            _debug_('GET: making a new ScheduledRecordings', 2)
            scheduledRecordings = ScheduledRecordings()
            self.saveScheduledRecordings(scheduledRecordings)

        _debug_('ScheduledRecordings has %s items.' % \
                len(scheduledRecordings.programList), 1)

        try:
            mod_time = os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME]
            self.scheduledRecordings_cache = mod_time, scheduledRecordings
        except OSError:
            pass
        return scheduledRecordings


    #
    # function to save the schedule to disk
    #
    def saveScheduledRecordings(self, scheduledRecordings=None):

        if not scheduledRecordings:
            _debug_('SAVE: making a new ScheduledRecordings', 2)
            scheduledRecordings = ScheduledRecordings()

        self.findOverlaps(scheduledRecordings)
        _debug_('SAVE: saving cached file (%s)' % config.TV_RECORD_SCHEDULE, 2)
        _debug_("SAVE: ScheduledRecordings has %s items." % len(scheduledRecordings.programList), 2)
        try:
            f = open(config.TV_RECORD_SCHEDULE, 'w')
        except IOError:
            os.unlink(config.TV_RECORD_SCHEDULE)
            f = open(config.TV_RECORD_SCHEDULE, 'w')

        jellyToXML(scheduledRecordings, f)
        f.close()

        try:
            mod_time = os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME]
            self.scheduledRecordings_cache = mod_time, scheduledRecordings
        except OSError:
            pass

        return TRUE

    #
    # Load the saved set of recorded shows
    #
    def loadPreviouslyRecordedShows(self):
        if self.previouslyRecordedShows:
            return

        cacheFile = config.FREEVO_CACHEDIR + "/previouslyRecorded.pickle"
        try:
            self.previouslyRecordedShows = pickle.load(open(cacheFile, "r"))
        except IOError:
            self.previouslyRecordedShows = {}
            pass

    #
    # Save the set of recorded shows
    #
    def savePreviouslyRecordedShows(self):
        if not self.previouslyRecordedShows:
            return

        cacheFile=config.FREEVO_CACHEDIR+"/previouslyRecorded.pickle"
        pickle.dump(self.previouslyRecordedShows, open(cacheFile,"w"))

    #
    # Return true if this is a new episode of 'prog'
    #
    def newEpisode(self, prog=None):
        todayStr = datetime.date.today().strftime('%Y%m%d')
        progStr = str(prog.date)
        _debug_('Program Date: "%s"' % progStr, 5)
        _debug_('Todays Date : "%s"' % todayStr, 5)
        if (len(progStr)==8):
            _debug_('Good date format', 5)
            #Year
            todaysYear=(todayStr[0:4])
            progYear=(progStr[0:4])
            #Month
            todaysMonth=(todayStr[4:2])
            progMonth=(progStr[4:2])
            #Day
            todaysDay=(todayStr[6:2])
            progDay=(progStr[6:2])
            if todaysYear > progYear:
                #program from a previous year
                return FALSE
            elif progYear > todaysYear:
                #program in the future
                return TRUE
            else:
                _debug_('Same year', 5)
                #program in the same year
                if todaysMonth > progMonth:
                    #program in a previous month
                    return FALSE
                elif progMonth > todaysMonth:
                    #program in the future
                    return TRUE
                else:
                    _debug_('Same month', 5)
                    #program in the same month
                    if todaysDay > progDay:
                        #program was previous aired this month
                        return FALSE
                    else:
                        _debug_('Same day or in the upcoming month', 5)
                        #program is today or in the upcoming days
                        return TRUE
        else:
            _debug_('No good date format, assuming new Episode to be on the safe side', 3)
            return TRUE

    #
    # Shrink a string by removing all spaces and making it
    # lower case and then returning the MD5 digest of it.
    #
    def shrink(self, text):
        if text:
            text = md5.new(text.lower().replace(' ', '')).hexdigest()
        else:
            text = ''
        return text


    def getPreviousRecordingKey(self, prog):
        '''Return the key to be used for a given prog in the
        previouslyRecordedShows hashtable.'''
        shrunkTitle = self.shrink(prog.title)
        shrunkSub   = self.shrink(prog.sub_title)
        shrunkDesc  = self.shrink(prog.desc);
        return ('%s-%s-%s' % (shrunkTitle, shrunkSub, shrunkDesc), \
                '%s-%s-'   % (shrunkTitle, shrunkSub),              \
                '%s--%s'   % (shrunkTitle, shrunkDesc))


    def getPreviousRecording(self, prog):
        '''Get a previous recording, or None if none.'''
        try:
            return self.previouslyRecordedShows[self.getPreviousRecordingKey(prog)]
        except KeyError:
            return None


    def removeDuplicate(self, prog=None):
        '''Remove a duplicate recording'''
        self.loadPreviouslyRecordedShows()
        previous = self.getPreviousRecording(prog)
        if previous:
            _debug_('Found duplicate, removing', 5)
            del self.previouslyRecordedShows[self.getPreviousRecordingKey(previous)]
            self.savePreviouslyRecordedShows()


    def addDuplicate(self, prog=None):
        '''Add program to duplicates hash'''
        _debug_('No previous recordings for "%s", "%s", "%s", adding to hash and saving' % \
        (prog.title, prog.sub_title, prog.desc), 5)
        self.loadPreviouslyRecordedShows()
        self.previouslyRecordedShows[self.getPreviousRecordingKey(prog)] = prog
        for key in self.getPreviousRecordingKey(prog):
            self.previouslyRecordedShows[key] = prog.start
        self.savePreviouslyRecordedShows()


    def duplicate(self, prog=None):
        '''Identify if the given programme is a duplicate. If not,
        record it as previously recorded.'''
        self.loadPreviouslyRecordedShows()
        previous = self.getPreviousRecording(prog)
        if previous:
            _debug_('Found duplicate for "%s", "%s", "%s", not adding' % \
            (prog.title, prog.sub_title, prog.desc), 5)
            return TRUE
        return FALSE


    def addRecordingToSchedule(self, prog=None, inputSchedule=None):
        if inputSchedule:
            scheduledRecordings=inputSchedule
        else:
            scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.addProgram(prog, tv_util.getKey(prog))
        if not inputSchedule:
            if config.DUPLICATE_DETECTION:
                self.addDuplicate(prog)
            self.saveScheduledRecordings(scheduledRecordings)


    def removeRecordingFromSchedule(self, prog=None, inputSchedule=None):
        if inputSchedule:
            scheduledRecordings=inputSchedule
        else:
            scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.removeProgram(prog, tv_util.getKey(prog))
        if not inputSchedule:
            if config.DUPLICATE_DETECTION:
                self.removeDuplicate(prog)
            self.saveScheduledRecordings(scheduledRecordings)


    def conflictResolution(self, prog):
        def exactMatch(self, prog):
            if prog.desc:
                descResult=FALSE
                descMatches=None
                (descResult, descMatches)=self.findMatches(prog.desc)
                if descResult:
                    _debug_('Exact Matches %s' % (len(descMatches)), 5)
                    return descMatches

            if prog.sub_title:
                sub_titleResult=FALSE
                sub_titleMatches=None
                (sub_titleResult, sub_titleMatches)=self.findMatches(prog.sub_title)
                if sub_titleResult:
                    _debug_('Exact Matches %s' % (len(sub_titleMatches)), 5)
                    return sub_titleMatches
            return None

        def getConflicts(self, prog, myScheduledRecordings):
            _debug_('Before mySched recordings; ignore all addProgram lines', 5)
            self.addRecordingToSchedule(prog, myScheduledRecordings)
            progs = myScheduledRecordings.getProgramList()
            proglist = list(progs)
            proglist.sort(self.progsTimeCompare)
            conflictRating=0
            conflicts=[]
            for i in range(0, len(proglist)-1):
                thisprog = progs[proglist[i]]
                nextprog = progs[proglist[i+1]]
                if thisprog.stop > nextprog.start:
                    conflictRating=conflictRating+1
                    if thisprog==prog:
                        conflicts.append(nextprog)
                    elif nextprog==prog:
                        conflicts.append(thisprog)
            self.removeRecordingFromSchedule(prog, myScheduledRecordings)
            _debug_('After mySched recordings; stop ignoring all addProgram lines', 5)
            return (conflictRating,conflicts)

        def getRatedConflicts(self,prog,myScheduledRecordings):
            ratedConflicts=[]
            occurances = exactMatch(self,prog)
            if not occurances:
                #program no longer exists
                return (FALSE,None,None)
            #Search through all occurances of looking for a non-conflicted occurance
            for oneOccurance in occurances:
                (rating, conflictedProgs)=getConflicts(self,oneOccurance,myScheduledRecordings)
                if rating==0:
                    _debug_('No Conflict', 5)
                    programsToChange=[]
                    programsToChange.append(('add',oneOccurance))
                    return(TRUE,ratedConflicts,programsToChange)
                _debug_('Conflict Found', 5)
                ratedConflicts.append((rating,conflictedProgs,oneOccurance))
            return (FALSE,ratedConflicts,None)

        if config.CONFLICT_RESOLUTION:
            _debug_('Conflict resolution enabled', 5)
            ratedConflicts=[]
            myScheduledRecordings = copy.deepcopy(self.getScheduledRecordings())

            #Try to record it at its listed time
            (rating, conflictedProg)=getConflicts(self,prog,myScheduledRecordings)
            if rating==0:
                #No need to do anything fancy; this will work at its defaul time
                progsToChange=[]
                progsToChange.append(('add',prog))
                return (TRUE, 'No conflicts, using default time', progsToChange)

            #Default time didn't work, let's try all times known
            (result, ratedConflicts, progsToChange)=getRatedConflicts(self,prog,myScheduledRecordings)
            if result:
                #No conflicts
                return (TRUE, 'No conflicts if new program is added', progsToChange)
            if not ratedConflicts:
                #Program no longer exists, should never hit this unless schedule changes
                return (FALSE, 'Cannot schedule, new prog no longer exists', None)

            _debug_('Going into conflict resolution via scheduled program re-scheduling', 5)
            # No viable time to schedule the program without a conflict
            # Try and reschedule the already scheduled program
            atleastOneSingleConflict=FALSE
            for (scheduledConflictRating,scheduledConflictPrograms,conflictProgram) in ratedConflicts:
                #Only handle one conflict at the moment
                if scheduledConflictRating==1:
                    atleastOneSingleConflict=TRUE
                    scheduledConflictProgram=scheduledConflictPrograms[0]
                    #remove already scheduled program and try to reschedule it with the new program
                    self.removeRecordingFromSchedule(scheduledConflictProgram,myScheduledRecordings)
                    self.addRecordingToSchedule(conflictProgram,myScheduledRecordings)
                    (result, ratedConflicts, progsToChange)=getRatedConflicts(self, \
                        scheduledConflictProgram, myScheduledRecordings)
                    if result:
                        #No conflicts
                        progsToChange.append(('del',scheduledConflictProgram))
                        progsToChange.append(('add',conflictProgram))
                        return (TRUE, 'No conflicts if scheduled program is rescheduled', progsToChange)
                    if not ratedConflicts:
                        #Program no longer exists, should never hit this unless schedule changes
                        progsToChange.append(('del',scheduledConflictProgram))
                        progsToChange.append(('add',conflictProgram))
                        return (TRUE, 'Cannot find conflicted program, adding new', progsToChange)
                    #Return this to original state
                    self.addRecordingToSchedule(scheduledConflictProgram,myScheduledRecordings)
                    self.removeRecordingFromSchedule(conflictProgram,myScheduledRecordings)
            if not atleastOneSingleConflict:
                #Dirty way to (not) handle multiple conflicts
                return (FALSE, 'Cannot handle multiple conflicts: %s not scheduled' % (prog.title), None)

            _debug_('Going into conflict resolution via priority', 5)
            # No viable option to reschedule the original program
            # Time to resolve the conflict via priority
            tempRating=1000
            tempConflicted=None
            tempProg=None
            #Find least conflicted
            for (conflictedRating, conflictedPrograms, tempProgram) in ratedConflicts:
                #Cannot handle multiple conflicts
                conflictedProgram=conflictedPrograms[0]
                if conflictedRating < tempRating:
                    tempRating=conflictedRating
                    tempConflicted=conflictedProgram
                    tempProg=tempProgram
            conflictedProgram=tempConflicted
            prog=tempProgram

            #Here is where it gets ugly
            (isProgFav, progFav) = self.getFavoriteObject(prog)
            (isConfFav, confFav) = self.getFavoriteObject(conflictedProgram)
            if not isProgFav and isConfFav:
                #Regular recording has higher priority then favorite
                progsToChange=[]
                progsToChange.append(('del', conflictedProgram))
                progsToChange.append(('add', prog))
                reason='New program is a regular recording(added), scheduled is a Favorite(removed)'
                return (TRUE, reason, progsToChange)
            elif isProgFav and not isConfFav:
                #Regular recording has higher priority then favorite
                progsToChange=[]
                progsToChange.append(('del', prog))
                progsToChange.append(('add', conflictedProgram))
                reason='Scheduled program is a regular recording(added), new is a Favorite(removed)'
                return (TRUE, reason, progsToChange)
            elif not isProgFav and not isConfFav:
                return (FALSE, 'Both are regular programs, not adding new recording', None)
            elif isProgFav and isConfFav:
                #Both are favorites, go by priority (lower is better)
                if progFav.priority < confFav.priority:
                    progsToChange=[]
                    progsToChange.append(('del', conflictedProgram))
                    progsToChange.append(('add', prog))
                    reason='New program is higher rated(added), Scheduled is lower(removed)'
                    return (TRUE, reason, progsToChange)
                elif confFav.priority < progFav.priority:
                    progsToChange=[]
                    progsToChange.append(('del', prog))
                    progsToChange.append(('add', conflictedProgram))
                    reason='Scheduled program is higher rated(added), New is lower(removed)'
                    return (TRUE, reason, progsToChange)
                else:
                    #Equal priority, not adding new program
                    return (FALSE, 'Both are regular programs, not adding new recording', None)
            else:
                return (FALSE, 'No viable way to schedule', None)
        else:
            progsToChange=[]
            progsToChange.append(('add', prog))
            return (TRUE, 'Conflict resolution disabled', progsToChange)


    def checkOnlyNewDetection(self, prog=None):
        if config.ONLY_NEW_DETECTION:
            _debug_('Only new episode detection enabled', 5)
            if not self.doesFavoriteRecordOnlyNewEpisodes(prog):
                return (TRUE, 'Favorite records all episodes, record')
            if self.newEpisode(prog):
                return (TRUE, 'New episode, record')
            else:
                return (FALSE, 'Old episode, do not record')
        else:
            return (TRUE, 'Only new episode detection disabled, record')


    def checkDuplicateDetection(self, prog=None):
        if config.DUPLICATE_DETECTION:
            _debug_('Duplicate detection enabled', 5)
            if self.doesFavoriteAllowDuplicates(prog):
                return (TRUE, 'Favorite allows duplicates, record')
            if not self.duplicate(prog):
                return (TRUE, 'Not a duplicate, record')
            else:
                return (FALSE, 'Duplicate recording, do not record')
        else:
            return (TRUE, 'Duplicate detection is disabled, record')


    def setTunerid(self, prog):
        for chan in guide.chan_list:
            if prog.channel_id == chan.id:
                _debug_('scheduleRecording: "%s"' % (prog), 3)
                prog.tunerid = chan.tunerid
        return prog


    def scheduleRecording(self, prog=None):
        global guide

        if not prog:
            return (FALSE, 'no prog')

        now = time.time()
        if now > prog.stop:
            return (FALSE, 'cannot record it if it is over')

        self.updateGuide()

        (isFav, favorite) = self.isProgAFavorite(prog)
        if isFav:
            (onlyNewBool, onlyNewReason) = self.checkOnlyNewDetection(prog)
            _debug_('Only new episode detection result: %s reason %s' % (onlyNewBool,onlyNewReason), 3)
            if not onlyNewBool:
                #failed only new episode check (old episode, etc)
                return (FALSE, onlyNewReason)

            (duplicateBool, duplicateReason) = self.checkDuplicateDetection(prog)
            _debug_('Duplicate detection result: %s reason %s' % (duplicateBool,duplicateReason), 3)
            if not duplicateBool:
                #failed duplicate check (duplicate, etc)
                return (FALSE, duplicateReason)

        (ableToResolveBool, resolutionReason, progsToChange) = self.conflictResolution(prog)
        _debug_('Conflict resolution result: %s reason %s' % (ableToResolveBool,resolutionReason), 3)
        if not ableToResolveBool:
            #No viable solution was found
            return (FALSE, resolutionReason)

        if progsToChange:
            for (cmd, prog) in progsToChange:
                prog=self.setTunerid(prog)
                if cmd=='add':
                    _debug_('SCHEDULER: adding %s' % (prog.title), 3)
                    self.addRecordingToSchedule(prog)
                elif cmd=='del':
                    _debug_('SCHEDULER: removing %s' % (prog.title), 3)
                    self.removeRecordingFromSchedule(prog)
        else:
            prog=self.setTunerid(prog)
            _debug_('SCHEDULER: adding %s' % (prog.title), 3)
            self.addRecordingToSchedule(prog)

        # check, maybe we need to start right now
        self.checkToRecord()

        return (TRUE, 'recording scheduled')


    def removeScheduledRecording(self, prog=None):
        if not prog:
            return (FALSE, 'no prog')

        # get our version of 'prog'
        # It's a bad hack, but we can use isRecording than
        sr = self.getScheduledRecordings()
        progs = sr.getProgramList()

        for saved_prog in progs.values():
            if String(saved_prog) == String(prog):
                prog = saved_prog
                break

        try:
            recording = prog.isRecording
        except Exception, e:
            print 'prog.isRecording:', e
            recording = FALSE

        self.removeRecordingFromSchedule(prog)

        now = time.time()

        # if now >= prog.start and now <= prog.stop and recording:
        if recording:
            print 'stopping current recording %s' % (prog)
            rec_plugin = plugin.getbyname('RECORD')
            if rec_plugin:
                rec_plugin.Stop()

        return (TRUE, 'recording removed')


    def isProgScheduled(self, prog, schedule=None):

        if schedule == {}:
            return (FALSE, 'prog not scheduled')

        if not schedule:
            schedule = self.getScheduledRecordings().getProgramList()

        for me in schedule.values():
            if me.start == prog.start and me.channel_id == prog.channel_id:
                return (TRUE, 'prog is scheduled')

        return (FALSE, 'prog not scheduled')


    def findProg(self, chan=None, start=None):
        global guide

        _debug_('findProg: %s, %s' % (chan, start), 3)

        if not chan or not start:
            return (FALSE, 'no chan or no start')

        self.updateGuide()

        for ch in guide.chan_list:
            if chan == ch.id:
                _debug_('CHANNEL MATCH: %s' % ch.id, 5)
                for prog in ch.programs:
                    if start == '%s' % prog.start:
                        _debug_('PROGRAM MATCH 1: %s' % prog, 5)
                        return (TRUE, prog.utf2str())

        return (FALSE, 'prog not found')


    def findMatches(self, find=None, movies_only=None):
        global guide

        _debug_('findMatches: %s' % find, 3)

        matches = []
        max_results = 500

        if not find and not movies_only:
            _debug_('nothing to find', 5)
            return (FALSE, 'no search string')

        self.updateGuide()

        pattern = '.*' + find + '\ *'
        regex = re.compile(pattern, re.IGNORECASE)
        now = time.time()

        for ch in guide.chan_list:
            for prog in ch.programs:
                if now >= prog.stop:
                    continue
                if not find or regex.match(prog.title) or regex.match(prog.desc) \
                   or regex.match(prog.sub_title):
                    if movies_only:
                        # We can do better here than just look for the MPAA
                        # rating.  Suggestions are welcome.
                        if 'MPAA' in prog.utf2str().getattr('ratings').keys():
                            matches.append(prog.utf2str())
                            _debug_('PROGRAM MATCH 2: %s' % prog, 5)
                    else:
                        # We should never get here if not find and not
                        # movies_only.
                        matches.append(prog.utf2str())
                        _debug_('PROGRAM MATCH 3: %s' % prog, 5)
                if len(matches) >= max_results:
                    break

        _debug_('Found %d matches.' % len(matches), 3)

        if matches:
            return (TRUE, matches)
        return (FALSE, 'no matches')


    def updateGuide(self):
        global guide
        guide = tv.epg_xmltv.get_guide()


    def checkToRecord(self):
        rec_cmd = None
        rec_prog = None
        cleaned = None
        delay_recording = FALSE

        sr = self.getScheduledRecordings()
        progs = sr.getProgramList()

        currently_recording = None
        for prog in progs.values():
            try:
                recording = prog.isRecording
            except:
                recording = FALSE

            if recording:
                currently_recording = prog

        now = time.time()
        for prog in progs.values():
            _debug_('checkToRecord: progloop=%s' % prog, 5)

            try:
                recording = prog.isRecording
            except:
                recording = FALSE

            if not recording \
                and now >= (prog.start - config.TV_RECORD_PADDING_PRE) \
                and now < (prog.stop + config.TV_RECORD_PADDING_POST):
                # just add to the 'we want to record this' list
                # then end the loop, and figure out which has priority,
                # remember to take into account the full length of the shows
                # and how much they overlap, or chop one short
                duration = int((prog.stop + config.TV_RECORD_PADDING_POST) - now)
                if duration < 10:
                    return

                if currently_recording:
                    # Hey, something is already recording!
                    if prog.start - 10 <= now:
                        # our new recording should start no later than now!
                        # check if the new prog is a favorite and the current running is
                        # not. If so, the user manually added something, we guess it
                        # has a higher priority.


                        if self.isProgAFavorite(prog)[0] \
                            and not self.isProgAFavorite(currently_recording)[0] \
                            and now < (prog.stop + config.TV_RECORD_PADDING_POST):
                            _debug_('Ignoring %s' % prog, 3)
                            continue
                        sr.removeProgram(currently_recording,
                                         tv_util.getKey(currently_recording))
                        plugin.getbyname('RECORD').Stop()
                        time.sleep(5)
                        _debug_('CALLED RECORD STOP 1: %s' % currently_recording, 3)
                    else:
                        # at this moment we must be in the pre-record padding
                        if currently_recording.stop - 10 <= now:
                            # The only reason we are still recording is because of
                            # the post-record padding.
                            # Therefore we have overlapping paddings but not
                            # real stop / start times.
                            overlap = (currently_recording.stop + \
                                       config.TV_RECORD_PADDING_POST) - \
                                      (prog.start - config.TV_RECORD_PADDING_PRE)
                            if overlap <= ((config.TV_RECORD_PADDING_PRE +
                                            config.TV_RECORD_PADDING_POST)/4):
                                sr.removeProgram(currently_recording,
                                                 tv_util.getKey(currently_recording))
                                plugin.getbyname('RECORD').Stop()
                                time.sleep(5)
                                _debug_('CALLED RECORD STOP 2: %s' % currently_recording, 3)
                            else:
                                delay_recording = TRUE
                        else:
                            delay_recording = TRUE


                if delay_recording:
                    _debug_('delaying: %s' % prog, 3)
                else:
                    _debug_('going to record: %s' % prog, 3)
                    prog.isRecording = TRUE
                    prog.rec_duration = duration
                    prog.filename = tv_util.getProgFilename(prog)
                    rec_prog = prog


        for prog in progs.values():
            # If the program is over remove the entry.
            if now > (prog.stop + config.TV_RECORD_PADDING_POST):
                _debug_('found a program to clean: %s' % prog, 3)
                cleaned = TRUE
                del progs[tv_util.getKey(prog)]

        if rec_prog or cleaned:
            sr.setProgramList(progs)
            self.saveScheduledRecordings(sr)

        if rec_prog:
            _debug_('start recording: %s' % rec_prog, 1)
            self.record_app = plugin.getbyname('RECORD')

            if not self.record_app:
                print_plugin_warning()
                _debug_('ERROR:  Recording %s failed.' % rec_prog.title, 0)
                self.removeScheduledRecording(rec_prog)
                return

            self.vg = self.fc.getVideoGroup(rec_prog.channel_id, FALSE)
            suffix=self.vg.vdev.split('/')[-1]
            self.tv_lock_file = config.FREEVO_CACHEDIR + '/record.'+suffix
            self.record_app.Record(rec_prog)

            # Cleanup old recordings (if enabled)
            if config.RECORDSERVER_CLEANUP_THRESHOLD > 0:
                space_threshold = config.RECORDSERVER_CLEANUP_THRESHOLD * 1024 * 1024 * 1024
                path = config.TV_RECORD_DIR
                freespace = util.freespace(path)
                if freespace < space_threshold:
                    files = os.listdir(path)
                    files = util.find_matches(files, config.VIDEO_SUFFIX)
                    files = [(f, os.stat(os.path.join(path,f)).st_mtime) for f in files]
                    files.sort(lambda x,y: cmp(x[1], y[1]))
                    i = 0
                    while freespace < space_threshold and i < len(files):
                        oldestrec = files[i][0]
                        oldestfxd = oldestrec[:oldestrec.rfind('.')] + '.fxd'
                        print 'Low on disk space - delete oldest recording: %s' % oldestrec
                        os.remove(os.path.join(path,oldestrec))
                        os.remove(os.path.join(path,oldestfxd))
                        freespace = util.freespace(path)
                        i = i + 1

    def addFavorite(self, name, prog, exactchan=FALSE, exactdow=FALSE, exacttod=FALSE):
        if not name:
            return (FALSE, 'no name')

        (status, favs) = self.getFavorites()
        priority = len(favs) + 1
        fav = tv.record_types.Favorite(name, prog, exactchan, exactdow, exacttod, priority, allowDuplicates, onlyNew)

        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.addFavorite(fav)
        self.saveScheduledRecordings(scheduledRecordings)
        self.addFavoriteToSchedule(fav)

        return (TRUE, 'favorite added')


    def addEditedFavorite(self, name, title, chan, dow, mod, priority, allowDuplicates, onlyNew):
        fav = tv.record_types.Favorite()

        fav.name = name
        fav.title = title
        fav.channel = chan
        fav.dow = dow
        fav.mod = mod
        fav.priority = priority
        fav.allowDuplicates = allowDuplicates
        fav.onlyNew = onlyNew

        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.addFavorite(fav)
        self.saveScheduledRecordings(scheduledRecordings)
        self.addFavoriteToSchedule(fav)

        return (TRUE, 'favorite added')


    def removeFavorite(self, name=None):
        if not name:
            return (FALSE, 'no name')

        (status, fav) = self.getFavorite(name)
        self.removeFavoriteFromSchedule(fav)
        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.removeFavorite(name)
        self.saveScheduledRecordings(scheduledRecordings)

        return (TRUE, 'favorite removed')


    def clearFavorites(self):
        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.clearFavorites()
        self.saveScheduledRecordings(scheduledRecordings)

        return (TRUE, 'favorites cleared')


    def getFavorites(self):
        return (TRUE, self.getScheduledRecordings().getFavorites())


    def getFavorite(self, name):
        (status, favs) = self.getFavorites()

        if favs.has_key(name):
            fav = favs[name]
            return (TRUE, fav)
        else:
            return (FALSE, 'not a favorite')


    def adjustPriority(self, favname, mod=0):
        save = []
        mod = int(mod)
        (status, me) = self.getFavorite(favname)
        oldprio = int(me.priority)
        newprio = oldprio + mod

        _debug_('ap: mod=%s' % mod, 3)

        sr = self.getScheduledRecordings()
        favs = sr.getFavorites().values()

        _debug_('adjusting prio of '+favname, 3)
        for fav in favs:
            fav.priority = int(fav.priority)

            if fav.name == me.name:
                _debug_('MATCH', 5)
                fav.priority = newprio
                _debug_('moved prio of %s: %s => %s' % (fav.name, oldprio, newprio), 3)
                continue
            if mod < 0:
                if fav.priority < newprio or fav.priority > oldprio:
                    _debug_('fp: %s, old: %s, new: %s' % (fav.priority, oldprio, newprio), 3)
                    _debug_('skipping: %s' % fav.name, 3)
                    continue
                fav.priority = fav.priority + 1
                _debug_('moved prio of %s: %s => %s' % (fav.name, fav.priority-1, fav.priority), 3)

            if mod > 0:
                if fav.priority > newprio or fav.priority < oldprio:
                    _debug_('skipping: %s' % fav.name, 3)
                    continue
                fav.priority = fav.priority - 1
                _debug_('moved prio of %s: %s => %s' % (fav.name, fav.priority+1, fav.priority), 3)

        sr.setFavoritesList(favs)
        self.saveScheduledRecordings(sr)

        return (TRUE, 'priorities adjusted')

    def getFavoriteObject(self, prog, favs=None):
        #more liberal favorite check that returns an object
        if not favs:
            (status, favs) = self.getFavorites()
        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).lower()) >= 0:
               return (TRUE, fav)
        # if we get this far prog is not a favorite
        return (FALSE, 'not a favorite')

    def isProgAFavorite(self, prog, favs=None):
        if not favs:
            (status, favs) = self.getFavorites()

        lt = time.localtime(prog.start)
        dow = '%s' % lt[6]
        mod = '%s' % ((lt[3]*60)+(lt[4] / 30 * 30))
        mod = '%s' % ((lt[3]*60)+lt[4])

        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).lower()) >= 0:
                if fav.channel == tv_util.get_chan_displayname(prog.channel_id) \
                or fav.channel == 'ANY':
                    if Unicode(fav.dow) == Unicode(dow) or Unicode(fav.dow) == u'ANY':
                        if Unicode(fav.mod) == Unicode(mod) \
                        or Unicode(fav.mod) == u'ANY':
                            return (TRUE, fav.name)

        # if we get this far prog is not a favorite
        return (FALSE, 'not a favorite')

    def doesFavoriteRecordOnlyNewEpisodes(self, prog, favs=None):
        if not favs:
           (status, favs) = self.getFavorites()
        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).lower()) >= 0:
               _debug_('NEW: %s'%fav.onlyNew, 5)
               if fav.onlyNew == '1':
                  return TRUE

    def doesFavoriteAllowDuplicates(self, prog, favs=None):
        if not favs:
           (status, favs) = self.getFavorites()
        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).lower()) >= 0:
               _debug_('DUP: %s'%fav.allowDuplicates, 5)
               if fav.allowDuplicates == '1':
                  return TRUE

    def removeFavoriteFromSchedule(self, fav):
        # TODO: make sure the program we remove is not
        #       covered by another favorite.

        tmp = {}
        tmp[fav.name] = fav

        scheduledRecordings = self.getScheduledRecordings()
        progs = scheduledRecordings.getProgramList()
        for prog in progs.values():
            (isFav, favorite) = self.isProgAFavorite(prog, tmp)
            if isFav:
                self.removeScheduledRecording(prog)

        return (TRUE, 'favorite unscheduled')


    def addFavoriteToSchedule(self, fav):
        global guide
        favs = {}
        favs[fav.name] = fav

        self.updateGuide()

        for ch in guide.chan_list:
            for prog in ch.programs:
                (isFav, favorite) = self.isProgAFavorite(prog, favs)
                if isFav:
                    prog.isFavorite = favorite
                    self.scheduleRecording(prog)

        return (TRUE, 'favorite scheduled')


    def updateFavoritesSchedule(self):
        #  TODO: do not re-add a prog to record if we have
        #        previously decided not to record it.

        global guide

        self.updateGuide()

        # First get the timeframe of the guide.
        last = 0
        for ch in guide.chan_list:
            for prog in ch.programs:
                if prog.start > last: last = prog.start

        scheduledRecordings = self.getScheduledRecordings()

        (status, favs) = self.getFavorites()

        if not len(favs):
            return (FALSE, 'there are no favorites to update')


        # Then remove all scheduled favorites in that timeframe to
        # make up for schedule changes.
        progs = scheduledRecordings.getProgramList()
        for prog in progs.values():

            # try:
            #     favorite = prog.isFavorite
            # except:
            #     favorite = FALSE

            # if prog.start <= last and favorite:
            (isFav, favorite) = self.isProgAFavorite(prog, favs)
            if prog.start <= last and isFav:
                # do not yet remove programs currently being recorded:
                isRec = hasattr(prog, "isRecording") and prog.isRecording
                if not isRec:
                    self.removeScheduledRecording(prog)

        for ch in guide.chan_list:
            for prog in ch.programs:
                (isFav, favorite) = self.isProgAFavorite(prog, favs)
                isRec = hasattr(prog, "isRecording") and prog.isRecording
                if isFav and not isRec:
                    prog.isFavorite = favorite
                    self.scheduleRecording(prog)

        return (TRUE, 'favorites schedule updated')


    #################################################################
    #  Start XML-RPC published methods.                             #
    #################################################################

    def xmlrpc_isPlayerRunning(self):
        (status, message) = (FALSE, 'RecordServer::isPlayerRunning: cannot acquire lock')
        self.lock.acquire()
        try:
            status = self.isPlayerRunning()
            message = status and 'player is running' or 'player is not running'
        finally:
            self.lock.release()
        return (status, message)

    def xmlrpc_isRecording(self):
        (status, message) = (FALSE, 'RecordServer::isRecording: cannot acquire lock')
        self.lock.acquire()
        try:
            status = self.isRecording()
            message = status and 'is recording' or 'is not recording'
        finally:
            self.lock.release()
        return (status, message)

    def xmlrpc_findNextProgram(self):
        (status, message) = (FALSE, 'RecordServer::findNextProgram: cannot acquire lock')
        self.lock.acquire()
        try:
            response = self.findNextProgram()
            status = response != None
            return (status, jellyToXML(response))
        finally:
            self.lock.release()
        return (status, message)

    def xmlrpc_getScheduledRecordings(self):
        (status, message) = (FALSE, 'RecordServer::getScheduledRecordings: cannot acquire lock')
        self.lock.acquire()
        try:
            return (TRUE, jellyToXML(self.getScheduledRecordings()))
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_saveScheduledRecordings(self, scheduledRecordings=None):
        (status, message) = (FALSE, 'RecordServer::saveScheduledRecordings: cannot acquire lock')
        self.lock.acquire()
        try:
            status = self.saveScheduledRecordings(scheduledRecordings)
            message = status and 'saveScheduledRecordings::success' or 'saveScheduledRecordings::failure'
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_scheduleRecording(self, prog=None):
        if not prog:
            return (FALSE, 'RecordServer::scheduleRecording:  no prog')

        (status, message) = (FALSE, 'RecordServer::scheduleRecording: cannot acquire lock')
        self.lock.acquire()
        try:
            prog = unjellyFromXML(prog)
            (status, response) = self.scheduleRecording(prog)
            message = 'RecordServer::scheduleRecording: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_removeScheduledRecording(self, prog=None):
        if not prog:
            return (FALSE, 'RecordServer::removeScheduledRecording:  no prog')

        (status, message) = (FALSE, 'RecordServer::removeScheduledRecording: cannot acquire lock')
        self.lock.acquire()
        try:
            prog = unjellyFromXML(prog)
            (status, response) = self.removeScheduledRecording(prog)
            message = 'RecordServer::removeScheduledRecording: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_isProgScheduled(self, prog=None, schedule=None):
        if not prog:
            return (FALSE, 'removeScheduledRecording::failure:  no prog')

        (status, message) = (FALSE, 'RecordServer::removeScheduledRecording: cannot acquire lock')
        self.lock.acquire()
        try:
            prog = unjellyFromXML(prog)
            if schedule:
                schedule = unjellyFromXML(schedule)
            (status, response) = self.isProgScheduled(prog, schedule)
            message = 'RecordServer::isProgScheduled: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_findProg(self, chan, start):
        (status, message) = (FALSE, 'RecordServer::findProg: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.findProg(chan, start)
            message = status and jellyToXML(response) or ('RecordServer::findProg: %s' % response)
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_findMatches(self, find, movies_only):
        (status, message) = (FALSE, 'RecordServer::findMatches: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.findMatches(find, movies_only)
            message = status and jellyToXML(response) or ('RecordServer::findMatches: %s' % response)
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_echotest(self, blah):
        (status, message) = (FALSE, 'RecordServer::echotest: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, message) = (TRUE, 'RecordServer::echotest: %s' % blah)
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_addFavorite(self, name, prog, exactchan=FALSE, exactdow=FALSE, exacttod=FALSE):
        (status, message) = (FALSE, 'RecordServer::addFavorite: cannot acquire lock')
        self.lock.acquire()
        try:
            prog = unjellyFromXML(prog)
            (status, response) = self.addFavorite(name, prog, exactchan, exactdow, exacttod)
            message = 'RecordServer::addFavorite: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_addEditedFavorite(self, name, title, chan, dow, mod, priority, allowDuplicates, onlyNew):
        (status, message) = (FALSE, 'RecordServer::addEditedFavorite: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.addEditedFavorite(unjellyFromXML(name), \
            unjellyFromXML(title), chan, dow, mod, priority, allowDuplicates, onlyNew)
            message = 'RecordServer::addEditedFavorite: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_removeFavorite(self, name=None):
        (status, message) = (FALSE, 'RecordServer::removeFavorite: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.removeFavorite(name)
            message = 'RecordServer::removeFavorite: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_clearFavorites(self):
        (status, message) = (FALSE, 'RecordServer::clearFavorites: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.clearFavorites()
            message = 'RecordServer::clearFavorites: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_getFavorites(self):
        (status, message) = (FALSE, 'RecordServer::getFavorites: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, message) = (TRUE, jellyToXML(self.getScheduledRecordings().getFavorites()))
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_getFavorite(self, name):
        (status, message) = (FALSE, 'RecordServer::getFavorite: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.getFavorite(name)
            message = status and jellyToXML(response) or 'RecordServer::getFavorite: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_adjustPriority(self, favname, mod=0):
        (status, message) = (FALSE, 'RecordServer::adjustPriority: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.adjustPriority(favname, mod)
            message = 'RecordServer::adjustPriority: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_isProgAFavorite(self, prog, favs=None):
        (status, message) = (FALSE, 'RecordServer::adjustPriority: cannot acquire lock')
        self.lock.acquire()
        try:
            prog = unjellyFromXML(prog)
            if favs:
                favs = unjellyFromXML(favs)
            (status, response) = self.isProgAFavorite(prog, favs)
            message = 'RecordServer::adjustPriority: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_removeFavoriteFromSchedule(self, fav):
        (status, message) = (FALSE, 'RecordServer::removeFavoriteFromSchedule: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.removeFavoriteFromSchedule(fav)
            message = 'RecordServer::removeFavoriteFromSchedule: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_addFavoriteToSchedule(self, fav):
        (status, message) = (FALSE, 'RecordServer::addFavoriteToSchedule: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.addFavoriteToSchedule(fav)
            message = 'RecordServer::addFavoriteToSchedule: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    def xmlrpc_updateFavoritesSchedule(self):
        (status, message) = (FALSE, 'updateFavoritesSchedule: cannot acquire lock')
        self.lock.acquire()
        try:
            (status, response) = self.updateFavoritesSchedule()
            message = 'RecordServer::updateFavoritesSchedule: %s' % response
        finally:
            self.lock.release()
        return (status, message)


    #################################################################
    #  End XML-RPC published methods.                               #
    #################################################################


    def create_fxd(self, rec_prog):
        from util.fxdimdb import FxdImdb, makeVideo
        fxd = FxdImdb()

        (filebase, fileext) = os.path.splitext(rec_prog.filename)
        fxd.setFxdFile(filebase, overwrite=TRUE)

        video = makeVideo('file', 'f1', os.path.basename(rec_prog.filename))
        fxd.setVideo(video)
        fxd.info['tagline'] = fxd.str2XML(rec_prog.sub_title)
        fxd.info['plot'] = fxd.str2XML(rec_prog.desc)
        fxd.info['runtime'] = None
        fxd.info['recording_timestamp'] = str(time.time())
        fxd.info['year'] = time.strftime('%m-%d ' + config.TV_TIMEFORMAT,
                                         time.localtime(rec_prog.start))
        fxd.title = rec_prog.title
        fxd.writeFxd()


    def startMinuteCheck(self):
        next_minute = (int(time.time()/60) * 60 + 60) - int(time.time())
        _debug_('top of the minute in %s seconds' % next_minute, 1)
        reactor.callLater(next_minute, self.minuteCheck)

    def minuteCheck(self):
        next_minute = (int(time.time()/60) * 60 + 60) - int(time.time())
        if next_minute != 60:
            # Compensate for timer drift
            if DEBUG:
                log.debug('top of the minute in %s seconds' % next_minute)
            reactor.callLater(next_minute, self.minuteCheck)
        else:
            reactor.callLater(60, self.minuteCheck)

        self.checkToRecord()


    def eventNotice(self):
        #print 'RECORDSERVER GOT EVENT NOTICE'
        # Use callLater so that handleEvents will get called the next time
        # through the main loop.
        reactor.callLater(0, self.handleEvents)


    def handleEvents(self):
        event = rc_object.get_event()

        if event:
            if event == OS_EVENT_POPEN2:
                print 'popen %s' % event.arg[1]
                event.arg[0].child = util.popen3.Popen3(event.arg[1])

            elif event == OS_EVENT_WAITPID:
                pid = event.arg[0]
                print 'waiting on pid %s' % pid

                for i in range(20):
                    try:
                        wpid = os.waitpid(pid, os.WNOHANG)[0]
                    except OSError:
                        # forget it
                        continue
                    if wpid == pid:
                        break
                    time.sleep(0.1)

            elif event == OS_EVENT_KILL:
                pid = event.arg[0]
                sig = event.arg[1]

                print 'killing pid %s with sig %s' % (pid, sig)
                try:
                    os.kill(pid, sig)
                except OSError:
                    pass

                for i in range(20):
                    try:
                        wpid = os.waitpid(pid, os.WNOHANG)[0]
                    except OSError:
                        # forget it
                        continue
                    if wpid == pid:
                        break
                    time.sleep(0.1)

                else:
                    print 'force killing with signal 9'
                    try:
                        os.kill(pid, 9)
                    except OSError:
                        pass
                    for i in range(20):
                        try:
                            wpid = os.waitpid(pid, os.WNOHANG)[0]
                        except OSError:
                            # forget it
                            continue
                        if wpid == pid:
                            break
                        time.sleep(0.1)
                print 'recorderver: After wait()'

            elif event == RECORD_START:
                print 'received event RECORD_START'
                prog = event.arg
                open(self.tv_lock_file, 'w').close()
                self.create_fxd(prog)
                if config.VCR_PRE_REC:
                    util.popen3.Popen3(config.VCR_PRE_REC)

            elif event == RECORD_STOP:
                print 'received event RECORD_STOP'
                os.remove(self.tv_lock_file)
                prog = event.arg
                try:
                    snapshot(prog.filename)
                except:
                    # If automatic pickling fails, use on-demand caching when
                    # the file is accessed instead.
                    os.rename(vfs.getoverlay(prog.filename + '.raw.tmp'),
                              vfs.getoverlay(os.path.splitext(prog.filename)[0] + '.png'))
                    pass
                if config.VCR_POST_REC:
                    util.popen3.Popen3(config.VCR_POST_REC)
                if config.REMOVE_COMMERCIALS:
                    (result, response) = connectionTest('connection test')
                    if result:
                       (status, idnr) = initCommDetectJob(prog.filename)
                       (status, output) = listJobs()
                       _debug_(output, 5)
                       (status, output) = queueIt(idnr, True)
                       _debug_(output, 5)
                    else:
                       _debug_('commdetect server not running', 1)
            else:
                print 'not handling event %s' % str(event)
                return
        else:
            print 'no event to get'


def main():
    app = Application("RecordServer")
    rs = RecordServer()
    if (DEBUG == 0):
        app.listenTCP(config.RECORDSERVER_PORT, server.Site(rs, logPath='/dev/null'))
    else:
        app.listenTCP(config.RECORDSERVER_PORT, server.Site(rs))
    rs.startMinuteCheck()
    rc_object.subscribe(rs.eventNotice)
    app.run(save=0)


if __name__ == '__main__':
    import traceback
    import time
    import glob

    locks = glob.glob(config.FREEVO_CACHEDIR + '/record.*')
    for f in locks:
        print 'Removed old record lock \"%s\"' % f
        os.remove(f)

    while 1:
        try:
            start = time.time()
            main()
            break
        except:
            traceback.print_exc()
            if start + 10 > time.time():
                print 'server problem, sleeping 1 min'
                time.sleep(60)






freevo webserver start

Traceback (most recent call last):
  File "/var/lib/python-support/python2.5/freevo/helpers/webserver.py", line 52, in <module>
    from twisted.internet import app 
ImportError: cannot import name app

import sys, os
import config

appname = os.path.splitext(os.path.basename(sys.argv[0]))[0]
appconf = appname.upper()

# change uid
if __name__ == '__main__':
    uid='config.'+appconf+'_UID'
    gid='config.'+appconf+'_GID'
    try:
        if eval(uid) and os.getuid() == 0:
            os.setgid(eval(gid))
            os.setuid(eval(uid))
            os.environ['USER'] = pwd.getpwuid(os.getuid())[0]
            os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
    except Exception, e:
        print e

from twisted.internet import app
#from twisted.internet import reactor
from twisted.web import static, server, vhost, script
from twisted.python import log
import twisted.web.rewrite as rewrite


if len(sys.argv)>1 and sys.argv[1] == '--help':
    print 'start or stop the internal webserver'
    print 'usage freevo webserver [ start | stop ]'
    sys.exit(0)

# No debugging in this module
DEBUG = hasattr(config, appconf+'_DEBUG') and eval('config.'+appconf+'_DEBUG') or config.DEBUG

def _debug_(text, level=1):
    if DEBUG >= level:
        try:
            log.debug(String(text))
        except:
            print String(text)

def helpimagesrewrite(request):
    if request.postpath and request.postpath[0]=='help' and request.postpath[1]=='images':
        request.postpath.pop(0) 
        request.path = '/'+'/'.join(request.prepath+request.postpath)
    if request.postpath and request.postpath[0]=='help' and request.postpath[1]=='styles':
        request.postpath.pop(0) 
        request.path = '/'+'/'.join(request.prepath+request.postpath)

def main():
    # the start and stop stuff will be handled from the freevo script

    logfile = '%s/%s-%s.log' % (config.LOGDIR, appname, os.getuid())
    log.startLogging(open(logfile, 'a'))

    if os.path.isdir(os.path.join(os.environ['FREEVO_PYTHON'], 'www/htdocs')):
        docRoot = os.path.join(os.environ['FREEVO_PYTHON'], 'www/htdocs')
    else:
        docRoot = os.path.join(config.SHARE_DIR, 'htdocs')

    root = static.File(docRoot)
    root.processors = { '.rpy': script.ResourceScript, }

    child_resources = []
    child_resources.extend(config.VIDEO_ITEMS)
    child_resources.extend(config.AUDIO_ITEMS)
    child_resources.extend(config.IMAGE_ITEMS)
    child_resources.extend([('Recorded TV', config.TV_RECORD_DIR)])
    child_resources.extend([('Webserver Cache', config.WEBSERVER_CACHEDIR)])
    for item in child_resources:
        if isinstance(item, tuple) and len(item) == 2:
            (title, path) = item
            root.putChild(path.replace("/", "_"), static.File(path))
    
    root.putChild('vhost', vhost.VHostMonsterResource())
    rewriter =  rewrite.RewriterResource(root, helpimagesrewrite)
    if (config.DEBUG == 0):
        site = server.Site(rewriter, logPath='/dev/null')
    else:
        site = server.Site(rewriter)
    
    application = app.Application('web')
#    reactor.listenTCP(config.WEBSERVER_PORT, site)
    application.listenTCP(config.WEBSERVER_PORT, site)
    application.run(save=0)
#    reactor.run(0)

    
if __name__ == '__main__':
    import traceback
    import time
    while 1:
        try:
            start = time.time()
            main()
            break
        except:
            traceback.print_exc()
            if start + 10 > time.time():
                print 'server problem, sleeping 1 min'
                time.sleep(60)