summaryrefslogtreecommitdiff
path: root/epgsearchext.c
diff options
context:
space:
mode:
authorChristian Wieninger <winni@debian.(none)>2007-11-11 15:40:28 +0100
committerChristian Wieninger <winni@debian.(none)>2007-11-11 15:40:28 +0100
commit8d4f8607dc1558ce73eb4c376bdbf78ddb65da83 (patch)
treed0c5dde81a36ab2e8a2edc7c1e6922556518b312 /epgsearchext.c
downloadvdr-plugin-epgsearch-8d4f8607dc1558ce73eb4c376bdbf78ddb65da83.tar.gz
vdr-plugin-epgsearch-8d4f8607dc1558ce73eb4c376bdbf78ddb65da83.tar.bz2
Initial commit
Diffstat (limited to 'epgsearchext.c')
-rw-r--r--epgsearchext.c1559
1 files changed, 1559 insertions, 0 deletions
diff --git a/epgsearchext.c b/epgsearchext.c
new file mode 100644
index 0000000..3706cf2
--- /dev/null
+++ b/epgsearchext.c
@@ -0,0 +1,1559 @@
+/*
+Copyright (C) 2004-2007 Christian Wieninger
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+
+The author can be reached at cwieninger@gmx.de
+
+The project's page is at http://winni.vdr-developer.org/epgsearch
+*/
+
+#include <vector>
+#include "epgsearchext.h"
+#include "epgsearchcfg.h"
+#include "epgsearchcats.h"
+#include "epgsearchtools.h"
+#include <vdr/tools.h>
+#include "menu_searchresults.h"
+#include "menu_dirselect.h"
+#include "changrp.h"
+#include "menu_search.h"
+#include "menu_searchedit.h"
+#include "menu_recsdone.h"
+#include "searchtimer_thread.h"
+#include "timer_thread.h"
+#include "uservars.h"
+#include "blacklist.h"
+#include <math.h>
+#ifdef HAVE_PCREPOSIX
+#include <pcreposix.h>
+#else
+#include <regex.h>
+#endif
+
+cSearchExts SearchExts;
+cSearchExts SearchTemplates;
+extern bool updateForced;
+
+#define MAX_SUBTITLE_LENGTH 40
+
+// -- cSearchExt -----------------------------------------------------------------
+char *cSearchExt::buffer = NULL;
+
+cSearchExt::cSearchExt(void)
+{
+ ID = -1;
+ *search = 0;
+ options = 1;
+ useTime = false;
+ startTime = 0000;
+ stopTime = 2359;
+ useChannel = false;
+ channelMin = Channels.GetByNumber(cDevice::CurrentChannel());
+ channelMax = Channels.GetByNumber(cDevice::CurrentChannel());
+ channelGroup = NULL;
+ useCase = false;
+ mode = 0;
+ useTitle = true;
+ useSubtitle = true;
+ useDescription = true;
+ useDuration = false;
+ minDuration = 0;
+ maxDuration = 2359;
+ useAsSearchTimer = false;
+ useDayOfWeek = false;
+ DayOfWeek = 0;
+ buffer = NULL;
+ *directory = 0;
+ useEpisode = 0;
+ Priority = EPGSearchConfig.DefPriority;
+ Lifetime = EPGSearchConfig.DefLifetime;
+ MarginStart = EPGSearchConfig.DefMarginStart;
+ MarginStop = EPGSearchConfig.DefMarginStop;
+ useVPS = false;
+ action = searchTimerActionRecord;
+ useExtEPGInfo = false;
+ catvalues = (char**) malloc(SearchExtCats.Count() * sizeof(char*));
+ cSearchExtCat *SearchExtCat = SearchExtCats.First();
+ int index = 0;
+ while (SearchExtCat)
+ {
+ catvalues[index] = (char*)malloc(MaxFileName);
+ *catvalues[index] = 0;
+ SearchExtCat = SearchExtCats.Next(SearchExtCat);
+ index++;
+ }
+ avoidRepeats = 0;
+ compareTitle = 1;
+ compareSubtitle = 1;
+ compareSummary = 1;
+ allowedRepeats = 0;
+ catvaluesAvoidRepeat = 0;
+ repeatsWithinDays = 0;
+ delAfterDays = 0;
+ recordingsKeep = 0;
+ switchMinsBefore = 1;
+ pauseOnNrRecordings = 0;
+ blacklistMode = blacklistsNo; // no blacklists
+ blacklists.Clear();
+ fuzzyTolerance = 1;
+ useInFavorites = 0;
+ menuTemplate = 0;
+ delMode = 0;
+ delAfterCountRecs = 0;
+ delAfterDaysOfFirstRec = 0;
+ useAsSearchTimerFrom = 0;
+ useAsSearchTimerTil = 0;
+ ignoreMissingEPGCats = 0;
+ unmuteSoundOnSwitch = 0;
+}
+
+cSearchExt::~cSearchExt(void)
+{
+ if (buffer) {
+ free(buffer);
+ buffer = NULL;
+ }
+
+ if (catvalues)
+ {
+ cSearchExtCat *SearchExtCat = SearchExtCats.First();
+ int index = 0;
+ while (SearchExtCat)
+ {
+ free(catvalues[index]);
+ SearchExtCat = SearchExtCats.Next(SearchExtCat);
+ index++;
+ }
+ free(catvalues);
+ catvalues = NULL;
+ }
+}
+
+cSearchExt& cSearchExt::operator= (const cSearchExt &SearchExt)
+{
+ CopyFromTemplate(&SearchExt);
+ ID = SearchExt.ID;
+ strcpy(search, SearchExt.search);
+ catvalues = (char**) malloc(SearchExtCats.Count() * sizeof(char*));
+ cSearchExtCat *SearchExtCat = SearchExtCats.First();
+ int index = 0;
+ while (SearchExtCat)
+ {
+ catvalues[index] = (char*)malloc(MaxFileName);
+ *catvalues[index] = 0;
+ strcpy(catvalues[index], SearchExt.catvalues[index]);
+ SearchExtCat = SearchExtCats.Next(SearchExtCat);
+ index++;
+ }
+
+ return *this;
+}
+
+void cSearchExt::CopyFromTemplate(const cSearchExt* templ)
+{
+ options = templ->options;
+ useTime = templ->useTime;
+ startTime = templ->startTime;
+ stopTime = templ->stopTime;
+ useChannel = templ->useChannel;
+ useCase = templ->useCase;
+ mode = templ->mode;
+ useTitle = templ->useTitle;
+ useSubtitle = templ->useSubtitle;
+ useDescription = templ->useDescription;
+ useDuration = templ->useDuration;
+ minDuration = templ->minDuration;
+ maxDuration = templ->maxDuration;
+ useAsSearchTimer = templ->useAsSearchTimer;
+ useDayOfWeek = templ->useDayOfWeek;
+ DayOfWeek = templ->DayOfWeek;
+ useEpisode = templ->useEpisode;
+ strcpy(directory, templ->directory);
+ Priority = templ->Priority;
+ Lifetime = templ->Lifetime;
+ MarginStart = templ->MarginStart;
+ MarginStop = templ->MarginStop;
+ useVPS = templ->useVPS;
+ action = templ->action;
+ useExtEPGInfo = templ->useExtEPGInfo;
+ switchMinsBefore = templ->switchMinsBefore;
+ pauseOnNrRecordings = templ->pauseOnNrRecordings;
+
+ cSearchExtCat *SearchExtCat = SearchExtCats.First();
+ int index = 0;
+ while (SearchExtCat)
+ {
+ strcpy(catvalues[index], templ->catvalues[index]);
+ SearchExtCat = SearchExtCats.Next(SearchExtCat);
+ index++;
+ }
+
+ channelMin = templ->channelMin;
+ channelMax = templ->channelMax;
+ if (channelGroup)
+ {
+ free(channelGroup);
+ channelGroup = NULL;
+ }
+ if (templ->channelGroup)
+ channelGroup = strdup(templ->channelGroup);
+ avoidRepeats = templ->avoidRepeats;
+ compareTitle = templ->compareTitle;
+ compareSubtitle = templ->compareSubtitle;
+ compareSummary = templ->compareSummary;
+ allowedRepeats = templ->allowedRepeats;
+ catvaluesAvoidRepeat = templ->catvaluesAvoidRepeat;
+ repeatsWithinDays = templ->repeatsWithinDays;
+ delAfterDays = templ->delAfterDays;
+ recordingsKeep = templ->recordingsKeep;
+ blacklistMode = templ->blacklistMode;
+ blacklists.Clear();
+ cBlacklistObject* blacklistObj = templ->blacklists.First();
+ while(blacklistObj)
+ {
+ blacklists.Add(new cBlacklistObject(blacklistObj->blacklist));
+ blacklistObj = templ->blacklists.Next(blacklistObj);
+ }
+ fuzzyTolerance = templ->fuzzyTolerance;
+ useInFavorites = templ->useInFavorites;
+ menuTemplate = templ->menuTemplate;
+ delMode = templ->delMode;
+ delAfterCountRecs = templ->delAfterCountRecs;
+ delAfterDaysOfFirstRec = templ->delAfterDaysOfFirstRec;
+ useAsSearchTimerFrom = templ->useAsSearchTimerFrom;
+ useAsSearchTimerTil = templ->useAsSearchTimerTil;
+ ignoreMissingEPGCats = templ->ignoreMissingEPGCats;
+ unmuteSoundOnSwitch = templ->unmuteSoundOnSwitch;
+}
+
+bool cSearchExt::operator< (const cListObject &ListObject)
+{
+ cSearchExt *SE = (cSearchExt *)&ListObject;
+ return strcasecmp(search, SE->search) < 0;
+}
+
+const char *cSearchExt::ToText()
+{
+ char tmp_Start[5] = "";
+ char tmp_Stop[5] = "";
+ char tmp_minDuration[5] = "";
+ char tmp_maxDuration[5] = "";
+ char* tmp_chanSel = NULL;
+ char* tmp_search = NULL;
+ char* tmp_directory = NULL;
+ char* tmp_catvalues = NULL;
+ char* tmp_blacklists = NULL;
+
+ free(buffer);
+ tmp_search = strdup(search);
+ while(strstr(tmp_search, "|"))
+ tmp_search = strreplace(tmp_search, "|", "!^pipe^!"); // ugly: replace a pipe with something,
+ // that should not happen to be part of a search string
+ tmp_directory = strdup(directory);
+ while(strstr(tmp_directory, "|"))
+ tmp_directory = strreplace(tmp_directory, "|", "!^pipe^!"); // ugly: replace a pipe with something,
+ // that should not happen to be part of a search string
+
+ strreplace(tmp_search, ':', '|');
+ strreplace(tmp_directory, ':', '|');
+
+ if (useTime)
+ {
+ sprintf(tmp_Start, "%04d", startTime);
+ sprintf(tmp_Stop, "%04d", stopTime);
+ }
+ if (useDuration)
+ {
+ sprintf(tmp_minDuration, "%04d", minDuration);
+ sprintf(tmp_maxDuration, "%04d", maxDuration);
+ }
+
+ if (useChannel==1)
+ {
+ if (channelMin->Number() < channelMax->Number())
+ asprintf(&tmp_chanSel, "%s|%s", CHANNELSTRING(channelMin), CHANNELSTRING(channelMax));
+ else
+ asprintf(&tmp_chanSel, "%s", CHANNELSTRING(channelMin));
+ }
+ if (useChannel==2)
+ {
+ int channelGroupNr = ChannelGroups.GetIndex(channelGroup);
+ if (channelGroupNr == -1)
+ {
+ LogFile.eSysLog("channel group '%s' does not exist!", channelGroup);
+ useChannel = 0;
+ }
+ else
+ tmp_chanSel = strdup(channelGroup);
+ }
+
+ if (useExtEPGInfo)
+ {
+ cSearchExtCat *SearchExtCat = SearchExtCats.First();
+ int index = 0;
+ while (SearchExtCat)
+ {
+ char* catvalue = NULL;
+ asprintf(&catvalue, "%s", catvalues[index]);
+ while(strstr(catvalue, ":"))
+ catvalue = strreplace(catvalue, ":", "!^colon^!"); // ugly: replace with something, that should not happen to be part ofa category value
+ while(strstr(catvalue, "|"))
+ catvalue = strreplace(catvalue, "|", "!^pipe^!"); // ugly: replace with something, that should not happen to be part of a regular expression
+
+ if (index == 0)
+ asprintf(&tmp_catvalues, "%d#%s", SearchExtCat->id, catvalue);
+ else
+ {
+ char* temp = tmp_catvalues;
+ asprintf(&tmp_catvalues, "%s|%d#%s", tmp_catvalues, SearchExtCat->id, catvalue);
+ free(temp);
+ }
+ SearchExtCat = SearchExtCats.Next(SearchExtCat);
+ index++;
+ free(catvalue);
+ }
+ }
+
+ if (blacklistMode == blacklistsSelection && blacklists.Count() > 0)
+ {
+ cBlacklistObject *blacklistObj = blacklists.First();
+ int index = 0;
+ while (blacklistObj)
+ {
+ if (index == 0)
+ asprintf(&tmp_blacklists, "%d", blacklistObj->blacklist->ID);
+ else
+ {
+ char* temp = tmp_blacklists;
+ asprintf(&tmp_blacklists, "%s|%d", tmp_blacklists, blacklistObj->blacklist->ID);
+ free(temp);
+ }
+ blacklistObj = blacklists.Next(blacklistObj);
+ index++;
+ }
+ }
+
+ asprintf(&buffer, "%d:%s:%d:%s:%s:%d:%s:%d:%d:%d:%d:%d:%d:%s:%s:%d:%d:%d:%d:%s:%d:%d:%d:%d:%d:%d:%d:%s:%d:%d:%d:%d:%d:%ld:%d:%d:%d:%d:%d:%d:%s:%d:%d:%d:%d:%d:%d:%ld:%ld:%d:%d",
+ ID,
+ tmp_search,
+ useTime,
+ tmp_Start,
+ tmp_Stop,
+ useChannel,
+ (useChannel>0 && useChannel<3)?tmp_chanSel:"0",
+ useCase,
+ mode,
+ useTitle,
+ useSubtitle,
+ useDescription,
+ useDuration,
+ tmp_minDuration,
+ tmp_maxDuration,
+ useAsSearchTimer,
+ useDayOfWeek,
+ DayOfWeek,
+ useEpisode,
+ tmp_directory,
+ Priority,
+ Lifetime,
+ MarginStart,
+ MarginStop,
+ useVPS,
+ action,
+ useExtEPGInfo,
+ useExtEPGInfo?tmp_catvalues:"",
+ avoidRepeats,
+ allowedRepeats,
+ compareTitle,
+ compareSubtitle,
+ compareSummary,
+ catvaluesAvoidRepeat,
+ repeatsWithinDays,
+ delAfterDays,
+ recordingsKeep,
+ switchMinsBefore,
+ pauseOnNrRecordings,
+ blacklistMode,
+ blacklists.Count()>0?tmp_blacklists:"",
+ fuzzyTolerance,
+ useInFavorites,
+ menuTemplate,
+ delMode,
+ delAfterCountRecs,
+ delAfterDaysOfFirstRec,
+ useAsSearchTimerFrom,
+ useAsSearchTimerTil,
+ ignoreMissingEPGCats,
+ unmuteSoundOnSwitch);
+
+ if (tmp_chanSel) free(tmp_chanSel);
+ if (tmp_search) free(tmp_search);
+ if (tmp_directory) free(tmp_directory);
+ if (tmp_catvalues) free(tmp_catvalues);
+ if (tmp_blacklists) free(tmp_blacklists);
+
+ return buffer;
+}
+
+bool cSearchExt::Parse(const char *s)
+{
+ char *line;
+ char *pos;
+ char *pos_next;
+ int parameter = 1;
+ int valuelen;
+ char value[MaxFileName];
+ bool disableSearchtimer = false;
+
+ *directory = 0;
+ *search = 0;
+
+ pos = line = strdup(s);
+ pos_next = pos + strlen(pos);
+ if (*pos_next == '\n') *pos_next = 0;
+ while (*pos) {
+ while (*pos == ' ') pos++;
+ if (*pos) {
+ if (*pos != ':') {
+ pos_next = strchr(pos, ':');
+ if (!pos_next)
+ pos_next = pos + strlen(pos);
+ valuelen = pos_next - pos + 1;
+ if (valuelen > MaxFileName) valuelen = MaxFileName;
+ strn0cpy(value, pos, valuelen);
+ pos = pos_next;
+ switch (parameter) {
+ case 1:
+ if (!isnumber(value)) return false;
+ ID = atoi(value);
+ break;
+ case 2: strcpy(search, value);
+ break;
+ case 3: useTime = atoi(value);
+ break;
+ case 4: startTime = atoi(value);
+ break;
+ case 5: stopTime = atoi(value);
+ break;
+ case 6: useChannel = atoi(value);
+ break;
+ case 7:
+ if (useChannel == 0)
+ {
+ channelMin = NULL;
+ channelMax = NULL;
+ }
+ else if (useChannel == 1)
+ {
+ int minNum=0, maxNum=0;
+ int fields = sscanf(value, "%d-%d", &minNum, &maxNum);
+ if (fields == 0) // stored with ID
+ {
+ char *channelMinbuffer = NULL;
+ char *channelMaxbuffer = NULL;
+ int channels = sscanf(value, "%a[^|]|%a[^|]", &channelMinbuffer, &channelMaxbuffer);
+ channelMin = Channels.GetByChannelID(tChannelID::FromString(channelMinbuffer), true, true);
+ if (!channelMin)
+ {
+ LogFile.eSysLog("ERROR: channel '%s' not defined", channelMinbuffer);
+ channelMin = channelMax = NULL;
+ disableSearchtimer = true;
+ useChannel = 0;
+ }
+ if (channels == 1)
+ channelMax = channelMin;
+ else
+ {
+ channelMax = Channels.GetByChannelID(tChannelID::FromString(channelMaxbuffer), true, true);
+ if (!channelMax)
+ {
+ LogFile.eSysLog("ERROR: channel '%s' not defined", channelMaxbuffer);
+ channelMin = channelMax = NULL;
+ disableSearchtimer = true;
+ useChannel = 0;
+ }
+ }
+ free(channelMinbuffer);
+ free(channelMaxbuffer);
+ }
+ }
+ else if (useChannel == 2)
+ channelGroup = strdup(value);
+ break;
+ case 8: useCase = atoi(value);
+ break;
+ case 9: mode = atoi(value);
+ break;
+ case 10: useTitle = atoi(value);
+ break;
+ case 11: useSubtitle = atoi(value);
+ break;
+ case 12: useDescription = atoi(value);
+ break;
+ case 13: useDuration = atoi(value);
+ break;
+ case 14: minDuration = atoi(value);
+ break;
+ case 15: maxDuration = atoi(value);
+ break;
+ case 16: useAsSearchTimer = atoi(value);
+ break;
+ case 17: useDayOfWeek = atoi(value);
+ break;
+ case 18: DayOfWeek = atoi(value);
+ break;
+ case 19: useEpisode = atoi(value);
+ break;
+ case 20: strcpy(directory, value);
+ break;
+ case 21: Priority = atoi(value);
+ break;
+ case 22: Lifetime = atoi(value);
+ break;
+ case 23: MarginStart = atoi(value);
+ break;
+ case 24: MarginStop = atoi(value);
+ break;
+ case 25: useVPS = atoi(value);
+ break;
+ case 26: action = atoi(value);
+ break;
+ case 27: useExtEPGInfo = atoi(value);
+ break;
+ case 28:
+ if (!ParseExtEPGValues(value))
+ {
+ LogFile.eSysLog("ERROR reading ext. EPG values - 1");
+ free(line);
+ return false;
+ }
+ break;
+ case 29: avoidRepeats = atoi(value);
+ break;
+ case 30: allowedRepeats = atoi(value);
+ break;
+ case 31: compareTitle = atoi(value);
+ break;
+ case 32: compareSubtitle = atoi(value);
+ break;
+ case 33: compareSummary = atoi(value);
+ break;
+ case 34: catvaluesAvoidRepeat = atol(value);
+ break;
+ case 35: repeatsWithinDays = atoi(value);
+ break;
+ case 36: delAfterDays = atoi(value);
+ break;
+ case 37: recordingsKeep = atoi(value);
+ break;
+ case 38: switchMinsBefore = atoi(value);
+ break;
+ case 39: pauseOnNrRecordings = atoi(value);
+ break;
+ case 40: blacklistMode = atoi(value);
+ break;
+ case 41:
+ if (blacklistMode == blacklistsSelection && !ParseBlacklistIDs(value))
+ {
+ LogFile.eSysLog("ERROR parsing blacklist IDs");
+ free(line);
+ return false;
+ }
+ break;
+ case 42: fuzzyTolerance = atoi(value);
+ break;
+ case 43: useInFavorites = atoi(value);
+ break;
+ case 44: menuTemplate = atoi(value);
+ break;
+ case 45: delMode = atoi(value);
+ break;
+ case 46: delAfterCountRecs = atoi(value);
+ break;
+ case 47: delAfterDaysOfFirstRec = atoi(value);
+ break;
+ case 48:
+ useAsSearchTimerFrom = atol(value);
+ break;
+ case 49:
+ useAsSearchTimerTil = atol(value);
+ break;
+ case 50:
+ ignoreMissingEPGCats = atoi(value);
+ break;
+ case 51:
+ unmuteSoundOnSwitch = atoi(value);
+ break;
+ } //switch
+ }
+ parameter++;
+ }
+ if (*pos) pos++;
+ } //while
+
+ strreplace(directory, '|', ':');
+ strreplace(search, '|', ':');
+ while(strstr(search, "!^pipe^!"))
+ strreplace(search, "!^pipe^!", "|");
+ while(strstr(directory, "!^pipe^!"))
+ strreplace(directory, "!^pipe^!", "|");
+
+ if (disableSearchtimer && useAsSearchTimer)
+ {
+ useAsSearchTimer = false;
+ LogFile.Log(1, "search timer '%s' disabled", search);
+ }
+
+ free(line);
+ return (parameter >= 11) ? true : false;
+}
+
+char* cSearchExt::BuildFile(const cEvent* pEvent) const
+{
+ char* file = NULL;
+
+ if (!pEvent)
+ return file;
+
+ const char *Subtitle = pEvent ? pEvent->ShortText() : NULL;
+ char SubtitleBuffer[MAX_SUBTITLE_LENGTH];
+ if (isempty(Subtitle))
+ {
+ sprintf(SubtitleBuffer, "%s-%s", GETDATESTRING(pEvent), GETTIMESTRING(pEvent));
+ Subtitle = SubtitleBuffer;
+ }
+ else if (strlen(Subtitle) > MAX_SUBTITLE_LENGTH)
+ {
+ strn0cpy(SubtitleBuffer, Subtitle, MAX_SUBTITLE_LENGTH);
+ Subtitle = SubtitleBuffer;
+ }
+
+ if (useEpisode)
+ {
+ char* pFile = NULL;
+ asprintf(&pFile, "%s~%s", pEvent->Title(), Subtitle);
+ if (file) free(file);
+ file = strdup(pFile);
+ free(pFile);
+ }
+ else
+ asprintf(&file, "%s", pEvent->Title());
+
+ if (!isempty(directory))
+ {
+ char* pFile = NULL;
+
+ cVarExpr varExprDir(directory);
+ if (!varExprDir.DependsOnVar("%title%", pEvent) && !varExprDir.DependsOnVar("%subtitle%", pEvent))
+ asprintf(&pFile, "%s~%s", directory, file?file:"");
+ else
+ // ignore existing title and subtitle in file if already used as variables in directory
+ asprintf(&pFile, "%s", directory);
+
+ // parse the epxression and evaluate it
+ cVarExpr varExprFile(pFile);
+ if (pFile) free(pFile);
+ pFile = strdup(varExprFile.Evaluate(pEvent).c_str());
+
+ cVarExpr varExprSearchFile(pFile);
+ if (pFile) free(pFile);
+ pFile = strdup(varExprSearchFile.Evaluate(this).c_str());
+
+ if (file) free(file);
+ file = strdup(pFile);
+ free(pFile);
+ }
+// replace some special chars
+ if (file)
+ {
+ while(strstr(file, "|")) file = strreplace(file, "|", "!^pipe^!");
+ while(strstr(file, ":")) file = strreplace(file, ':', '|');
+ while(strstr(file, " ~")) file = strreplace(file, " ~", "~");
+ while(strstr(file, "~ ")) file = strreplace(file, "~ ", "~");
+ }
+ return file;
+}
+
+bool cSearchExt::ParseBlacklistIDs(const char *s)
+{
+ char *line;
+ char *pos;
+ char *pos_next;
+ int valuelen;
+ char value[MaxFileName];
+
+ cMutexLock BlacklistLock(&Blacklists);
+ blacklists.Clear();
+
+ pos = line = strdup(s);
+ pos_next = pos + strlen(pos);
+ if (*pos_next == '\n') *pos_next = 0;
+ while (*pos) {
+ while (*pos == ' ') pos++;
+ if (*pos) {
+ if (*pos != '|') {
+ pos_next = strchr(pos, '|');
+ if (!pos_next)
+ pos_next = pos + strlen(pos);
+ valuelen = pos_next - pos + 1;
+ if (valuelen > MaxFileName) valuelen = MaxFileName;
+ strn0cpy(value, pos, valuelen);
+ pos = pos_next;
+ cBlacklist* blacklist = Blacklists.GetBlacklistFromID(atoi(value));
+ if (!blacklist)
+ LogFile.eSysLog("blacklist ID %s missing, will be skipped", value);
+ else
+ blacklists.Add(new cBlacklistObject(blacklist));
+ }
+ }
+ if (*pos) pos++;
+ } //while
+
+ free(line);
+ return true;
+}
+
+bool cSearchExt::ParseExtEPGValues(const char *s)
+{
+ char *line;
+ char *pos;
+ char *pos_next;
+ int valuelen;
+ char value[MaxFileName];
+
+ pos = line = strdup(s);
+ pos_next = pos + strlen(pos);
+ if (*pos_next == '\n') *pos_next = 0;
+ while (*pos) {
+ while (*pos == ' ') pos++;
+ if (*pos) {
+ if (*pos != '|') {
+ pos_next = strchr(pos, '|');
+ if (!pos_next)
+ pos_next = pos + strlen(pos);
+ valuelen = pos_next - pos + 1;
+ if (valuelen > MaxFileName) valuelen = MaxFileName;
+ strn0cpy(value, pos, valuelen);
+ pos = pos_next;
+ if (!ParseExtEPGEntry(value))
+ {
+ LogFile.eSysLog("ERROR reading ext. EPG value: %s", value);
+ free(line);
+ return false;
+ }
+ }
+ }
+ if (*pos) pos++;
+ } //while
+
+ free(line);
+ return true;
+}
+
+bool cSearchExt::ParseExtEPGEntry(const char *s)
+{
+ char *line;
+ char *pos;
+ char *pos_next;
+ int parameter = 1;
+ int valuelen;
+ char value[MaxFileName];
+ int currentid = -1;
+
+ pos = line = strdup(s);
+ pos_next = pos + strlen(pos);
+ if (*pos_next == '\n') *pos_next = 0;
+ while (*pos) {
+ while (*pos == ' ') pos++;
+ if (*pos) {
+ if (*pos != '#') {
+ pos_next = strchr(pos, '#');
+ if (!pos_next)
+ pos_next = pos + strlen(pos);
+ valuelen = pos_next - pos + 1;
+ if (valuelen > MaxFileName) valuelen = MaxFileName;
+ strn0cpy(value, pos, valuelen);
+ pos = pos_next;
+ switch (parameter) {
+ case 1:
+ {
+ currentid = atoi(value);
+ int index = SearchExtCats.GetIndexFromID(currentid);
+ if (index > -1 && index < SearchExtCats.Count())
+ strcpy(catvalues[index], "");
+ }
+ break;
+ case 2:
+ if (currentid > -1)
+ {
+ int index = SearchExtCats.GetIndexFromID(currentid);
+ if (index > -1 && index < SearchExtCats.Count())
+ {
+ while(strstr(value, "!^colon^!"))
+ strreplace(value, "!^colon^!", ":");
+ while(strstr(value, "!^pipe^!"))
+ strreplace(value, "!^pipe^!", "|");
+ strcpy(catvalues[index], value);
+ }
+ }
+ break;
+ } //switch
+ }
+ parameter++;
+ }
+ if (*pos) pos++;
+ } //while
+
+ free(line);
+ return (parameter >= 2) ? true : false;
+}
+
+bool cSearchExt::Save(FILE *f)
+{
+ return fprintf(f, "%s\n", ToText()) > 0;
+}
+
+cEvent * cSearchExt::GetEventBySearchExt(const cSchedule *schedules, const cEvent *Start, bool inspectTimerMargin)
+{
+ if (!schedules) return NULL;
+
+ cEvent *pe = NULL;
+ cEvent *p1 = NULL;
+
+ const cList<cEvent>* Events = schedules->Events();
+ if (Start)
+ p1 = Events->Next(Start);
+ else
+ p1 = Events->First();
+
+ time_t tNow=time(NULL);
+ char* szTest = NULL;
+ char* searchText = strdup(search);
+
+ int searchStart = 0, searchStop = 0;
+ if (useTime)
+ {
+ searchStart = startTime;
+ searchStop = stopTime;
+ if (searchStop < searchStart)
+ searchStop += 2400;
+ }
+ int minSearchDuration = 0;
+ int maxSearchDuration = 0;
+ if (useDuration)
+ {
+ minSearchDuration = minDuration/100*60 + minDuration%100;
+ maxSearchDuration = maxDuration/100*60 + maxDuration%100;
+ }
+
+ if (!useCase)
+ ToLower(searchText);
+
+ for (cEvent *p = p1; p; p = Events->Next(p))
+ {
+ if(!p)
+ {
+ break;
+ }
+
+ if (szTest)
+ {
+ free(szTest);
+ szTest = NULL;
+ }
+
+ // ignore events without title
+ if (!p->Title() || !*p->Title())
+ continue;
+
+ asprintf(&szTest, "%s%s%s%s%s", (useTitle?(p->Title()?p->Title():""):""), (useSubtitle||useDescription)?"~":"",
+ (useSubtitle?(p->ShortText()?p->ShortText():""):""),useDescription?"~":"",
+ (useDescription?(p->Description()?p->Description():""):""));
+
+ if (tNow < p->EndTime() + (inspectTimerMargin?(MarginStop * 60):0))
+ {
+ if (!useCase)
+ ToLower(szTest);
+
+ if (useTime)
+ {
+ time_t tEvent = p->StartTime();
+ struct tm tmEvent;
+ localtime_r(&tEvent, &tmEvent);
+
+ int eventStart = tmEvent.tm_hour*100 + tmEvent.tm_min;
+ int eventStart2 = eventStart + 2400;
+ if ((eventStart < searchStart || eventStart > searchStop) &&
+ (eventStart2 < searchStart || eventStart2 > searchStop))
+ continue;
+
+ if (useDayOfWeek)
+ {
+ if (DayOfWeek >= 0)
+ {
+ if (( DayOfWeek != tmEvent.tm_wday || (DayOfWeek == tmEvent.tm_wday && eventStart < searchStart)) &&
+ (!((DayOfWeek+1)%7 == tmEvent.tm_wday && eventStart2 < searchStop)))
+ continue;
+ }
+ else
+ {
+ int iFound = 0;
+ for(int i=0; i<7; i++)
+ {
+ if ((abs(DayOfWeek) & (int)pow(2,i)) && ((i == tmEvent.tm_wday && eventStart >= searchStart) ||
+ ((i+1)%7 == tmEvent.tm_wday && eventStart2 < searchStop)))
+ {
+ iFound = 1;
+ break;
+ }
+ }
+ if (!iFound)
+ continue;
+ }
+ }
+ }
+ if (useDuration)
+ {
+ int duration = p->Duration()/60;
+ if (minSearchDuration > duration || maxSearchDuration < duration)
+ continue;
+ }
+
+ if (!useTime && useDayOfWeek)
+ {
+ time_t tEvent = p->StartTime();
+ struct tm tmEvent;
+ tm tm = *localtime_r(&tEvent, &tmEvent);
+ if (DayOfWeek >= 0 && DayOfWeek != tmEvent.tm_wday)
+ continue;
+ if (DayOfWeek < 0)
+ {
+ int iFound = 0;
+ for(int i=0; i<7; i++)
+ if (abs(DayOfWeek) & (int)pow(2,i) && i == tmEvent.tm_wday)
+ {
+ iFound = 1;
+ break;
+ }
+ if (!iFound)
+ continue;
+ }
+ }
+
+ if (*szTest)
+ {
+ if (!MatchesSearchMode(szTest, searchText, mode," ,;|~", fuzzyTolerance))
+ continue;
+ }
+
+ if (useExtEPGInfo && !MatchesExtEPGInfo(p))
+ continue;
+ pe=p;
+ break;
+ }
+ }
+ if (szTest)
+ free(szTest);
+ free(searchText);
+ return pe;
+}
+
+// returns a pointer array to the matching search results
+cSearchResults* cSearchExt::Run(int PayTVMode, bool inspectTimerMargin, int evalLimitMins, cSearchResults* pPrevResults, bool suppressRepeatCheck)
+{
+ LogFile.Log(3,"start search for search timer '%s'", search);
+
+ cSchedulesLock schedulesLock;
+ const cSchedules *schedules;
+ schedules = cSchedules::Schedules(schedulesLock);
+ if(!schedules) {
+ LogFile.Log(1,"schedules are currently locked! try again later.");
+ return NULL;
+ }
+
+ bool noPayTV = false;
+ if (PayTVMode == -1) // use search's setting
+ noPayTV = (useChannel == 3);
+ else
+ noPayTV = (PayTVMode == 1);
+
+ time_t tNow=time(NULL);
+ const cSchedule *Schedule = schedules->First();
+ cSearchResults* pSearchResults = pPrevResults;
+ cSearchResults* pBlacklistResults = GetBlacklistEvents(inspectTimerMargin?MarginStop:0);
+
+ int counter = 0;
+ while (Schedule) {
+ cChannel* channel = Channels.GetByChannelID(Schedule->ChannelID(),true,true);
+ if (!channel)
+ {
+ Schedule = (const cSchedule *)schedules->Next(Schedule);
+ continue;
+ }
+
+ if (useChannel == 1 && channelMin && channelMax)
+ {
+ if (channelMin->Number() > channel->Number() || channelMax->Number() < channel->Number())
+ {
+ Schedule = (const cSchedule *)schedules->Next(Schedule);
+ continue;
+ }
+ }
+ if (useChannel == 2 && channelGroup)
+ {
+ cChannelGroup* group = ChannelGroups.GetGroupByName(channelGroup);
+ if (!group || !group->ChannelInGroup(channel))
+ {
+ Schedule = (const cSchedule *)schedules->Next(Schedule);
+ continue;
+ }
+ }
+
+ if (useChannel == 3 && noPayTV)
+ {
+ if (channel->Ca() >= CA_ENCRYPTED_MIN)
+ {
+ Schedule = (const cSchedule *)schedules->Next(Schedule);
+ continue;
+ }
+ }
+
+ if (noPayTV) // no paytv
+ {
+ if (channel->Ca() >= CA_ENCRYPTED_MIN)
+ {
+ Schedule = (const cSchedule *)schedules->Next(Schedule);
+ continue;
+ }
+ }
+
+ const cEvent *pPrevEvent = NULL;
+ do {
+ const cEvent* event = GetEventBySearchExt(Schedule, pPrevEvent,inspectTimerMargin);
+ pPrevEvent = event;
+ if (evalLimitMins && event) // limit evaluation to now + limit
+ {
+ if (tNow + evalLimitMins*60 <= event->EndTime())
+ break;
+ }
+ if (event && Channels.GetByChannelID(event->ChannelID(),true,true))
+ {
+ if (pBlacklistResults && pBlacklistResults->Lookup(event))
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d): matches blacklist", event->Title()?event->Title():"no title", event->ShortText()?event->ShortText():"no subtitle", GETDATESTRING(event), GETTIMESTRING(event), ChannelNrFromEvent(event));
+ continue;
+ }
+ if (!pSearchResults) pSearchResults = new cSearchResults;
+ pSearchResults->Add(new cSearchResult(event, this));
+ counter++;
+ }
+ } while(pPrevEvent);
+ Schedule = (const cSchedule *)schedules->Next(Schedule);
+ }
+ LogFile.Log(3,"found %d event(s) for search timer '%s'", counter, search);
+
+ if (pBlacklistResults) delete pBlacklistResults;
+
+ if (useAsSearchTimer && avoidRepeats && pSearchResults && !suppressRepeatCheck)
+ {
+ pSearchResults->SortBy(CompareEventTime); // sort before checking repeats to make sure the first event is selected
+ CheckRepeatTimers(pSearchResults);
+ }
+
+ return pSearchResults;
+}
+
+cSearchResults* cSearchExt::GetBlacklistEvents(int MarginStop)
+{
+ if (blacklistMode == blacklistsNo) return NULL;
+
+ cMutexLock BlacklistLock(&Blacklists);
+ cSearchResults* blacklistEvents = NULL;
+ if (blacklistMode == blacklistsAll)
+ {
+ cBlacklist* tmpblacklist = Blacklists.First();
+ while(tmpblacklist)
+ {
+ blacklistEvents = tmpblacklist->Run(blacklistEvents, MarginStop);
+ tmpblacklist = Blacklists.Next(tmpblacklist);
+ }
+ }
+ if (blacklistMode == blacklistsSelection)
+ {
+ cBlacklistObject* tmpblacklistObj = blacklists.First();
+ while(tmpblacklistObj)
+ {
+ blacklistEvents = tmpblacklistObj->blacklist->Run(blacklistEvents, MarginStop);
+ tmpblacklistObj = blacklists.Next(tmpblacklistObj);
+ }
+ }
+ return blacklistEvents;
+
+}
+
+void cSearchExt::CheckRepeatTimers(cSearchResults* pResults)
+{
+ if (!pResults)
+ return;
+ if (avoidRepeats == 0)
+ return;
+
+ LogFile.Log(2,"analysing repeats for search timer '%s'...", search);
+ if (action == searchTimerActionAnnounceOnly)
+ {
+ LogFile.Log(3,"search timer set to 'announce only', so skip all");
+ return;
+ }
+
+ cSearchResult* pResultObj = NULL;
+ for (pResultObj = pResults->First(); pResultObj; pResultObj = pResults->Next(pResultObj))
+ {
+ if (action == searchTimerActionAnnounceOnly) // only announce if there is no timer for the event
+ {
+ pResultObj->needsTimer = false;
+ continue;
+ }
+
+ const cEvent* pEvent = pResultObj->event;
+ // check if this event was already recorded
+ int records = 0;
+ cRecDone* firstRec = NULL;
+ LogFile.Log(3,"get count recordings");
+ records = RecsDone.GetCountRecordings(pEvent, this, &firstRec);
+ LogFile.Log(3,"rec ordings: %d", records);
+
+ if (records > allowedRepeats) // already recorded
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d): already recorded %d equal event(s)", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), records);
+ pResultObj->needsTimer = false; // first asume we need no timer
+ continue;
+ }
+
+ int plannedTimers = 0;
+ LogFile.Log(3,"get planned recordings");
+ cSearchResult* pFirstResultMatching = NULL;
+ // check other results, if they are already planned for equal events
+ for (cSearchResult* pResultObjP = pResults->First(); pResultObjP; pResultObjP = pResults->Next(pResultObjP))
+ {
+ if (pResultObj == pResultObjP) break;
+
+ const cEvent* pEventP = pResultObjP->event;
+ if (!pEventP) continue;
+
+ if (!pResultObjP->needsTimer) continue;
+
+ if (EventsMatch(pEvent, pEventP, compareTitle, compareSubtitle, compareSummary, catvaluesAvoidRepeat))
+ {
+ if (!pFirstResultMatching) pFirstResultMatching = pResultObjP;
+ plannedTimers++;
+ }
+ }
+ LogFile.Log(3,"planned: %d", plannedTimers);
+
+ if (plannedTimers + records > allowedRepeats)
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d): events planned(%d), recorded(%d), allowed(%d)", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), plannedTimers, records, allowedRepeats);
+ pResultObj->needsTimer = false;
+ continue;
+ }
+ else if (allowedRepeats > 0 && repeatsWithinDays > 0) // if we only allow repeats with in a given range
+ {
+ if (firstRec) // already recorded, check for allowed repeat within days
+ {
+ if (firstRec->startTime > pEvent->StartTime() - pEvent->Duration()) // no repeat
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d); no repeat for event already recorded at %s, channel %d", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), DAYDATETIME(firstRec->startTime), firstRec->ChannelNr());
+ pResultObj->needsTimer = false;
+ continue;
+ }
+ int daysFromFirstRec = int(double((pEvent->StartTime() - firstRec->startTime)) / (60*60*24) + 0.5);
+ if (daysFromFirstRec > repeatsWithinDays)
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d); first recording at %s is %d days before, limit is %d days", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), DAYDATETIME(firstRec->startTime),daysFromFirstRec, repeatsWithinDays);
+ pResultObj->needsTimer = false;
+ continue;
+ }
+ }
+ if (plannedTimers > 0 && pFirstResultMatching)
+ {
+ const cEvent* pFirst = pFirstResultMatching->event;
+ if (pFirst->StartTime() > pEvent->StartTime() - pEvent->Duration()) // no repeat
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d); no repeat for event already recorded at %s - %s, channel %d", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), GETDATESTRING(pFirst), GETTIMESTRING(pFirst), ChannelNrFromEvent(pFirst));
+ pResultObj->needsTimer = false;
+ continue;
+ }
+
+ int daysBetween = int(double((pEvent->StartTime() - pFirst->StartTime())) / (60*60*24) + 0.5);
+ if (daysBetween > repeatsWithinDays)
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d); first event '%s~%s' (%s - %s) is %d days before, limit is %d days", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), GETDATESTRING(pFirst), GETTIMESTRING(pFirst),daysBetween, repeatsWithinDays);
+ pResultObj->needsTimer = false;
+ continue;
+ }
+ }
+ }
+ bool dummy;
+ cTimer* timer = cSearchTimerThread::GetTimer(this, pEvent, dummy);
+ if (timer && !timer->HasFlags(tfActive))
+ {
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d), existing timer disabled", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent));
+ pResultObj->needsTimer = false;
+ continue;
+ }
+ else
+ LogFile.Log(3,"*** planning event '%s~%s' (%s - %s, channel %d) for recording", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent));
+ }
+ int needsTimer = 0;
+ for (pResultObj = pResults->First(); pResultObj; pResultObj = pResults->Next(pResultObj))
+ if (pResultObj->needsTimer) needsTimer++;
+
+ LogFile.Log(2,"%d/%d events need a timer for search timer '%s'", needsTimer, pResults->Count(), search);
+}
+
+void cSearchExt::CheckExistingRecordings(cSearchResults* pResults)
+{
+ if (!pResults)
+ return;
+
+ LogFile.Log(3,"analysing existing recordings for search timer '%s'...", search);
+
+ // how many recordings do we already have?
+ int num = GetCountRecordings();
+
+ cSearchResult* pResultObj = NULL;
+ int remain = pauseOnNrRecordings - num;
+ for (pResultObj = pResults->First(); pResultObj; pResultObj = pResults->Next(pResultObj), remain--)
+ {
+ if (!pResultObj->needsTimer) continue; // maybe already disabled because of done feature
+ pResultObj->needsTimer = (remain > 0);
+ if (remain <= 0)
+ {
+ const cEvent* pEvent = pResultObj->event;
+ LogFile.Log(3,"skip '%s~%s' (%s - %s, channel %d): only %d recordings are allowed", pEvent->Title()?pEvent->Title():"no title", pEvent->ShortText()?pEvent->ShortText():"no subtitle", GETDATESTRING(pEvent), GETTIMESTRING(pEvent), ChannelNrFromEvent(pEvent), pauseOnNrRecordings);
+ }
+ }
+}
+
+bool cSearchExt::MatchesExtEPGInfo(const cEvent* e)
+{
+ if (!e || !e->Description())
+ return false;
+ cSearchExtCat* SearchExtCat = SearchExtCats.First();
+ while (SearchExtCat)
+ {
+ char* value = NULL;
+ int index = SearchExtCats.GetIndexFromID(SearchExtCat->id);
+ if (index > -1)
+ value = catvalues[index];
+ if (value && SearchExtCat->searchmode >= 10 && atol(value) == 0) // numerical value != 0 ?
+ value = NULL;
+ if (value && *value)
+ {
+ char* testvalue = GetExtEPGValue(e, SearchExtCat);
+ if (!testvalue)
+ return (ignoreMissingEPGCats?true:false);
+
+ // compare not case sensitive
+ char* valueLower = strdup(value);
+ ToLower(valueLower);
+ ToLower(testvalue);
+ if (!MatchesSearchMode(testvalue, valueLower, SearchExtCat->searchmode, ",;|~", fuzzyTolerance))
+ {
+ free(testvalue);
+ free(valueLower);
+ return false;
+ }
+ free(testvalue);
+ free(valueLower);
+ }
+ SearchExtCat = SearchExtCats.Next(SearchExtCat);
+ }
+ return true;
+}
+
+void cSearchExt::OnOffTimers(bool bOn)
+{
+ for (cTimer *ti = Timers.First(); ti; ti = Timers.Next(ti))
+ {
+ if (((!bOn && ti->HasFlags(tfActive)) || (bOn && !ti->HasFlags(tfActive))) && TriggeredFromSearchTimerID(ti) == ID)
+ ti->OnOff();
+ }
+ Timers.SetModified();
+}
+
+void cSearchExt::DeleteAllTimers()
+{
+ cList<cTimerObj> DelTimers;
+ cTimer *ti = Timers.First();
+ while(ti)
+ {
+ if (!ti->Recording() && TriggeredFromSearchTimerID(ti) == ID)
+ {
+ cTimer* tiNext = Timers.Next(ti);
+ LogFile.iSysLog("deleting timer %s", *ti->ToDescr());
+ Timers.Del(ti);
+ Timers.SetModified();
+ ti = tiNext;
+ }
+ else
+ ti = Timers.Next(ti);
+ };
+}
+
+cTimerObjList* cSearchExt::GetTimerList(cTimerObjList* timerList)
+{
+ if (!timerList)
+ timerList = new cTimerObjList;
+
+ for (cTimer *ti = Timers.First(); ti; ti = Timers.Next(ti))
+ {
+ if (TriggeredFromSearchTimerID(ti) == ID)
+ {
+ // check if already in list
+ bool found = false;
+ for (cTimerObj *tObj = timerList->First(); tObj; tObj = timerList->Next(tObj))
+ {
+ if (tObj->timer == ti)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ timerList->Add(new cTimerObj(ti));
+ }
+ }
+ return timerList;
+}
+
+// counts the currently existent recordings triggered by this search timer
+int cSearchExt::GetCountRecordings()
+{
+ int countRecs = 0;
+
+ for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording))
+ {
+ if (recording->IsEdited()) continue; // ignore recordings edited
+ if (!recording->Info()) continue;
+ char* searchID = GetAuxValue(recording, "s-id");
+
+ if (!searchID) continue;
+ if (ID == atoi(searchID))
+ countRecs++;
+ free(searchID);
+ }
+ LogFile.Log(3, "found %d recordings for search '%s'", countRecs, search);
+ return countRecs;
+}
+
+bool cSearchExt::IsActiveAt(time_t t)
+{
+ if (useAsSearchTimer == 0) return false;
+ if (useAsSearchTimer == 2)
+ {
+ if (useAsSearchTimerFrom > 0 && t < useAsSearchTimerFrom) return false;
+ if (useAsSearchTimerTil > 0 && t > useAsSearchTimerTil) return false;
+ }
+ return true;
+}
+
+// -- cSearchExts ----------------------------------------------------------------
+bool cSearchExts::Load(const char *FileName)
+{
+ cMutexLock SearchExtsLock(this);
+ Clear();
+ if (FileName) {
+ free(fileName);
+ fileName = strdup(FileName);
+ }
+
+ bool result = true;
+ if (fileName && access(fileName, F_OK) == 0) {
+ LogFile.iSysLog("loading %s", fileName);
+ FILE *f = fopen(fileName, "r");
+ if (f) {
+ int line = 0;
+ char buffer[MAXPARSEBUFFER];
+ result = true;
+ while (fgets(buffer, sizeof(buffer), f) > 0) {
+ line++;
+ char *p = strchr(buffer, '#');
+ if (p == buffer) *p = 0;
+
+ stripspace(buffer);
+ if (!isempty(buffer)) {
+ cSearchExt* search = new cSearchExt;
+ if (search->Parse(buffer))
+ Add(search);
+ else {
+ LogFile.eSysLog("error in '%s', line %d\n", fileName, line);
+ delete search;
+ result = false;
+ break;
+ }
+ }
+ }
+ fclose(f);
+ }
+ else {
+ LOG_ERROR_STR(fileName);
+ result = false;
+ }
+ }
+
+ if (!result)
+ fprintf(stderr, "vdr: error while reading '%s'\n", fileName);
+ LogFile.Log(2,"loaded searches from %s (count: %d)", fileName, Count());
+ return result;
+}
+
+int cSearchExts::GetNewID()
+{
+ cMutexLock SearchExtsLock(this);
+ int newID = -1;
+ cSearchExt *l = (cSearchExt *)First();
+ while (l) {
+ newID = max(newID, l->ID);
+ l = (cSearchExt *)l->Next();
+ }
+ return newID+1;
+}
+
+void cSearchExts::Update(void)
+{
+ cMutexLock SearchExtsLock(this);
+ cSearchExt *l = (cSearchExt *)First();
+ while (l) {
+ // check if ID is set
+ if (l->ID == -1)
+ l->ID = GetNewID();
+ l = (cSearchExt *)l->Next();
+ }
+}
+
+bool cSearchExts::Save(void)
+{
+ cMutexLock SearchExtsLock(this);
+ bool result = true;
+ cSearchExt *l = (cSearchExt *)this->First();
+ cSafeFile f(fileName);
+ if (f.Open()) {
+ while (l) {
+ if (!l->Save(f)) {
+ result = false;
+ break;
+ }
+ l = (cSearchExt *)l->Next();
+ }
+ if (!f.Close())
+ result = false;
+ }
+ else
+ result = false;
+ return result;
+}
+
+cSearchExt* cSearchExts::GetSearchFromID(int ID)
+{
+ if (ID == -1)
+ return NULL;
+ cMutexLock SearchExtsLock(this);
+ cSearchExt *l = (cSearchExt *)First();
+ while (l) {
+ if (l->ID == ID)
+ return l;
+ l = (cSearchExt *)l->Next();
+ }
+ return NULL;
+}
+
+void cSearchExts::RemoveBlacklistID(int ID)
+{
+ bool changed = false;
+ cMutexLock SearchExtsLock(this);
+ cSearchExt *l = (cSearchExt *)First();
+ while (l)
+ {
+ cBlacklistObject* blacklistObj = l->blacklists.First();
+ while(blacklistObj)
+ {
+ cBlacklistObject* blacklistObjNext = l->blacklists.Next(blacklistObj);
+ if (blacklistObj->blacklist->ID == ID)
+ {
+ l->blacklists.Del(blacklistObj);
+ changed = true;
+ }
+ blacklistObj = blacklistObjNext;
+ }
+ l = (cSearchExt *)l->Next();
+ }
+ if (changed)
+ Save();
+}
+
+bool cSearchExts::Exists(const cSearchExt* SearchExt)
+{
+ cMutexLock SearchExtsLock(this);
+ cSearchExt *l = (cSearchExt *)First();
+ while (l)
+ {
+ if (l == SearchExt)
+ return true;
+ l = (cSearchExt *)l->Next();
+ }
+ return false;
+}
+
+cSearchExts* cSearchExts::Clone()
+{
+ cSearchExts* clonedList = new cSearchExts();
+
+ cMutexLock SearchExtsLock(this);
+ cSearchExt *l = (cSearchExt *)First();
+ while (l)
+ {
+ cSearchExt* clone = new cSearchExt();
+ *clone = *l;
+ clonedList->Add(clone);
+ l = (cSearchExt *)l->Next();
+ }
+ return clonedList;
+}
+
+bool cSearchExts::CheckForAutoDelete(cSearchExt* SearchExt)
+{
+ if (!SearchExt || SearchExt->delMode == 0) return false;
+
+ cRecDone* firstRec = NULL;
+ bool delSearch = false;
+ int recs = RecsDone.GetTotalCountRecordings(SearchExt, &firstRec);
+ if (SearchExt->delMode == 1 && SearchExt->delAfterCountRecs > 0)
+ delSearch = recs >= SearchExt->delAfterCountRecs;
+ if (SearchExt->delMode == 2 && SearchExt->delAfterDaysOfFirstRec && firstRec)
+ delSearch = (time(NULL) - firstRec->startTime) > SearchExt->delAfterDaysOfFirstRec * 24 * 60 * 60;
+ if (delSearch)
+ {
+ int DelID = SearchExt->ID;
+ LogFile.Log(1,"auto deleting search '%s' (ID: %d)", SearchExt->search, DelID);
+ cMutexLock SearchExtsLock(&SearchExts);
+ SearchExts.Del(SearchExt);
+ SearchExts.Save();
+ RecsDone.RemoveSearchID(DelID);
+ }
+ return delSearch;
+}