Exemple #1
0
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;
}