diff options
author | Christian Wieninger <winni@debian.(none)> | 2007-11-11 15:40:28 +0100 |
---|---|---|
committer | Christian Wieninger <winni@debian.(none)> | 2007-11-11 15:40:28 +0100 |
commit | 8d4f8607dc1558ce73eb4c376bdbf78ddb65da83 (patch) | |
tree | d0c5dde81a36ab2e8a2edc7c1e6922556518b312 /epgsearchext.c | |
download | vdr-plugin-epgsearch-8d4f8607dc1558ce73eb4c376bdbf78ddb65da83.tar.gz vdr-plugin-epgsearch-8d4f8607dc1558ce73eb4c376bdbf78ddb65da83.tar.bz2 |
Initial commit
Diffstat (limited to 'epgsearchext.c')
-rw-r--r-- | epgsearchext.c | 1559 |
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; +} |