class WebRecorder():
    '''
    API methods are called by the RecorderPlugin in the RecorderWebService module.
    '''
    # signal modes
    # TODO change mode to STATUS und ERROR...
    MSG_STATUS = MessageListener.MSG_STATUS
    # MSG_EPG = MessageListener.MSG_EPG
    MSG_REFRESH = MessageListener.MSG_REFRESH
    
    # Modes or types of rec? 
    (MODE_DATA, MODE_REC, MODE_BLOCK) = range(0xA0, 0xA3)
    (TYPE_HEAD, TYPE_PROG, TYPE_INFO) = range(3)



    def __init__(self):
        '''
        starts the app
        '''
        self.configuration = Config()
        self.configuration.setupLogging("webdvb.log")
        
        self._lastMessage = None
        ml = MessageListener();
        ml.addListener(ml.MSG_STATUS, self.storeLastMessage)
        # ml.addListener(ml.MSG_EPG, this.storeLastMessage)
        ml.addListener(ml.MSG_REFRESH, self.storeLastMessage)
        
        channelReader = ChannelReader()
        cPath = self.configuration.getChannelFilePath()

        channelReader.readChannels(cPath)
        self.channelList = channelReader.getChannels()
        self.progProvider = EPGProgramProvider(self, self.channelList, self.configuration)
        self._lastEpgRead = 0.0
        # self._readCachedEpgData()
        self.checkEPGData()
  

  
    def _readCachedEpgData(self):
        ml = MessageListener();
        if not self.channelList:
            ml.signalMessage(self.MSG_STATUS, "Where is that channel.conf? RTF!")
            return

        ml.signalMessage(self.MSG_STATUS, "Reading programm info")
        msg = "Idle"
        try:
            self.progProvider.readEPGCache()
            ml.signalMessage(self.MSG_REFRESH, "Program info read")  # enforces a new list
        except IOError:
            msg = "No EPG data"
        except Exception, ex:
            msg = "Error reading cached EPG Data: " + str(ex.args[0])
            self.configuration.getLogger().exception(msg)
            print msg
        self.configuration.logInfo(msg)                        
        ml.signalMessage(self.MSG_STATUS, msg)
Exemple #2
0
class WebRecorder():
    '''
    API methods are called by the RecorderPlugin in the RecorderWebService module.
    '''
    # signal modes
    # TODO change mode to STATUS und ERROR...
    MSG_STATUS = MessageListener.MSG_STATUS
    # MSG_EPG = MessageListener.MSG_EPG
    MSG_REFRESH = MessageListener.MSG_REFRESH

    # Modes or types of rec?
    (MODE_DATA, MODE_REC, MODE_BLOCK) = list(range(0xA0, 0xA3))
    (TYPE_HEAD, TYPE_PROG, TYPE_INFO) = list(range(3))

    def __init__(self):
        '''
        starts the app
        '''
        self.configuration = Config()
        self.configuration.setupLogging("webdvb.log")

        self._lastMessage = None
        ml = MessageListener()
        ml.addListener(ml.MSG_STATUS, self.storeLastMessage)
        # ml.addListener(ml.MSG_EPG, this.storeLastMessage)
        ml.addListener(ml.MSG_REFRESH, self.storeLastMessage)

        channelReader = ChannelReader()
        cPath = self.configuration.getChannelFilePath()

        channelReader.readChannels(cPath)
        self.channelList = channelReader.getChannels()
        self.progProvider = EPGProgramProvider(self, self.channelList,
                                               self.configuration)
        self._lastEpgRead = 0.0
        # self._readCachedEpgData()
        self.checkEPGData()

    def _readCachedEpgData(self):
        ml = MessageListener()
        if not self.channelList:
            ml.signalMessage(self.MSG_STATUS,
                             "Where is that channel.conf? RTF!")
            return

        ml.signalMessage(self.MSG_STATUS, "Reading programm info")
        msg = "Idle"
        try:
            self.progProvider.readEPGCache()
            ml.signalMessage(self.MSG_REFRESH,
                             "Program info read")  # enforces a new list
        except IOError:
            msg = "No EPG data"
        except Exception as ex:
            msg = "Error reading cached EPG Data: " + str(ex.args[0])
            self.configuration.getLogger().exception(msg)
            print(msg)
        self.configuration.logInfo(msg)
        ml.signalMessage(self.MSG_STATUS, msg)

    def _getEPGDevice(self):
        return DVBDevice.getGrabber(self.channelList, self.configuration)

    def _collectEPGFromDevice(self):
        epgUpdater = self.progProvider.getEPGUpdater()
        self._updater.updateDatabase()
        if epgUpdater.hasError():
            MessageListener().signalMessage(self.MSG_STATUS,
                                            epgUpdater.getErrorMessage())

    '''
    API to Recorder plugin
    Basic idea: should be json objects
    '''

    def storeLastMessage(self, message):
        self._lastMessage = message

    def getLastSignal(self):
        msg = self._lastMessage
        self._lastMessage = None
        return msg

    def readEPGDeviceData(self):
        self._collectEPGFromDevice()

    def getChannelList(self):
        channelNames = []
        for channel in self.channelList:
            channelNames.append(channel.getName())
        jchannels = json.dumps(channelNames)
        return jchannels

    def getProgrammInfoForChannel(self, aChannelString):
        daytoDayList = self.progProvider.getInfosForChannel(aChannelString)
        jDayToDayArray = self._formatProgramList(daytoDayList)
        jInfos = json.dumps(jDayToDayArray)
        return jInfos

    def toggleRecordMode(self, jsonString):
        jsonDict = json.loads(jsonString)
        epgInfo = self._lookupEPGInfoFromJSON(jsonDict)
        forceRemove = jsonDict["type"] == self.TYPE_INFO
        if epgInfo is not None:
            self.progProvider.toggleRecordInfo(epgInfo, forceRemove)
            result = self._formatProgramRow(epgInfo)
        else:
            jsonDict["error"] = "Entry not found"
            result = jsonDict
        if self._lastMessage is not None:
            result["error"] = self.getLastSignal()
        return json.dumps(result)

    def getFilterList(self, searchTuple):
        channelName = searchTuple[0]
        filterString = searchTuple[1]
        epgInfoList = self.progProvider.searchInChannel(
            channelName, filterString)
        jDayToDayArray = self._formatProgramList(epgInfoList)
        return json.dumps(jDayToDayArray)

    def getAutoSelectList(self):
        autoselectList = self.progProvider.getAutoSelector(
        ).getAutoSelectionList()

        jList = self._formatAutoSelectList(autoselectList)
        return json.dumps(jList)

    def addToAutoSelection(self, jsonString):
        jsonDict = json.loads(jsonString)
        epgInfo = self._lookupEPGInfoFromJSON(jsonDict)
        autoSelector = self.progProvider.getAutoSelector()
        autoSelector.addAutoSelectPreference(epgInfo)
        autoSelector.saveAutoSelectData()

    def saveAutoSelectionSetting(self, jsonString):
        # weekmode has changed. single entry. update & save
        jsonDict = json.loads(jsonString)
        hourString = jsonDict["timetext"]
        # Text is unicode!!
        titleString = jsonDict["text"]
        channelName = jsonDict["chanID"]
        weekModeString = jsonDict["weekMode"]

        autoSelector = self.progProvider.getAutoSelector()
        autoSelector.updateWeekMode(hourString, titleString, channelName,
                                    weekModeString)
        autoSelector.saveAutoSelectData()

    def removeFromAutoSelection(self, jsonString):
        # NOTE: json makes unicode out of the string
        jsonDict = json.loads(jsonString)
        hourString = jsonDict["timetext"]
        titleString = jsonDict["text"]
        channelName = jsonDict["chanID"]
        autoSelector = self.progProvider.getAutoSelector()
        autoSelector.removeFromAutoSelectPreference(hourString,
                                                    str(titleString),
                                                    str(channelName))
        autoSelector.saveAutoSelectData()

    def getRecordingList(self):
        recInfoList = self.progProvider.getRecordQueue().getRecList()
        jList = self._formatRecordingList(recInfoList)
        return json.dumps(jList)

    #### dict: {u'marginStop': 600, u'marginStart': 300, u'jobID': u'1'}
    def updateRecorderMargins(self, jsonString):
        jsonList = json.loads(jsonString)
        recInfoList = self.progProvider.getRecordQueue().getRecList()
        for jEntry in jsonList:
            jobID = jEntry["jobID"]
            found = next((recEntry for recEntry in recInfoList
                          if recEntry.getEPGInfo().getJobID() == jobID), None)
            if found:
                found.setMarginStart(jEntry["marginStart"])
                found.setMarginStop(jEntry["marginStop"])

        self.progProvider.getRecordQueue()._storeRecordQueue(recInfoList)

    '''
    search the database for a string
    "cmd":"SEARCH_ALL","arg":"test","data":""}
    '''

    def searchAll(self, searchString):
        epgInfoList = self.progProvider.searchAll(searchString)
        #jDayToDayArray = self._formatProgramList(epgInfoList)
        #return json.dumps(jDayToDayArray)
        #maybe sort them per channel?
        jDayList = []
        for epgInfo in epgInfoList:
            jInfo = self._formatProgramRow(epgInfo)
            jDayList.append(jInfo)

        errorMsg = None
        if len(jDayList) == 0:
            errorMsg = "Nothing found"
        jAnswer = {"type": self.TYPE_INFO, "error": errorMsg, "list": jDayList}
        return json.dumps(jAnswer)

    '''
    reread epg info if a modification took place.If changes took place (like a daemon epg update)
    update the database 
    '''

    def checkEPGData(self):
        fileName = self.configuration.getCachedXMLTVFilePath()
        currentModificationtime = 0
        try:
            currentModificationtime = OSTools.getLastModificationTime(fileName)
        except OSError as osError:
            msg = "CheckEpgData:" + osError.strerror
            self.configuration.logError(msg)
            self.storeLastMessage(msg)
            return


#         if self._lastEpgRead is None:
#             self._lastEpgRead= currentModificationtime-100

        if currentModificationtime - self._lastEpgRead > 60:
            self._lastEpgRead = currentModificationtime
            self._readCachedEpgData()

    '''
    End of WebRecorder API
    --
    
    Helper/conversion methods -- aka WebViewGenerator?
    '''
    def _formatHeader(self, epgInfo):
        header = epgInfo.getStartTime().strftime("%A %d %B")
        headerText = "<b>%s</b>" % header
        return {
            "type": self.TYPE_HEAD,
            "text": headerText,
            "time": None
        }

    def _formatProgramRow(self, epgInfo):
        epgTime = epgInfo.getStartTimeString()
        epgDate = epgInfo.getDateString()
        title = epgInfo.getTitle()
        duration = str(epgInfo.getDuration())
        description = epgInfo.getDescription()
        # TODO: Formating is client work- only the data!
        programText = "<b>%s</b><br>%s<small><i> Duration: %s</i></small>" % (
            title, description, duration)

        jobID = None
        if epgInfo.isMarkedForRecord():
            recmode = self.MODE_REC
            jobID = epgInfo.getJobID()
        elif epgInfo.isBlockedForRecord():
            recmode = self.MODE_BLOCK
        else:
            recmode = self.MODE_DATA
        return {
            "type": self.TYPE_PROG,
            "text": programText,
            "time": epgTime,
            "date": epgDate,
            "recordMode": recmode,
            "jobID": jobID,
            "title": title,
            "channel": epgInfo.getChannel().getName(),
            "epgOK": epgInfo.isConsistent,
            "error": None
        }

    def _formatProgramList(self, daytoDayList):
        jDayToDayArray = []
        for singleDayList in daytoDayList:
            jDayList = []
            # adds the header - setting the date only
            # TODO: Empty singleDayList! Should not happen
            if len(singleDayList) == 0:
                print("ERROR: Empty single day list")
                continue

            headerText = self._formatHeader(singleDayList[0])
            for epgInfo in singleDayList:
                jInfo = self._formatProgramRow(epgInfo)
                jDayList.append(jInfo)

            jDayObject = {
                "head": headerText,
                "list": jDayList
            }
            jDayToDayArray.append(jDayObject)
        if len(jDayToDayArray) == 0:
            return self.asJsonError("No data", self.getLastSignal())
        return jDayToDayArray

    def asJsonError(self, errorMsg, argumentString):
        reason = argumentString
        if not reason:
            reason = errorMsg
        return {"type": self.TYPE_INFO, "error": errorMsg, "args": reason}

    def _lookupEPGInfoFromJSON(self, jsonData):
        aChannelString = jsonData["channel"]
        dayString = jsonData["date"]
        timeString = jsonData["time"]
        # Note JSON data always uses unicode - convert it to byte encoding it to uf8
        daytoDayList = self.progProvider.getInfosForChannel(aChannelString)
        # Well... get the right DAY first....
        for singleDayList in daytoDayList:
            if singleDayList[0].getDateString() in dayString:
                for epgInfo in singleDayList:
                    if epgInfo.getStartTimeString() in timeString:
                        return epgInfo
        return None

    def _formatAutoSelectList(self, autoSelectList):

        jDayList = []
        for autoSelection in autoSelectList:
            timeString = autoSelection.getHourListString()
            title = autoSelection.getTitle()
            chanID = autoSelection.getChannelID()
            weekMode = autoSelection.getWeekMode()
            jInfo = {
                "type": self.TYPE_INFO,
                "timetext": timeString,
                "text": title,
                "title": title,
                "chanID": chanID,
                "weekMode": weekMode,
                "error": None
            }
            jDayList.append(jInfo)
        jsonASList = {
            "weekTypes": ["Mo-Fri", "Sa-Su", "Mo-Su"],
            "elements": jDayList
        }
        return jsonASList

    def _formatRecordingList(self, recInfoList):
        jDayList = []
        for recInfo in recInfoList:
            jInfo = self._formatRecordingRow(recInfo)
            jDayList.append(jInfo)
        return jDayList

    def _formatRecordingRow(self, recInfo):
        epgInfo = recInfo.getEPGInfo()
        epgTime = epgInfo.getStartTimeString()
        epgDate = epgInfo.getDateString()
        theDay = epgInfo.getStartTime().strftime("%a %d")
        start = epgInfo.getStartTime().strftime("%H:%M-")
        end = epgInfo.getEndTime().strftime("%H:%M")
        channel = epgInfo.getChannel().getName()
        formatTime = "<b>%s</b><i> %s</i><br><small>%s%s</small>" % (
            channel, theDay, start, end)

        title = epgInfo.getTitle()
        description = epgInfo.getDescription()
        jobID = epgInfo.getJobID()
        marginStart = recInfo.getMarginAsString(recInfo.marginStart)
        marginStop = recInfo.getMarginAsString(recInfo.marginEnd)
        if len(jobID) > 0:
            programText = "<b>%s</b><br>%s <i>(%s)</i>" % (title, description,
                                                           jobID)
        else:
            programText = "<b>%s</b><br>%s" % (title, description)
        return {
            "type": self.TYPE_INFO,
            "timetext": formatTime,
            "text": programText,
            "time": epgTime,
            "date": epgDate,
            "jobID": jobID,
            "title": title,
            "channel": channel,
            "marginStart": marginStart,
            "marginStop": marginStop,
            "error": None
        }
Exemple #3
0
class DVBRecorder():
    '''
    startup main class
    '''
    def __init__(self):
        '''
        starts the app
        '''
        self.configuration = Config()
        self.configuration.setupLogging("dvb.log")

        channelReader = ChannelReader()
        cPath = self.configuration.getChannelFilePath()

        channelReader.readChannels(cPath)
        self.channelList = channelReader.getChannels()
        self.progProvider = EPGProgramProvider(self, self.channelList,
                                               self.configuration)
        recView = RecorderView(self.progProvider)
        ml = MessageListener()
        ml.addListener(ml.MSG_STATUS, recView.setStatusLine)
        ml.addListener(ml.MSG_EPG, recView.notifyEPGStatus)
        ml.addListener(ml.MSG_REFRESH, recView.refreshProgrammInfos)
        t1 = ReaderThread(False, self._readCachedEpgData)
        t1.start()
        recView.openView()
        #returns on close
        t1.join()

    def _readCachedEpgData(self):
        ml = MessageListener()
        if not self.channelList:
            ml.signalMessage(ml.MSG_STATUS, "Where is that channel.conf? RTF!")
            return

        ml.signalMessage(ml.MSG_STATUS, "Reading programm info")
        msg = "Idle"
        try:
            self.progProvider.readEPGCache()
            ml.signalMessage(ml.MSG_REFRESH)  # enforces a new list
        except IOError:
            msg = "No EPG data"
        except Exception as ex:
            msg = "Error reading cached EPG Data: " + str(ex.args[0])

        self.configuration.logInfo(msg)
        ml.signalMessage(ml.MSG_STATUS, msg)

    ##callback for the epg program provider:
    def readEPGDeviceData(self):
        self._EPGThread = ReaderThread(False, self._collectEPGFromDevice)
        #TEST code: self._EPGThread = ReaderThread(False,self._readEPGSimData)
        self._EPGThread.start()

    def _collectEPGFromDevice(self):
        ml = MessageListener()
        ml.signalMessage(ml.MSG_EPG, True)
        ml.signalMessage(ml.MSG_STATUS, "Retrieving new EPG data")

        epgUpdater = self.progProvider.getEPGUpdater()
        epgUpdater.updateDatabase()
        if epgUpdater.hasError():
            ml.signalMessage(ml.MSG_STATUS, epgUpdater.getErrorMessage())
            ml.signalMessage(ml.MSG_EPG, False)
            return

        ml.signalMessage(ml.MSG_REFRESH)  # enforces redraw!
        ml.signalMessage(ml.MSG_STATUS, "EPG data up to date !")
        ml.signalMessage(ml.MSG_EPG, False)


#    def _readEPGSimData(self):
#        rawEPGPath = "/home/matze/JWSP/py1/VideoRecorder/src/xmltv/rawepg.xmltv"
#        with open(rawEPGPath, 'r') as aFile:
#            dvbList=aFile.readlines()
#
#        epgReader=EpgReader(self.channelList)
#        for xmls in dvbList:
#            print "parse xml"
#            epgReader.parseXML_TVString(xmls,UTC=True)
#
#        infoDictionary = epgReader.getInfoDictionary()
#        self.progProvider.updateProgramms(infoDictionary)
#        ml = MessageListener();
#        ml.signalMessage(ml.MODE_REFRESH)# enforces redraw!
#        ml.signalMessage(ml.MODE_STATUS,"EPG data up to date !")
#        ml.signalMessage(ml.MODE_EPG,False)

    def _getEPGDevice(self):
        return DVBDevice.getGrabber(self.channelList, self.configuration)

    def persistEPGData(self):
        self.configuration.logInfo("Saving data...")
        epgUpdater = self.progProvider.getEPGUpdater()
        self.progProvider.getAutoSelector().saveAutoSelectData()
        epgUpdater.persistDatabase()
        if epgUpdater.hasError():
            self.configuration.logError("Error saving xml data")
class RecorderDaemon():
    LOG_FILE = "dvb_suspend.log"
    HEARTBEAT = 60
    DAY_IN_SECONDS = 60*60*24
    EPG_UPDATE_INTERVAL = DAY_IN_SECONDS+10
    EPG_TS_Template = '%Y%m%d%H%M%S'
    
    def __init__(self,):
        self._config = Config()
        self._setUpLogging()
        self.epgUpdater = EpgUpdater(self._config)
        OSTools.ensureDirectory(self._config.getRecordingPath(),'')
        self._inhibitor = OSTools.Inhibitor()
        self._recordCmd = DVBDevice.getRecordCommander()
        self._lastJobId="0"
        self._recordPartIndex=0
        self.isActive=True
        self._daemonPolicy = None
   

    def _getNextJob(self):
        return self.epgUpdater.getRecordQueue().getNextRecordingEntry()

    def _updateEPGData(self):
        days = self._getDaysSinceLastEPGUpdate()
        if days == 0:
            self._log("EPG is up to date")
            return False
        self._log("Last EPG update %s day(s) ago - Updating now.."%str(days))
        self.epgUpdater.updateDatabase()
        if  self.epgUpdater.hasError():
            self._log("EPG Update failed: "+self.epgUpdater.getErrorMessage())
            return False

        self.epgUpdater.persistDatabase()
        currentDate= datetime.now().strftime(self.EPG_TS_Template)
        path = self._config.getEPGTimestampPath()
        with open(path,'w') as f:
            f.write(currentDate)
        return True
    
    '''
    Enforce a epg data read
    '''
    def readEPGData(self):
        path = self._config.getEPGTimestampPath() 
        if OSTools.fileExists(path):
            OSTools.removeFile(path)
        self._updateEPGData()
    

    def _getDaysSinceLastEPGUpdate(self):
        path = self._config.getEPGTimestampPath()
        if OSTools.fileExists(path):
            with open(path, 'r') as aFile:
                currentDate=aFile.read()
            try:
                checkDate = datetime.strptime(currentDate,self.EPG_TS_Template)
                delta = datetime.now()-checkDate
                return delta.days
            except ValueError:
                self._log("Error reading EPG Timestamp:"+str(currentDate))
        return -1

    def _isTimeLeftForEPGUpdate(self,nextStartTime):
        now = datetime.now()
        seconds = OSTools.getDifferenceInSeconds(nextStartTime, now)
        return seconds > self.HEARTBEAT*10

    def _secondsUntilNextRun(self,startTime,prerunSeconds):
        now = datetime.now()
        secondsUntilStart = OSTools.getDifferenceInSeconds(startTime, now)
        secondsToSleep = secondsUntilStart - prerunSeconds #wake up a minute+ before
        if secondsToSleep < prerunSeconds:
            self._log("Next run in less than %s seconds"%(str(secondsUntilStart))) #sleep until time is ready.... no hibernate
            if secondsUntilStart > 0:
                self._log("Waiting to launch...."+str(secondsUntilStart))
                xtime.sleep(secondsUntilStart)
            return 0
        return secondsToSleep
    
    
    def launchRecording(self,recInfo):
        #As opposed to a previous version no AT queue is used. Reason: recording needs to be supervised 
        #anyhow, so there is need for a supervising/scheduling process
        channel = recInfo.getEPGInfo().getChannel()
        OSTools.ensureDirectory(self._config.getRecordingPath(),channel.getEscapedName())
        jobID= recInfo.getEPGInfo().getJobID()
        self._syncRecordIndex(jobID)
        scheduler= Recorder(self._config,self._recordCmd,self._recordPartIndex)
        return scheduler.scheduleRecording(recInfo)            

    def _stopQueue(self):
        self.isActive=False
        
    def _exit(self):
        self._inhibitor.inhibit_gnome_screensaver(False)
        self._stopQueue()
        self._log("exit daemon")
        logging.shutdown()


    def _setUpLogging(self):
        path = self.LOG_FILE
        self._config.setupLogging(path)
        
    def _log(self,aString):
        self._getLogger().log(logging.INFO,aString)
        print(aString)

    def _getLogger(self):
        return logging.getLogger('dvb_scheduler')
    
    
    def _hasRecordingProcess(self):
        pidInfo = OSTools.getProcessPID(self._recordCmd.Command)
        if pidInfo is None:
            xtime.sleep(2) # in case of adjacent films
            pidInfo = OSTools.getProcessPID(self._recordCmd.Command)
  
        return pidInfo is not None 

    '''
    If that recInfo has been killed previously due to a bad reception increment the recordIndex which will
    be used for a unique title for a retry (therefore not overwriting already existing recordings)
    '''        
    def _syncRecordIndex(self,jobID):
        if self._lastJobId == jobID and int(jobID) > 1:
            self._recordPartIndex=self._recordPartIndex+1
        else:
            self._recordPartIndex=0
            self._lastJobId = jobID
            

    def _monitorCurrentRecording(self,recProcess,recordingJob):
        done = False
        #this is testing:
        emergencyCount=0;
        
        jobID=recordingJob.getEPGInfo().getJobID()
        isRecurrentWriteError=False
        recPath = self._config.getRecordingPath()
        videoSize=OSTools.getDirectorySize(recPath)
        xtime.sleep(self.HEARTBEAT)
        self._log("Monitoring JOB "+jobID)
        while not done:
            result = recProcess.poll()
            isAlive = result is None
            if isAlive:
                currentSize = OSTools.getDirectorySize(recPath)
                delta = currentSize - videoSize
                print(("JOB "+jobID+" - bytes written:"+str(delta))) #live sign- not logging
                videoSize = currentSize
                if delta == 0:
                    self._log("JOB "+jobID+" does not write any data")
                    if isRecurrentWriteError:
                        done=True
                        self._log("Terminating Rec process, preventing reschedule.. ")
                        recProcess.terminate()
                        self.__handleProcessTermination(recProcess)
                    isRecurrentWriteError=True #only on retry permitted
            else:
                self._log("Quit JOB "+jobID)
                self.__handleProcessTermination(recProcess)
                done=True
                
            if not done:
                #Ensure that an adjacent job can follow - decrease the wait time
                gludu=recordingJob.getGluedDurance()
                endtime = OSTools.addToDateTime(recordingJob.getExecutionTime(),gludu)
                delta = max(10,OSTools.getDifferenceInSeconds(endtime,datetime.now()))
                sleepTime = min(self.HEARTBEAT,delta)
                if sleepTime != self.HEARTBEAT:
                    emergencyCount+=1;
                    endTimeString=endtime.strftime("%H:%M.%S")
                    startTimeString = recordingJob.getExecutionTime().strftime("%H:%M.%S")
                    self._log("Stopping in seconds: %d (Info Start: %s expected end: %s with dur %d)"%(sleepTime,startTimeString,endTimeString,gludu))#log only the fragments
                    if emergencyCount > 10:
                        self._log("REC Q error- force process termination")
                        recProcess.terminate()
                        self.__handleProcessTermination(recProcess)
                        done=True
                xtime.sleep(sleepTime)
                
        self._log("JOB "+jobID+" is done")
        OSTools.syncFiles()
    
    def __handleProcessTermination(self,recProcess):
        result=recProcess.communicate()
        potentialErrorMessage = result[1].decode('utf8').strip()
        msg =">>"+result[0].decode('utf8').strip()+" status:"+potentialErrorMessage
        #TODO: check for errors ggf boolean back - cancel recording if necessary
        if "ERROR" in potentialErrorMessage:
            print("TODO Unhandled {c,t}ZAP error!")
        self._log(msg)
        
    
    def _initializePolicy(self):
        if Config.DAEMON_POLICY == Config.POLICY_SERVER:
            self._setServerPolicy()
        else:
            self._setVCRPolicy()
    
    def _setServerPolicy(self):
        self._daemonPolicy = ServerPolicy(self)

    def _setVCRPolicy(self):
        self._daemonPolicy = VCRPolicy(self)
        
    
    def _isPolicyChangeRequested(self,markerFile):
        if OSTools.fileExists(markerFile):
            OSTools.removeFile(markerFile)
            return True
        return False
    
    '''
    if a file is present with the name of "SaveEnergy" remove it and switch to VCR mode
    '''
    def isVCRPolicyChangeRequested(self):
        markerFile= self._config.getEnergySaverFileMarker()
        if self._isPolicyChangeRequested(markerFile):
            self._setVCRPolicy()
            return True
        return False
    
    def isServerPolicyChangeRequested(self):
        markerFile= self._config.getServerFileMarker()
        if self._isPolicyChangeRequested(markerFile):
            self._setServerPolicy()
            return True
        return False
    
    
    
    
    def _startDaemon(self):
        errorCount =0;
        while self.isActive and errorCount < 10:
            try:
                #Always a job, possibly a maintenance for epg update
                nextJob =self._getNextJob()
                startTime = nextJob.getExecutionTime()
                if self._isTimeLeftForEPGUpdate(startTime):
                    if self._updateEPGData():
                        continue #maybe a fresh job came in...
                secondsToSleep = self._secondsUntilNextRun(startTime,self._daemonPolicy.PRERUN_SECONDS)
                if self._daemonPolicy.isReadyToRecord(startTime,secondsToSleep):
                    if nextJob.getEPGInfo() is None:
                        self._log("Maintenance mode- will update EPG")
                        continue;
                    process = self.launchRecording(nextJob)
                    self._monitorCurrentRecording(process,nextJob)

            except KeyboardInterrupt:
                print('^C received, shutting down daemon')
                self._exit()

            except Exception as ex:
                msg= "Error running SuspensionDaemon: "+str(ex.args[0])
                self._getLogger().exception(msg)
                print(msg)
                errorCount = errorCount + 1
            
    def run(self):
        msg= self.epgUpdater.getErrorMessage()
        if msg:
            print("EPG Error:",msg)
        else:
            self._log("Starting daemon..")
            self._inhibitor.inhibit_gnome_screensaver(True)
            self.epgUpdater.readEPGCache()
            self._initializePolicy()
            self._startDaemon()