void cMenuSearchMain::SetHelpKeys(bool Force) { cMenuMyScheduleItem *item = (cMenuMyScheduleItem *)Get(Current()); int NewHelpKeys = 0; if (item) { if (item->Selectable() && item->timerMatch == tmFull) NewHelpKeys = 2; else NewHelpKeys = 1; } bool hasTimer = (NewHelpKeys == 2); if (NewHelpKeys != helpKeys || Force) { if (toggleKeys==0) SetHelp((EPGSearchConfig.redkeymode==0?(hasTimer?trVDR("Button$Timer"):trVDR("Button$Record")):tr("Button$Commands")), trVDR("Button$Now"), trVDR("Button$Next"), EPGSearchConfig.bluekeymode==0?trVDR("Button$Switch"):tr("Button$Search")); else { const char* szGreenToggled = CHANNELNAME(Channels.GetByNumber(currentChannel-1,-1)); const char* szYellowToggled = CHANNELNAME(Channels.GetByNumber(currentChannel+1,1)); SetHelp((EPGSearchConfig.redkeymode==1?(hasTimer?trVDR("Button$Timer"):trVDR("Button$Record")):tr("Button$Commands")), (EPGSearchConfig.toggleGreenYellow==0?trVDR("Button$Now"):szGreenToggled), (EPGSearchConfig.toggleGreenYellow==0?trVDR("Button$Next"):szYellowToggled), EPGSearchConfig.bluekeymode==1?trVDR("Button$Switch"):tr("Button$Search")); } helpKeys = NewHelpKeys; } }
char* cSearchTimerThread::SummaryExtended(cSearchExt* searchExt, cTimer* Timer, const cEvent* pEvent) { bool UseVPS = searchExt->useVPS && pEvent->Vps() && Setup.UseVps; time_t eStart; if (!UseVPS) eStart = pEvent->StartTime(); else eStart = pEvent->Vps(); time_t eStop; if (!UseVPS) eStop = pEvent->EndTime(); else eStop = pEvent->Vps() + pEvent->Duration(); // make sure that eStart and eStop represent a full minute eStart = (eStart / 60) * 60; eStop = (eStop / 60) * 60; time_t start = eStart - (UseVPS?0:(searchExt->MarginStart * 60)); time_t stop = eStop + (UseVPS?0:(searchExt->MarginStop * 60)); char* addSummaryFooter = NULL; msprintf(&addSummaryFooter, "<channel>%d - %s</channel><searchtimer>%s</searchtimer><start>%ld</start><stop>%ld</stop><s-id>%d</s-id><eventid>%ld</eventid>", Timer->Channel()->Number(), CHANNELNAME(Timer->Channel()), searchExt->search, start, stop, searchExt->ID, (long) pEvent->EventID()); const char* aux = Timer->Aux(); // remove epgsearch entries char* tmpaux = NULL; if (!isempty(aux)) { tmpaux = strdup(aux); const char* begin = strstr(aux, "<epgsearch>"); const char* end = strstr(aux, "</epgsearch>"); if (begin && end) { if (begin == aux) strcpy(tmpaux, ""); else strn0cpy(tmpaux, aux, begin-aux+1); strcat(tmpaux, end + strlen("</epgsearch>")); } } char* tmpSummary = NULL; msprintf(&tmpSummary, "<epgsearch>%s</epgsearch>%s", addSummaryFooter, tmpaux?tmpaux:""); free(addSummaryFooter); if (tmpaux) free(tmpaux); return tmpSummary; }
void cConflictCheck::EvaluateConflCheckCmd() { if (strlen(EPGSearchConfig.conflCheckCmd) > 0) { LogFile.Log(2,"evaluating conflict check command '%s'", EPGSearchConfig.conflCheckCmd); for(cConflictCheckTime* ct = failedList->First(); ct; ct = failedList->Next(ct)) { if (ct->ignore) continue; std::set<cConflictCheckTimerObj*,TimerObjSort>::iterator it; for (it = ct->failedTimers.begin(); it != ct->failedTimers.end(); ++it) if ((*it) && !(*it)->ignore) { string result = EPGSearchConfig.conflCheckCmd; if (!(*it)->OrigTimer()) { LogFile.Log(3,"timer has disappeared meanwhile"); continue; } else LogFile.Log(3,"evaluating conflict check command for timer '%s' (%s, channel %s)", (*it)->timer->File(), DAYDATETIME((*it)->start), CHANNELNAME((*it)->timer->Channel())); if ((*it)->Event()) { cVarExpr varExprEvent(result); result = varExprEvent.Evaluate((*it)->Event()); } cVarExpr varExprTimer(result); result = varExprTimer.Evaluate((*it)->timer); cVarExpr varExpr(result); varExpr.Evaluate(); } } } }
void cConflictCheck::AddConflict(cConflictCheckTimerObj* TimerObj, cConflictCheckTime* CheckTime, std::set<cConflictCheckTimerObj*>& pendingTimers) { for(cConflictCheckTimerObj* concTimer= timerList->First(); concTimer; concTimer = timerList->Next(concTimer)) { if (concTimer->start >= TimerObj->stop) continue; if (concTimer->stop <= TimerObj->start) continue; if (!TimerObj->concurrentTimers) TimerObj->concurrentTimers = new std::set<cConflictCheckTimerObj*,TimerObjSort>; TimerObj->concurrentTimers->insert(concTimer); } TimerObj->ignore = (TimerObj->timer->Priority() < EPGSearchConfig.checkMinPriority) || TimerObj->start > maxCheck; CheckTime->concurrentRecs.insert(TimerObj); pendingTimers.insert(TimerObj); TimerObj->lastRecStop = CheckTime->evaltime; if (TimerObj->lastRecStart > 0 && TimerObj->lastRecStart < TimerObj->lastRecStop) { TimerObj->recDuration += TimerObj->lastRecStop - TimerObj->lastRecStart; TimerObj->lastRecStart = 0; if ((TimerObj->stop - TimerObj->start - TimerObj->recDuration) < EPGSearchConfig.checkMinDuration * 60) TimerObj->ignore = true; } TimerObj->device = -1; if (!TimerObj->conflCheckTime) TimerObj->conflCheckTime = CheckTime; else return; CheckTime->failedTimers.insert(TimerObj); LogFile.Log(3,"conflict found for timer '%s' (%s, channel %s)", TimerObj->timer->File(), DAYDATETIME(TimerObj->start), CHANNELNAME(TimerObj->timer->Channel())); }
// checks for conflicts at one special time int cConflictCheck::ProcessCheckTime(cConflictCheckTime* checkTime) { if (!checkTime) return 0; LogFile.Log(3,"check time %s", DAYDATETIME(checkTime->evaltime)); LogFile.Log(3,"detach stopping timers"); int Conflicts = 0; // detach all stopping timers from their devices std::set<cConflictCheckTimerObj*,TimerObjSort>::iterator it; for (it = checkTime->stoppingTimers.begin(); it != checkTime->stoppingTimers.end(); ++it) if ((*it) && (*it)->device >= 0) { LogFile.Log(3,"detach device %d from timer '%s' (%s, channel %s) at %s", ((*it)->device)+1, (*it)->timer->File(), DAYDATETIME((*it)->start), CHANNELNAME((*it)->timer->Channel()), DAYDATETIME(checkTime->evaltime)); devices[(*it)->device].recTimers.erase(*it); (*it)->lastRecStop = checkTime->evaltime; if ((*it)->lastRecStart > 0 && (*it)->lastRecStart < (*it)->lastRecStop) { (*it)->recDuration += (*it)->lastRecStop - (*it)->lastRecStart; (*it)->lastRecStart = 0; if (((*it)->stop - (*it)->start - (*it)->recDuration) < EPGSearchConfig.checkMinDuration * 60) (*it)->ignore = true; } } LogFile.Log(3,"add pending timers"); // if we have pending timers add them to the current start list for (it = pendingTimers.begin(); it != pendingTimers.end(); ++it) { if ((*it) && (*it)->stop > checkTime->evaltime) checkTime->startingTimers.insert(*it); pendingTimers.erase(*it); } LogFile.Log(3,"attach starting timers"); // handle starting timers for (it = checkTime->startingTimers.begin(); it != checkTime->startingTimers.end(); ++it) { bool NeedsDetachReceivers = false; if (!(*it) || (*it)->device >= 0) continue; // already has a device int device = GetDevice(*it, &NeedsDetachReceivers); if (device >= 0) // device will be attached? { if (NeedsDetachReceivers) // but needs to detach all others? { // disable running timers std::set<cConflictCheckTimerObj*,TimerObjSort>::iterator it2 = devices[device].recTimers.begin(); for(; it2 != devices[device].recTimers.end(); ++it2) { LogFile.Log(3,"stopping timer '%s' (%s, channel %s) at %s on device %d because of higher priority", (*it2)->timer->File(), DAYDATETIME((*it2)->start), CHANNELNAME((*it2)->timer->Channel()), DAYDATETIME(checkTime->evaltime), device+1); AddConflict((*it2), checkTime, pendingTimers); devices[device].recTimers.erase(*it2); Conflicts++; } } devices[device].recTimers.insert(*it); (*it)->device = device; (*it)->lastRecStart = checkTime->evaltime; LogFile.Log(3,"recording timer '%s' (%s, channel %s) at %s on device %d", (*it)->timer->File(), DAYDATETIME((*it)->start), CHANNELNAME((*it)->timer->Channel()), DAYDATETIME(checkTime->evaltime), device+1); } else { AddConflict((*it), checkTime, pendingTimers); Conflicts++; } } LogFile.Log(3,"check time %s - done", DAYDATETIME(checkTime->evaltime)); return Conflicts; }
cList<cConflictCheckTimerObj>* cConflictCheck::CreateCurrentTimerList() { LogFile.Log(3,"current timer list creation started"); cList<cConflictCheckTimerObj>* CurrentTimerList = NULL; // collect single event timers time_t tMax = 0; cTimer* ti = NULL; for (ti = Timers.First(); ti; ti = Timers.Next(ti)) { tMax = max(tMax, ti->StartTime()); if (!ti->IsSingleEvent()) continue; // already recording? int deviceNr = gl_recStatusMonitor->TimerRecDevice(ti)-1; // create a copy of this timer cTimer* clone = new cTimer(*ti); clone->SetEvent(ti->Event()); cConflictCheckTimerObj* timerObj = new cConflictCheckTimerObj(clone, ti->StartTime(), ti->StopTime(), deviceNr, ti->Index()); if (deviceNr >= 0) { devices[deviceNr].recTimers.insert(timerObj); timerObj->lastRecStart = ti->StartTime(); } LogFile.Log(3,"add timer '%s' (%s, channel %s) for conflict check", ti->File(), DAYDATETIME(ti->StartTime()), CHANNELNAME(ti->Channel())); if (deviceNr >= 0) LogFile.Log(3,"timer already recording since %s on device %d", DAYDATETIME(ti->StartTime()), deviceNr+1); if (!CurrentTimerList) CurrentTimerList = new cList<cConflictCheckTimerObj>; CurrentTimerList->Add(timerObj); } // collect repeating timers from now until the date of the timer with tMax time_t maxCheck = time(NULL) + min(14,EPGSearchConfig.checkMaxDays) * SECSINDAY; tMax = max(tMax, maxCheck); for (ti = Timers.First(); ti; ti = Timers.Next(ti)) { if (ti->IsSingleEvent()) continue; time_t day = time(NULL); while(day < tMax) { if (ti->DayMatches(day)) { time_t Start = cTimer::SetTime(day, cTimer::TimeToInt(ti->Start())); int deviceNr = -1; if (Start < time(NULL)) { #ifndef DEBUG_CONFL if (ti->Recording()) deviceNr = gl_recStatusMonitor->TimerRecDevice(ti)-1; #else if (Start + ti->StopTime() - ti->StartTime() > time(NULL)) deviceNr = 0; #endif if (deviceNr == -1) // currently not recording, skip it { day += SECSINDAY; continue; } } else if (Start < ti->StartTime()) { day += SECSINDAY; continue; } // create a copy of this timer cTimer* clone = new cTimer(*ti); clone->SetEvent(ti->Event()); cConflictCheckTimerObj* timerObj = new cConflictCheckTimerObj(clone, Start, Start + ti->StopTime() - ti->StartTime(), deviceNr, ti->Index()); LogFile.Log(3,"add timer '%s' (%s, channel %s) for conflict check", ti->File(), DAYDATETIME(Start), CHANNELNAME(ti->Channel())); if (deviceNr >= 0) { LogFile.Log(3,"timer already recording since %s on device %d", DAYDATETIME(Start), deviceNr+1); devices[deviceNr].recTimers.insert(timerObj); timerObj->lastRecStart = Start; } if (!CurrentTimerList) CurrentTimerList = new cList<cConflictCheckTimerObj>; CurrentTimerList->Add(timerObj); } day += SECSINDAY; } } if (CurrentTimerList) CurrentTimerList->Sort(); LogFile.Log(3,"current timer list created"); return CurrentTimerList; }
void cConflictCheck::Check() { if (evaltimeList) DELETENULL(evaltimeList); if (timerList) DELETENULL(timerList); timerList = CreateCurrentTimerList(); if (timerList) evaltimeList = CreateEvaluationTimeList(timerList); if (evaltimeList) failedList = CreateConflictList(evaltimeList, timerList); if (failedList) { for(cConflictCheckTime* checkTime = failedList->First(); checkTime; checkTime = failedList->Next(checkTime)) { LogFile.Log(2,"result of conflict check for %s:", DAYDATETIME(checkTime->evaltime)); std::set<cConflictCheckTimerObj*,TimerObjSort>::iterator it; for (it = checkTime->failedTimers.begin(); it != checkTime->failedTimers.end(); ++it) LogFile.Log(2,"timer '%s' (%s, channel %s) failed", (*it)->timer->File(), DAYDATETIME((*it)->timer->StartTime()), CHANNELNAME((*it)->timer->Channel())); } } if (numConflicts > 0 && gl_timerStatusMonitor) gl_timerStatusMonitor->SetConflictCheckAdvised(); }
void cSwitchTimerThread::Action(void) { m_Active = true; // let VDR do its startup if (!cPluginEpgsearch::VDR_readyafterStartup) LogFile.Log(2, "SwitchTimerThread: waiting for VDR to become ready..."); while(m_Active && !cPluginEpgsearch::VDR_readyafterStartup) Wait.Wait(1000); time_t nextUpdate = time(NULL); while (m_Active) { time_t now = time(NULL); if (now >= nextUpdate) { LogFile.Log(3,"locking switch timers"); SwitchTimers.Lock(); LogFile.Log(3,"switch timer check started"); cSwitchTimer* switchTimer = SwitchTimers.First(); while (switchTimer && m_Active && Running()) { if (switchTimer->startTime - now < switchTimer->switchMinsBefore*60 + MSG_DELAY + 1) { cChannel *channel = Channels.GetByChannelID(switchTimer->channelID, true, true); bool doSwitch = (switchTimer->mode == 0); bool doAsk = (switchTimer->mode == 2); bool doUnmute = switchTimer->unmute; SwitchTimers.Del(switchTimer); const cEvent* event = switchTimer->Event(); if (event && channel && (event->EndTime() >= now)) { cString Message = cString::sprintf("%s: %s - %s", event->Title(), CHANNELNAME(channel), GETTIMESTRING(event)); cString SwitchCmd = cString::sprintf("CHAN %d", channel->Number()); // switch if (doSwitch) { LogFile.Log(1,"switching to channel %d", channel->Number()); if (cDevice::CurrentChannel() != channel->Number()) SendViaSVDRP(SwitchCmd); if (doUnmute && cDevice::PrimaryDevice()->IsMute()) cDevice::PrimaryDevice()->ToggleMute(); } if (!doAsk) SendMsg(Message); if (doAsk) { cString Message = cString::sprintf(tr("Switch to (%d) '%s'?"), channel->Number(), event->Title()); if(SendMsg(Message, true, MSG_DELAY) == kOk) { LogFile.Log(1,"switching to channel %d", channel->Number()); if (cDevice::CurrentChannel() != channel->Number()) SendViaSVDRP(SwitchCmd); if (doUnmute && cDevice::PrimaryDevice()->IsMute()) cDevice::PrimaryDevice()->ToggleMute(); } } if (m_Active && Running()) Wait.Wait(1000 * MSG_DELAY); } SwitchTimers.Save(); break; } switchTimer = SwitchTimers.Next(switchTimer); } SwitchTimers.Unlock(); LogFile.Log(3,"switch timer check finished"); if (m_Active && Running()) Wait.Wait(1000 * MSG_DELAY); m_lastUpdate = time(NULL); nextUpdate = long(m_lastUpdate/60)*60+ 60 - MSG_DELAY ; // check at each full minute minus 5sec if (SwitchTimers.Count() == 0) m_Active = false; if (!Running()) m_Active = false; } while (m_Active && time(NULL)%MSG_DELAY != 0) // sync heart beat to MSG_DELAY Wait.Wait(1000); Wait.Wait(1000); }; m_Instance = NULL; }
void cSearchTimerThread::CheckManualTimers() { LogFile.Log(1, "manual timer check started"); cSchedulesLock schedulesLock; const cSchedules *schedules; schedules = cSchedules::Schedules(schedulesLock); for (cTimer *ti = Timers.First(); ti && m_Active; ti = Timers.Next(ti)) { if (TriggeredFromSearchTimerID(ti) != -1) continue; // manual timer? if (TimerWasModified(ti)) { LogFile.Log(2,"timer for '%s' (%s, channel %s) modified by user - won't be touched", ti->File(), DAYDATETIME(ti->StartTime()), CHANNELNAME(ti->Channel())); continue; // don't update timers modified by user } char* szbstart = GetAuxValue(ti, "bstart"); int bstart = szbstart? atoi(szbstart) : 0; free(szbstart); char* szbstop = GetAuxValue(ti, "bstop"); int bstop = szbstop? atoi(szbstop) : 0; free(szbstop); // how to check? char* updateMethod = GetAuxValue(ti, "update"); if (updateMethod && atoi(updateMethod) == UPD_EVENTID) // by event ID? { // get the channels schedule const cSchedule* schedule = schedules->GetSchedule(ti->Channel()); if (schedule) { tEventID eventID = 0; char* szEventID = GetAuxValue(ti, "eventid"); if (szEventID) eventID = atol(szEventID); LogFile.Log(3,"checking manual timer %d by event ID %u", ti->Index()+1, eventID); const cEvent* event = schedule->GetEvent(eventID); if (event) { if (event->StartTime() - bstart != ti->StartTime() || event->EndTime() + bstop != ti->StopTime()) ModifyManualTimer(event, ti, bstart, bstop); } else LogFile.Log(1,"ooops - no event found with id %u for manual timer %d", eventID, ti->Index()+1); if (szEventID) free(szEventID); } } if (updateMethod && atoi(updateMethod) == UPD_CHDUR) // by channel and time? { // get the channels schedule const cSchedule* schedule = schedules->GetSchedule(ti->Channel()); if (schedule) { // collect all events touching the old timer margins cSearchResults eventlist; for (cEvent *testevent = schedule->Events()->First(); testevent; testevent = schedule->Events()->Next(testevent)) { if (testevent->StartTime() < ti->StopTime() && testevent->EndTime() > ti->StartTime()) eventlist.Add(new cSearchResult(testevent, (const cSearchExt*)NULL)); } LogFile.Log(3,"checking manual timer %d by channel and time, found %d candidates", ti->Index()+1, eventlist.Count()); if (eventlist.Count() > 0) { // choose the event with the best match by duration long origlen = (ti->StopTime() - bstop) - (ti->StartTime() + bstart); double maxweight = 0; const cEvent* event = eventlist.First()->event; for (cSearchResult* pResultObj = eventlist.First(); pResultObj; pResultObj = eventlist.Next(pResultObj)) { const cEvent* testevent = pResultObj->event; time_t start = (testevent->StartTime() < ti->StartTime()) ? ti->StartTime() : testevent->StartTime(); time_t stop = (testevent->EndTime() > ti->StopTime()) ? ti->StopTime() : testevent->EndTime(); double weight = double(stop - start) / double(testevent->EndTime() - testevent->StartTime()); LogFile.Log(3,"candidate '%s~%s' (%s - %s) timer match: %f, duration match: %f", testevent->Title(), testevent->ShortText()?testevent->ShortText():"", GETDATESTRING(testevent), GETTIMESTRING(testevent), weight, (double(testevent->EndTime() - testevent->StartTime()) / origlen)); if (weight > maxweight && (double(testevent->EndTime() - testevent->StartTime()) / origlen) >= 0.9) { maxweight = weight; event = testevent; } } LogFile.Log(3,"selected candidate is '%s~%s' (%s - %s)", event->Title(), event->ShortText()?event->ShortText():"", GETDATESTRING(event), GETTIMESTRING(event)); if ((maxweight > 0 && event->StartTime() - bstart != ti->StartTime()) || (event->EndTime() + bstop != ti->StopTime())) ModifyManualTimer(event, ti, bstart, bstop); else if (maxweight <= 0) LogFile.Log(3,"selected candidate is too bad"); } else LogFile.Log(1,"ooops - no events found touching manual timer %d", ti->Index()+1); } if (updateMethod) free(updateMethod); } } LogFile.Log(1, "manual timer check finished"); }
void cSearchTimerThread::Action(void) { if (EPGSearchConfig.useExternalSVDRP && !cSVDRPClient::SVDRPSendCmd) { LogFile.eSysLog("ERROR - SVDRPSend script not specified or does not exist (use -f option)"); return; } SetPriority(SEARCHTIMER_NICE); m_Active = true; // let VDR do its startup if (!cPluginEpgsearch::VDR_readyafterStartup) LogFile.Log(2, "SearchTimerThread: waiting for VDR to become ready..."); while(Running() && m_Active && !cPluginEpgsearch::VDR_readyafterStartup) Wait.Wait(1000); time_t nextUpdate = time(NULL); while (m_Active && Running()) { time_t now = time(NULL); bool needUpdate = NeedUpdate(); if (now >= nextUpdate || needUpdate) { justRunning = true; if (updateForced & UPDS_WITH_EPGSCAN) { LogFile.Log(1,"starting EPG scan before search timer update"); EITScanner.ForceScan(); do { Wait.Wait(1000); } while(EITScanner.Active() && m_Active && Running()); LogFile.Log(1,"EPG scan finished"); } if (Timers.BeingEdited()) { Wait.Wait(1000); continue; } LogFile.iSysLog("search timer update started"); UserVars.ResetCache(); // reset internal cache of user vars cTimerObjList* pOutdatedTimers = NULL; // for thread safeness we work with a copy of the current searches, // because SVDRP would not work if the main thread would be locked cSearchExts* localSearchExts = SearchExts.Clone(); localSearchExts->SortBy(CompareSearchExtPrioDescTerm); cSearchExt *searchExt = localSearchExts->First(); // reset announcelist announceList.Clear(); while (searchExt && m_Active && Running()) { if (!searchExt->IsActiveAt(now)) { searchExt = localSearchExts->Next(searchExt); continue; } pOutdatedTimers = searchExt->GetTimerList(pOutdatedTimers); cSearchResults* pSearchResults = searchExt->Run(-1, true); if (!pSearchResults) { searchExt = localSearchExts->Next(searchExt); continue; } pSearchResults->SortBy(CompareEventTime); if (searchExt->pauseOnNrRecordings > 0) searchExt->CheckExistingRecordings(pSearchResults); for (cSearchResult* pResultObj = pSearchResults->First(); pResultObj; pResultObj = pSearchResults->Next(pResultObj)) { if (!Running()) break; const cEvent* pEvent = pResultObj->event; if (!pEvent) continue; cChannel *channel = Channels.GetByChannelID(pEvent->ChannelID(), true, true); if (!channel) continue; int index = 0; cTimer *timer = new cTimer(pEvent); // create the file char* file = NULL; if ((file = searchExt->BuildFile(pEvent)) != NULL) { while(strstr(file, "!^pipe^!")) file = strreplace(file, "!^pipe^!", "|"); // revert the translation of '|' in BuildFile if (strstr(file, "!^invalid^!") || strlen(file) == 0) { LogFile.eSysLog("Skipping timer due to invalid or empty filename"); if (time(NULL) <= timer->StopTime()) pOutdatedTimers->DelTimer(timer); delete timer; free(file); continue; } timer->SetFile(file); free(file); } int Priority = searchExt->Priority; int Lifetime = searchExt->Lifetime; // search for an already existing timer bool bTimesMatchExactly = false; cTimer *t = GetTimer(searchExt, pEvent, bTimesMatchExactly); char* Summary = NULL; uint timerMod = tmNoChange; if (t) { // already exists pOutdatedTimers->DelTimer(t); if (!t->HasFlags(tfActive)) { // do not update inactive timers LogFile.Log(2,"timer for '%s~%s' (%s - %s, channel %d) not active - won't be touched", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent)); delete timer; continue; } int triggerID = TriggeredFromSearchTimerID(t); if (!pResultObj->needsTimer && !t->Recording()) // not needed { if (triggerID == searchExt->ID) { LogFile.Log(1,"delete timer for '%s~%s' (%s - %s, channel %d)", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent)); RemoveTimer(t, pEvent); } else if (triggerID == -1) //manual timer { LogFile.Log(2,"keep obsolete timer for '%s~%s' (%s - %s, channel %d) - was manually created", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent)); } delete timer; continue; } if (TimerWasModified(t)) // don't touch timer modified by user { LogFile.Log(2,"timer for '%s~%s' (%s - %s, channel %d) modified by user - won't be touched", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent)); delete timer; continue; } if (triggerID > -1 && triggerID != searchExt->ID) { LogFile.Log(2,"timer for '%s~%s' (%s - %s, channel %d) already created by search id %d - won't be touched", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), triggerID); delete timer; continue; } char* pFile = NULL; // File is prepared for svdrp, so prepare t->File for comparision too msprintf(&pFile, "%s", t->File()); pFile = strreplace(pFile, ':', '|'); pFile = strreplace(pFile, " ~", "~"); pFile = strreplace(pFile, "~ ", "~"); Summary = SummaryExtended(searchExt, t, pEvent); if (bTimesMatchExactly && strcmp(pFile, timer->File()) == 0 && (t->Aux() != NULL && strcmp(t->Aux(), Summary) == 0) ) { // dir, title, episode name and summary have not changed if (Summary) free(Summary); delete timer; free(pFile); continue; } else { if (!bTimesMatchExactly) timerMod = (uint)timerMod | tmStartStop; if (strcmp(pFile, timer->File()) != 0) timerMod |= tmFile; if (t->Aux() != NULL && strcmp(t->Aux(), Summary) != 0) { char* oldEventID = GetAuxValue(t, "eventid"); char* newEventID = GetAuxValue(Summary, "eventid"); if (oldEventID && newEventID && strcmp(oldEventID, newEventID) != 0) timerMod |= tmAuxEventID; free(oldEventID); free(newEventID); } if (LogFile.Level() >= 3) // output reasons for a timer modification { if (timerMod & tmStartStop) LogFile.Log(3,"timer for '%s~%s' (%s - %s, channel %d) : start/stop has changed", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent)); if (timerMod & tmFile) LogFile.Log(3,"timer for '%s~%s' (%s - %s, channel %d) : title and/or episdode has changed (old: %s, new: %s", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), timer?timer->File():"", pFile); if (timerMod & tmAuxEventID) LogFile.Log(3,"timer for '%s~%s' (%s - %s, channel %d) : aux info for event id has changed", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent)); } index = t->Index()+1; Priority = t->Priority(); Lifetime = t->Lifetime(); } free(pFile); if (t->Recording() && t->StopTime() == timer->StopTime()) { // only update recording timers if stop time has changed, since all other settings can't be modified LogFile.Log(2,"timer for '%s~%s' (%s - %s, channel %d) already recording - no changes possible", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent)); delete timer; continue; } } else { if (!pResultObj->needsTimer) { delete timer; continue; } } if (searchExt->action == searchTimerActionAnnounceViaOSD) { if (t || // timer already exists or NoAnnounces.InList(pEvent) || // announcement not wanted anymore or (EPGSearchConfig.noAnnounceWhileReplay && cDevice::PrimaryDevice()->Replaying() && !(updateForced & UPDS_WITH_OSD)) // no announce while replay within automatic updates ) { if (Summary) free(Summary); delete timer; continue; } if (!announceList.Lookup(pEvent)) announceList.Add(new cSearchResult(pEvent, searchExt->ID)); if (Summary) free(Summary); delete timer; continue; } if (searchExt->action == searchTimerActionAnnounceViaMail) { if (t || // timer already exists or NoAnnounces.InList(pEvent) || pEvent->StartTime() < time(NULL)) // already started? { if (Summary) free(Summary); delete timer; continue; } mailNotifier.AddAnnounceEventNotification(pEvent->EventID(), pEvent->ChannelID(), searchExt->ID); if (Summary) free(Summary); delete timer; continue; } if (searchExt->action == searchTimerActionSwitchOnly || searchExt->action == searchTimerActionAnnounceAndSwitch) // add to switch list { time_t now = time(NULL); if (now < pEvent->StartTime()) { if (!SwitchTimers.InSwitchList(pEvent)) { cMutexLock SwitchTimersLock(&SwitchTimers); int mode = 0; if (searchExt->action == searchTimerActionAnnounceAndSwitch) mode = 2; LogFile.Log(3,"adding switch timer event for '%s~%s' (%s - %s); search timer: '%s'", pEvent->Title(), pEvent->ShortText()?pEvent->ShortText():"", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), searchExt->search); SwitchTimers.Add(new cSwitchTimer(pEvent, searchExt->switchMinsBefore, mode, searchExt->unmuteSoundOnSwitch)); SwitchTimers.Save(); cSwitchTimerThread::Init(); } } if (Summary) free(Summary); delete timer; continue; } if (AddModTimer(timer, index, searchExt, pEvent, Priority, Lifetime, Summary, timerMod)) { if (index == 0) LogFile.Log(1,"added timer for '%s~%s' (%s - %s); search timer: '%s'", pEvent->Title(), pEvent->ShortText()?pEvent->ShortText():"", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), searchExt->search); else LogFile.Log(1,"modified timer %d for '%s~%s' (%s - %s); search timer: '%s'", index, pEvent->Title(), pEvent->ShortText()?pEvent->ShortText():"", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), searchExt->search); } if (Summary) free(Summary); delete timer; } delete pSearchResults; searchExt = localSearchExts->Next(searchExt); } if (localSearchExts) delete localSearchExts; if (pOutdatedTimers) { if (pOutdatedTimers->Count() > 0) { LogFile.Log(1,"removing outdated timers"); for(cTimerObj *tObj = pOutdatedTimers->First(); tObj; tObj = pOutdatedTimers->Next(tObj)) { cTimer* t = tObj->timer; // timer could have been deleted meanwhile, so check if its still there bool found = false; for(cTimer* checkT = Timers.First(); checkT; checkT = Timers.Next(checkT)) if (checkT == t) { found = true; break; } if (!found) continue; if (TimerWasModified(t)) continue; if (!t->Event()) continue; // if there is no event, we keep the timer, since EPG could have been cleared if (time(NULL) > t->StopTime()) continue; // if this timer has (just) finished, let VDR do the cleanup if (t->Recording()) continue; // do not remove recording timers LogFile.Log(1,"delete timer for '%s' (%s, channel %s)", t->File(), DAYDATETIME(t->StartTime()), CHANNELNAME(t->Channel())); RemoveTimer(t, t->Event()); } LogFile.Log(1,"removing outdated timers - done"); } delete pOutdatedTimers; } TimersDone.ClearOutdated(); TimersDone.Save(); if (announceList.Count() > 0) { cString msgfmt = cString::sprintf(tr("%d new broadcast(s) found! Show them?"), announceList.Count()); if (SendMsg(msgfmt, true,7) == kOk) { m_plugin->showAnnounces = true; cRemote::CallPlugin("epgsearch"); } } CheckEPGHours(); LogFile.iSysLog("search timer update finished"); // check for conflicts if (EPGSearchConfig.checkTimerConflictsAfterUpdate && m_Active && Running()) { LogFile.iSysLog("check for timer conflicts"); cConflictCheck conflictCheck; conflictCheck.Check(); if (conflictCheck.relevantConflicts > 0) { if (EPGSearchConfig.sendMailOnConflicts) { cMailConflictNotifier mailNotifier; mailNotifier.SendConflictNotifications(conflictCheck); } conflictCheck.EvaluateConflCheckCmd(); cString msgfmt = cString::sprintf(tr("%d timer conflict(s)! First at %s. Show them?"), conflictCheck.relevantConflicts, *DateTime(conflictCheck.nextRelevantConflictDate)); bool doMessage = EPGSearchConfig.noConflMsgWhileReplay == 0 || !cDevice::PrimaryDevice()->Replaying() || conflictCheck.nextRelevantConflictDate - now < 2*60*60 || (updateForced & UPDS_WITH_OSD); if (doMessage && SendMsg(msgfmt, true,7) == kOk) { m_plugin->showConflicts = true; cRemote::CallPlugin("epgsearch"); } } LogFile.iSysLog("check for timer conflicts - done"); } // delete expired recordings CheckExpiredRecs(); // check for updates for manual timers CheckManualTimers(); if (m_Active) mailNotifier.SendUpdateNotifications(); if ((updateForced & UPDS_WITH_OSD) && m_Active) SendMsg(tr("Search timer update done!")); // reset service call flag updateForced = 0; m_lastUpdate = time(NULL); nextUpdate = long(m_lastUpdate/60)*60 + (EPGSearchConfig.UpdateIntervall * 60); justRunning = false; } if (m_Active && Running()) Wait.Wait(2000); // to avoid high system load if time%30==0 while (Running() && m_Active && !NeedUpdate() && time(NULL)%30 != 0) // sync heart beat to a multiple of 5secs Wait.Wait(1000); }; LogFile.iSysLog("Leaving search timer thread"); }
eOSState cMenuSearchResults::Record(void) { UpdateCurrent(); cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current()); if (item) { if (item->timerMatch == tmFull) { eTimerMatch tm = tmNone; cTimer *timer = Timers.GetMatch(item->event, &tm); if (timer) { if (EPGSearchConfig.useVDRTimerEditMenu) return AddSubMenu(new cMenuEditTimer(timer)); else return AddSubMenu(new cMenuMyEditTimer(timer, false, item->event)); } } cTimer *timer = new cTimer(item->event); PrepareTimerFile(item->event, timer); cTimer *t = Timers.GetTimer(timer); if (EPGSearchConfig.onePressTimerCreation == 0 || t || !item->event || (!t && item->event && item->event->StartTime() - (Setup.MarginStart+2) * 60 < time(NULL))) { if (t) { delete timer; timer = t; } if (EPGSearchConfig.useVDRTimerEditMenu) return AddSubMenu(new cMenuEditTimer(timer, !t)); else return AddSubMenu(new cMenuMyEditTimer(timer, !t, item->event)); } else { string fullaux = ""; string aux = ""; if (item->event) { const cEvent* event = item->event; int bstart = event->StartTime() - timer->StartTime(); int bstop = timer->StopTime() - event->EndTime(); int checkmode = DefTimerCheckModes.GetMode(timer->Channel()); aux = UpdateAuxValue(aux, "channel", NumToString(timer->Channel()->Number()) + " - " + CHANNELNAME(timer->Channel())); aux = UpdateAuxValue(aux, "update", checkmode); aux = UpdateAuxValue(aux, "eventid", event->EventID()); aux = UpdateAuxValue(aux, "bstart", bstart); aux = UpdateAuxValue(aux, "bstop", bstop); fullaux = UpdateAuxValue(fullaux, "epgsearch", aux); } #ifdef USE_PINPLUGIN aux = ""; aux = UpdateAuxValue(aux, "protected", timer->FskProtection() ? "yes" : "no"); fullaux = UpdateAuxValue(fullaux, "pin-plugin", aux); #endif SetAux(timer, fullaux); Timers.Add(timer); gl_timerStatusMonitor->SetConflictCheckAdvised(); timer->Matches(); Timers.SetModified(); LogFile.iSysLog("timer %s added (active)", *timer->ToDescr()); if (HasSubMenu()) CloseSubMenu(); if (Update()) Display(); SetHelpKeys(); } } return osContinue; }