| 1 |  | 
| 2 |  | 
| 3 |   | 
| 4 |  | 
| 5 |  | 
| 6 |  | 
| 7 |  | 
| 8 |   | 
| 9 |  | 
| 10 |  | 
| 11 |  | 
| 12 |  | 
| 13 |   | 
| 14 |  | 
| 15 |  | 
| 16 |  | 
| 17 |  | 
| 18 |   | 
| 19 |  | 
| 20 |   | 
| 21 |  | 
| 22 |  | 
| 23 |   | 
| 24 | #include <vector> | 
| 25 | #include "epgsearchext.h" | 
| 26 | #include "epgsearchcfg.h" | 
| 27 | #include "epgsearchcats.h" | 
| 28 | #include "epgsearchtools.h" | 
| 29 | #include <vdr/tools.h> | 
| 30 | #include "menu_searchresults.h" | 
| 31 | #include "menu_dirselect.h" | 
| 32 | #include "changrp.h" | 
| 33 | #include "menu_search.h" | 
| 34 | #include "menu_searchedit.h" | 
| 35 | #include "menu_recsdone.h" | 
| 36 | #include "searchtimer_thread.h" | 
| 37 | #include "timer_thread.h" | 
| 38 | #include "uservars.h" | 
| 39 | #include "blacklist.h" | 
| 40 | #include <math.h> | 
| 41 |   | 
| 42 | cSearchExts SearchExts; | 
| 43 | cSearchExts SearchTemplates; | 
| 44 |   | 
| 45 | #ifndef MAX_SUBTITLE_LENGTH40 | 
| 46 |   #define MAX_SUBTITLE_LENGTH40 40 | 
| 47 | #endif | 
| 48 |   | 
| 49 |  | 
| 50 | char *cSearchExt::buffer = NULL__null; | 
| 51 |   | 
| 52 | cSearchExt::cSearchExt(void) | 
| 53 | { | 
| 54 |    ID = -1; | 
| 55 |    *search = 0; | 
| 56 |    options = 1; | 
| 57 |    useTime = false; | 
| 58 |    startTime = 0000; | 
| 59 |    stopTime = 2359; | 
| 60 |    useChannel = false; | 
| 61 |    channelMin = Channels.GetByNumber(cDevice::CurrentChannel()); | 
| 62 |    channelMax = Channels.GetByNumber(cDevice::CurrentChannel()); | 
| 63 |    channelGroup = NULL__null; | 
| 64 |    useCase = false; | 
| 65 |    mode = 0; | 
| 66 |    useTitle = true; | 
| 67 |    useSubtitle = true; | 
| 68 |    useDescription = true; | 
| 69 |    useDuration = false; | 
| 70 |    minDuration = 0; | 
| 71 |    maxDuration = 2359; | 
| 72 |    useAsSearchTimer = false; | 
| 73 |    useDayOfWeek = false; | 
| 74 |    DayOfWeek = 0; | 
| 75 |    buffer = NULL__null; | 
| 76 |    *directory = 0; | 
| 77 |    useEpisode = 0; | 
| 78 |    Priority = EPGSearchConfig.DefPriority; | 
| 79 |    Lifetime = EPGSearchConfig.DefLifetime; | 
| 80 |    MarginStart = EPGSearchConfig.DefMarginStart; | 
| 81 |    MarginStop = EPGSearchConfig.DefMarginStop; | 
| 82 |    useVPS = false; | 
| 83 |    action = searchTimerActionRecord; | 
| 84 |    useExtEPGInfo = false; | 
| 85 |    contentsFilter = ""; | 
| 86 |    catvalues = (char**) malloc(SearchExtCats.Count() * sizeof(char*)); | 
| 87 |    cSearchExtCat *SearchExtCat = SearchExtCats.First(); | 
| 88 |    int index = 0; | 
| 89 |    while (SearchExtCat) | 
| 90 |    { | 
| 91 |       catvalues[index] = (char*)malloc(MaxFileName255); | 
| 92 |       *catvalues[index] = 0; | 
| 93 |       SearchExtCat = SearchExtCats.Next(SearchExtCat); | 
| 94 |       index++; | 
| 95 |    } | 
| 96 |    avoidRepeats = 0; | 
| 97 |    compareTitle = 1; | 
| 98 |    compareSubtitle = 1; | 
| 99 |    compareSummary = 1; | 
| 100 |    compareSummaryMatchInPercent = 90; | 
| 101 |    compareDate = 0; | 
| 102 |    allowedRepeats = 0; | 
| 103 |    catvaluesAvoidRepeat = 0; | 
| 104 |    repeatsWithinDays = 0; | 
| 105 |    delAfterDays = 0; | 
| 106 |    recordingsKeep = 0; | 
| 107 |    switchMinsBefore = 1; | 
| 108 |    pauseOnNrRecordings = 0; | 
| 109 |    blacklistMode = blacklistsOnlyGlobal;  | 
| 110 |    blacklists.Clear(); | 
| 111 |    fuzzyTolerance = 1; | 
| 112 |    useInFavorites = 0; | 
| 113 |    menuTemplate = 0; | 
| 114 |    delMode = 0; | 
| 115 |    delAfterCountRecs = 0; | 
| 116 |    delAfterDaysOfFirstRec = 0; | 
| 117 |    useAsSearchTimerFrom = 0; | 
| 118 |    useAsSearchTimerTil = 0; | 
| 119 |    ignoreMissingEPGCats = 0; | 
| 120 |    unmuteSoundOnSwitch = 0; | 
| 121 |    skipRunningEvents = false; | 
| 122 | } | 
| 123 |   | 
| 124 | cSearchExt::~cSearchExt(void) | 
| 125 | { | 
| 126 |    if (buffer) { | 
| 127 |       free(buffer); | 
| 128 |       buffer = NULL__null; | 
| 129 |    } | 
| 130 |   | 
| 131 |    if (catvalues) | 
| 132 |    { | 
| 133 |       cSearchExtCat *SearchExtCat = SearchExtCats.First(); | 
| 134 |       int index = 0; | 
| 135 |       while (SearchExtCat) | 
| 136 |       { | 
| 137 | 	free(catvalues[index]); | 
| 138 | 	SearchExtCat = SearchExtCats.Next(SearchExtCat); | 
| 139 | 	index++; | 
| 140 |       } | 
| 141 |       free(catvalues); | 
| 142 |       catvalues = NULL__null; | 
| 143 |    } | 
| 144 | } | 
| 145 |   | 
| 146 | cSearchExt& cSearchExt::operator= (const cSearchExt &SearchExt) | 
| 147 | { | 
| 148 |    CopyFromTemplate(&SearchExt); | 
| 149 |    ID = SearchExt.ID; | 
| 150 |    strcpy(search, SearchExt.search); | 
| 151 |   | 
| 152 |    cSearchExtCat *SearchExtCat = SearchExtCats.First(); | 
| 153 |    int index = 0; | 
| 154 |    while (SearchExtCat) | 
| 155 |    { | 
| 156 |       *catvalues[index] = 0; | 
| 157 |       strcpy(catvalues[index], SearchExt.catvalues[index]); | 
| 158 |       SearchExtCat = SearchExtCats.Next(SearchExtCat); | 
| 159 |       index++; | 
| 160 |    } | 
| 161 |   | 
| 162 |    return *this; | 
| 163 | } | 
| 164 |   | 
| 165 |   void cSearchExt::CopyFromTemplate(const cSearchExt* templ, bool ignoreChannelSettings) | 
| 166 | { | 
| 167 |    options = templ->options; | 
| 168 |    useTime = templ->useTime; | 
| 169 |    startTime = templ->startTime; | 
| 170 |    stopTime = templ->stopTime; | 
| 171 |    if (!ignoreChannelSettings) | 
| 172 |      useChannel = templ->useChannel; | 
| 173 |    useCase = templ->useCase; | 
| 174 |    mode = templ->mode; | 
| 175 |    useTitle = templ->useTitle; | 
| 176 |    useSubtitle = templ->useSubtitle; | 
| 177 |    useDescription = templ->useDescription; | 
| 178 |    useDuration = templ->useDuration; | 
| 179 |    minDuration = templ->minDuration; | 
| 180 |    maxDuration = templ->maxDuration; | 
| 181 |    useAsSearchTimer = templ->useAsSearchTimer; | 
| 182 |    useDayOfWeek = templ->useDayOfWeek; | 
| 183 |    DayOfWeek = templ->DayOfWeek; | 
| 184 |    useEpisode = templ->useEpisode; | 
| 185 |    strcpy(directory, templ->directory); | 
| 186 |    Priority = templ->Priority; | 
| 187 |    Lifetime = templ->Lifetime; | 
| 188 |    MarginStart = templ->MarginStart; | 
| 189 |    MarginStop = templ->MarginStop; | 
| 190 |    useVPS = templ->useVPS; | 
| 191 |    action = templ->action; | 
| 192 |    useExtEPGInfo = templ->useExtEPGInfo; | 
| 193 |    contentsFilter = templ->contentsFilter; | 
| 194 |    switchMinsBefore = templ->switchMinsBefore; | 
| 195 |    pauseOnNrRecordings = templ->pauseOnNrRecordings; | 
| 196 |   | 
| 197 |    cSearchExtCat *SearchExtCat = SearchExtCats.First(); | 
| 198 |    int index = 0; | 
| 199 |    while (SearchExtCat) | 
| 200 |    { | 
| 201 |       strcpy(catvalues[index], templ->catvalues[index]); | 
| 202 |       SearchExtCat = SearchExtCats.Next(SearchExtCat); | 
| 203 |       index++; | 
| 204 |    } | 
| 205 |   | 
| 206 |    if (!ignoreChannelSettings) | 
| 207 |      { | 
| 208 |        channelMin = templ->channelMin; | 
| 209 |        channelMax = templ->channelMax; | 
| 210 |        if (channelGroup) | 
| 211 | 	 { | 
| 212 | 	   free(channelGroup); | 
| 213 | 	   channelGroup = NULL__null; | 
| 214 | 	 } | 
| 215 |        if (templ->channelGroup) | 
| 216 | 	 channelGroup = strdup(templ->channelGroup); | 
| 217 |      } | 
| 218 |    avoidRepeats = templ->avoidRepeats; | 
| 219 |    compareTitle = templ->compareTitle; | 
| 220 |    compareSubtitle = templ->compareSubtitle; | 
| 221 |    compareSummary = templ->compareSummary; | 
| 222 |    compareSummaryMatchInPercent = templ->compareSummaryMatchInPercent; | 
| 223 |    compareDate = templ->compareDate; | 
| 224 |    allowedRepeats = templ->allowedRepeats; | 
| 225 |    catvaluesAvoidRepeat = templ->catvaluesAvoidRepeat; | 
| 226 |    repeatsWithinDays = templ->repeatsWithinDays; | 
| 227 |    delAfterDays = templ->delAfterDays; | 
| 228 |    recordingsKeep = templ->recordingsKeep; | 
| 229 |    blacklistMode = templ->blacklistMode; | 
| 230 |    blacklists.Clear(); | 
| 231 |    cBlacklistObject* blacklistObj = templ->blacklists.First(); | 
| 232 |    while(blacklistObj) | 
| 233 |    { | 
| 234 |       blacklists.Add(new cBlacklistObject(blacklistObj->blacklist)); | 
| 235 |       blacklistObj = templ->blacklists.Next(blacklistObj); | 
| 236 |    } | 
| 237 |    fuzzyTolerance = templ->fuzzyTolerance; | 
| 238 |    useInFavorites = templ->useInFavorites; | 
| 239 |    menuTemplate = templ->menuTemplate; | 
| 240 |    delMode = templ->delMode; | 
| 241 |    delAfterCountRecs = templ->delAfterCountRecs; | 
| 242 |    delAfterDaysOfFirstRec = templ->delAfterDaysOfFirstRec; | 
| 243 |    useAsSearchTimerFrom = templ->useAsSearchTimerFrom; | 
| 244 |    useAsSearchTimerTil = templ->useAsSearchTimerTil; | 
| 245 |    ignoreMissingEPGCats = templ->ignoreMissingEPGCats; | 
| 246 |    unmuteSoundOnSwitch = templ->unmuteSoundOnSwitch; | 
| 247 | } | 
| 248 |   | 
| 249 | bool cSearchExt::operator< (const cListObject &ListObject) | 
| 250 | { | 
| 251 |    cSearchExt *SE = (cSearchExt *)&ListObject; | 
| 252 |    return strcasecmp(search, SE->search) < 0; | 
| 253 | } | 
| 254 |   | 
| 255 | char* replaceSpecialChars(const char* in) | 
| 256 | { | 
| 257 |    char* tmp_in = strdup(in); | 
| 258 |    while(strstr(tmp_in, "|")) | 
| 259 |       tmp_in = strreplace(tmp_in, "|", "!^pipe^!");  | 
| 260 |    strreplace(tmp_in, ':', '|'); | 
| 261 |    return tmp_in; | 
| 262 | } | 
| 263 |   | 
| 264 | const char *cSearchExt::ToText() | 
| 265 | { | 
| 266 |    char tmp_Start[5] = ""; | 
| 267 |    char tmp_Stop[5] = ""; | 
| 268 |    char tmp_minDuration[5] = ""; | 
| 269 |    char tmp_maxDuration[5] = ""; | 
| 270 |    cString tmp_chanSel; | 
| 271 |    char* tmp_catvalues = NULL__null; | 
| 272 |    char* tmp_blacklists = NULL__null; | 
| 273 |   | 
| 274 |    free(buffer); | 
| 275 |    char* tmp_search = replaceSpecialChars(search); | 
| 276 |    char* tmp_directory = replaceSpecialChars(directory); | 
| 277 |    char* tmp_contentsFilter = replaceSpecialChars(contentsFilter.c_str()); | 
| 278 |   | 
| 279 |    if (useTime) | 
| 280 |    { | 
| 281 |       sprintf(tmp_Start, "%04d", startTime); | 
| 282 |       sprintf(tmp_Stop, "%04d", stopTime); | 
| 283 |    } | 
| 284 |    if (useDuration) | 
| 285 |    { | 
| 286 |       sprintf(tmp_minDuration, "%04d", minDuration); | 
| 287 |       sprintf(tmp_maxDuration, "%04d", maxDuration); | 
| 288 |    } | 
| 289 |   | 
| 290 |    if (useChannel==1) | 
| 291 |    { | 
| 292 |       if (channelMin->Number() < channelMax->Number()) | 
| 293 | 	tmp_chanSel = cString::sprintf("%s|%s", CHANNELSTRING(channelMin)(*channelMin->GetChannelID().ToString()), CHANNELSTRING(channelMax)(*channelMax->GetChannelID().ToString())); | 
| 294 |       else | 
| 295 | 	tmp_chanSel = cString(CHANNELSTRING(channelMin)(*channelMin->GetChannelID().ToString())); | 
| 296 |    } | 
| 297 |    if (useChannel==2) | 
| 298 |    { | 
| 299 |       int channelGroupNr = ChannelGroups.GetIndex(channelGroup); | 
| 300 |       if (channelGroupNr == -1) | 
| 301 |       { | 
| 302 |          LogFile.eSysLog("channel group '%s' does not exist!", channelGroup); | 
| 303 |          useChannel = 0; | 
| 304 |       } | 
| 305 |       else | 
| 306 | 	tmp_chanSel = cString(channelGroup); | 
| 307 |    } | 
| 308 |   | 
| 309 |    if (useExtEPGInfo) | 
| 310 |    { | 
| 311 |       cSearchExtCat *SearchExtCat = SearchExtCats.First(); | 
| 312 |       int index = 0; | 
| 313 |       while (SearchExtCat) | 
| 314 |       { | 
| 315 |          char* catvalue = NULL__null; | 
| 316 |          if (msprintf(&catvalue, "%s", catvalues[index])==-1) break; | 
| 317 |          while(strstr(catvalue, ":")) | 
| 318 |             catvalue = strreplace(catvalue, ":", "!^colon^!");  | 
| 319 |          while(strstr(catvalue, "|")) | 
| 320 |             catvalue = strreplace(catvalue, "|", "!^pipe^!");  | 
| 321 |   | 
| 322 |          if (index == 0) | 
| 323 |             msprintf(&tmp_catvalues, "%d#%s", SearchExtCat->id, catvalue); | 
| 324 |          else | 
| 325 |          { | 
| 326 |             char* temp = tmp_catvalues; | 
| 327 |             msprintf(&tmp_catvalues, "%s|%d#%s", tmp_catvalues, SearchExtCat->id, catvalue); | 
| 328 |             free(temp); | 
| 329 |          } | 
| 330 |          SearchExtCat = SearchExtCats.Next(SearchExtCat); | 
| 331 |          index++; | 
| 332 |          free(catvalue); | 
| 333 |       } | 
| 334 |    } | 
| 335 |   | 
| 336 |    if (blacklistMode == blacklistsSelection && blacklists.Count() > 0) | 
| 337 |    { | 
| 338 |       cBlacklistObject *blacklistObj = blacklists.First(); | 
| 339 |       int index = 0; | 
| 340 |       while (blacklistObj) | 
| 341 |       { | 
| 342 |          if (index == 0) | 
| 343 |             msprintf(&tmp_blacklists, "%d", blacklistObj->blacklist->ID); | 
| 344 |          else | 
| 345 |          { | 
| 346 |             char* temp = tmp_blacklists; | 
| 347 |             msprintf(&tmp_blacklists, "%s|%d", tmp_blacklists, blacklistObj->blacklist->ID); | 
| 348 |             free(temp); | 
| 349 |          } | 
| 350 |          blacklistObj = blacklists.Next(blacklistObj); | 
| 351 |          index++; | 
| 352 |       } | 
| 353 |    } | 
| 354 |   | 
| 355 |    msprintf(&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:%d:%s:%d", | 
| 356 |             ID, | 
| 357 |             tmp_search, | 
| 358 |             useTime, | 
| 359 |             tmp_Start, | 
| 360 |             tmp_Stop, | 
| 361 |             useChannel, | 
| 362 |             (useChannel>0 && useChannel<3)?*tmp_chanSel:"0", | 
| 363 |             useCase, | 
| 364 |             mode, | 
| 365 |             useTitle, | 
| 366 |             useSubtitle, | 
| 367 |             useDescription, | 
| 368 |             useDuration, | 
| 369 |             tmp_minDuration, | 
| 370 |             tmp_maxDuration, | 
| 371 |             useAsSearchTimer, | 
| 372 |             useDayOfWeek, | 
| 373 |             DayOfWeek, | 
| 374 |             useEpisode, | 
| 375 |             tmp_directory, | 
| 376 |             Priority, | 
| 377 |             Lifetime, | 
| 378 |             MarginStart, | 
| 379 |             MarginStop, | 
| 380 |             useVPS, | 
| 381 |             action, | 
| 382 |             useExtEPGInfo, | 
| 383 |             useExtEPGInfo?tmp_catvalues:"", | 
| 384 |             avoidRepeats, | 
| 385 |             allowedRepeats, | 
| 386 |             compareTitle, | 
| 387 |             compareSubtitle, | 
| 388 |             compareSummary, | 
| 389 |             catvaluesAvoidRepeat, | 
| 390 |             repeatsWithinDays, | 
| 391 |             delAfterDays, | 
| 392 |             recordingsKeep, | 
| 393 |             switchMinsBefore, | 
| 394 |             pauseOnNrRecordings, | 
| 395 |             blacklistMode, | 
| 396 |             blacklists.Count()>0?tmp_blacklists:"", | 
| 397 |             fuzzyTolerance, | 
| 398 |             useInFavorites, | 
| 399 |             menuTemplate, | 
| 400 |             delMode, | 
| 401 |             delAfterCountRecs, | 
| 402 |             delAfterDaysOfFirstRec, | 
| 403 | 	    useAsSearchTimerFrom, | 
| 404 | 	    useAsSearchTimerTil, | 
| 405 | 	    ignoreMissingEPGCats, | 
| 406 | 	    unmuteSoundOnSwitch, | 
| 407 | 	    compareSummaryMatchInPercent, | 
| 408 | 	    contentsFilter.c_str(), | 
| 409 | 	    compareDate); | 
| 410 |   | 
| 411 |    if (tmp_search) free(tmp_search); | 
| 412 |    if (tmp_directory) free(tmp_directory); | 
| 413 |    if (tmp_catvalues) free(tmp_catvalues); | 
| 414 |    if (tmp_blacklists) free(tmp_blacklists); | 
| 415 |    if (tmp_contentsFilter) free(tmp_contentsFilter); | 
| 416 |   | 
| 417 |    return buffer; | 
| 418 | } | 
| 419 |   | 
| 420 | bool cSearchExt::Parse(const char *s) | 
| 421 | { | 
| 422 |    char *line; | 
| 423 |    char *pos; | 
| 424 |    char *pos_next; | 
| 425 |    int parameter = 1; | 
| 426 |    int valuelen; | 
| 427 |    char value[MaxFileName255]; | 
| 428 |    bool disableSearchtimer = false; | 
| 429 |   | 
| 430 |    *directory = 0; | 
| 431 |    *search = 0; | 
| 432 |   | 
| 433 |    pos = line = strdup(s); | 
 |  | 
| 434 |    pos_next = pos + strlen(pos); | 
| 435 |    if (*pos_next == '\n') *pos_next = 0; | 
 |  | 
| 436 |    while (*pos) { | 
 | 3  |  | Loop condition is true.  Entering loop body |  |  
  | 
| 437 |       while (*pos == ' ') pos++; | 
 | 4  |  | Loop condition is false. Execution continues on line 438 |  |  
  | 
| 438 |       if (*pos) { | 
 |  | 
| 439 |          if (*pos != ':') { | 
 |  | 
| 440 |             pos_next = strchr(pos, ':'); | 
| 441 |             if (!pos_next) | 
 | 7  |  | Assuming 'pos_next' is non-null |  |  
  | 
 |  | 
| 442 |                pos_next = pos + strlen(pos); | 
| 443 |             valuelen = pos_next - pos + 1; | 
| 444 |             if (valuelen > MaxFileName255) valuelen = MaxFileName255; | 
 | 9  |  | Assuming 'valuelen' is <= 255 |  |  
  | 
 |  | 
| 445 |             strn0cpy(value, pos, valuelen); | 
| 446 |             pos = pos_next; | 
| 447 |             switch (parameter) { | 
 | 11  |  | Control jumps to 'case 1:'  at line 448 |  |  
  | 
| 448 |                case 1: | 
| 449 |                   if (!isnumber(value)) return false; | 
 |  | 
 | 13  |  | Potential leak of memory pointed to by 'pos' |  
  | 
| 450 |                   ID = atoi(value); | 
| 451 |                   break; | 
| 452 |                case 2:  strcpy(search, value); | 
| 453 |                   break; | 
| 454 |                case 3:  useTime = atoi(value); | 
| 455 |                   break; | 
| 456 |                case 4:  startTime = atoi(value); | 
| 457 |                   break; | 
| 458 |                case 5:  stopTime = atoi(value); | 
| 459 |                   break; | 
| 460 |                case 6:  useChannel = atoi(value); | 
| 461 |                   break; | 
| 462 |                case 7: | 
| 463 |                   if (useChannel == 0) | 
| 464 |                   { | 
| 465 |                      channelMin = NULL__null; | 
| 466 |                      channelMax = NULL__null; | 
| 467 |                   } | 
| 468 |                   else if (useChannel == 1) | 
| 469 |                   { | 
| 470 |                      int minNum=0, maxNum=0; | 
| 471 |                      int fields = sscanf(value, "%d-%d", &minNum, &maxNum); | 
| 472 |                      if (fields == 0)  | 
| 473 |                      { | 
| 474 | #ifdef __FreeBSD__ | 
| 475 |                         char *channelMinbuffer = MALLOC(char, 32)(char *)malloc(sizeof(char) * (32)); | 
| 476 |                         char *channelMaxbuffer = MALLOC(char, 32)(char *)malloc(sizeof(char) * (32)); | 
| 477 |                         int channels = sscanf(value, "%31[^|]|%31[^|]", channelMinbuffer, channelMaxbuffer); | 
| 478 | #else | 
| 479 |                         char *channelMinbuffer = NULL__null; | 
| 480 |                         char *channelMaxbuffer = NULL__null; | 
| 481 |                         int channels = sscanf(value, "%m[^|]|%m[^|]", &channelMinbuffer, &channelMaxbuffer); | 
| 482 | #endif | 
| 483 |                         channelMin = Channels.GetByChannelID(tChannelID::FromString(channelMinbuffer), true, true); | 
| 484 |                         if (!channelMin) | 
| 485 |                         { | 
| 486 |                            LogFile.eSysLog("ERROR: channel '%s' not defined", channelMinbuffer); | 
| 487 |                            channelMin = channelMax = NULL__null; | 
| 488 |                            disableSearchtimer = true; | 
| 489 |                            useChannel = 0; | 
| 490 |                         } | 
| 491 |                         if (channels == 1) | 
| 492 |                            channelMax = channelMin; | 
| 493 |                         else | 
| 494 |                         { | 
| 495 |                            channelMax = Channels.GetByChannelID(tChannelID::FromString(channelMaxbuffer), true, true); | 
| 496 |                            if (!channelMax) | 
| 497 |                            { | 
| 498 |                               LogFile.eSysLog("ERROR: channel '%s' not defined", channelMaxbuffer); | 
| 499 |                               channelMin = channelMax = NULL__null; | 
| 500 |                               disableSearchtimer = true; | 
| 501 |                               useChannel = 0; | 
| 502 |                            } | 
| 503 |                         } | 
| 504 |                         free(channelMinbuffer); | 
| 505 |                         free(channelMaxbuffer); | 
| 506 |                      } | 
| 507 |                   } | 
| 508 |                   else if (useChannel == 2) | 
| 509 |                      channelGroup = strdup(value); | 
| 510 |                   break; | 
| 511 |                case 8:  useCase = atoi(value); | 
| 512 |                   break; | 
| 513 |                case 9:  mode = atoi(value); | 
| 514 |                   break; | 
| 515 |                case 10: useTitle = atoi(value); | 
| 516 |                   break; | 
| 517 |                case 11: useSubtitle = atoi(value); | 
| 518 |                   break; | 
| 519 |                case 12: useDescription = atoi(value); | 
| 520 |                   break; | 
| 521 |                case 13: useDuration = atoi(value); | 
| 522 |                   break; | 
| 523 |                case 14: minDuration = atoi(value); | 
| 524 |                   break; | 
| 525 |                case 15: maxDuration = atoi(value); | 
| 526 |                   break; | 
| 527 |                case 16: useAsSearchTimer = atoi(value); | 
| 528 |                   break; | 
| 529 |                case 17: useDayOfWeek = atoi(value); | 
| 530 |                   break; | 
| 531 |                case 18: DayOfWeek = atoi(value); | 
| 532 |                   break; | 
| 533 |                case 19: useEpisode = atoi(value); | 
| 534 |                   break; | 
| 535 |                case 20:  strcpy(directory, value); | 
| 536 |                   break; | 
| 537 |                case 21: Priority = atoi(value); | 
| 538 |                   break; | 
| 539 |                case 22: Lifetime = atoi(value); | 
| 540 |                   break; | 
| 541 |                case 23: MarginStart = atoi(value); | 
| 542 |                   break; | 
| 543 |                case 24: MarginStop = atoi(value); | 
| 544 |                   break; | 
| 545 |                case 25: useVPS = atoi(value); | 
| 546 |                   break; | 
| 547 |                case 26: action = atoi(value); | 
| 548 |                   break; | 
| 549 |                case 27: useExtEPGInfo = atoi(value); | 
| 550 |                   break; | 
| 551 |                case 28: | 
| 552 |                   if (!ParseExtEPGValues(value)) | 
| 553 |                   { | 
| 554 |                      LogFile.eSysLog("ERROR reading ext. EPG values - 1"); | 
| 555 |                      free(line); | 
| 556 |                      return false; | 
| 557 |                   } | 
| 558 |                   break; | 
| 559 |                case 29: avoidRepeats = atoi(value); | 
| 560 |                   break; | 
| 561 |                case 30: allowedRepeats = atoi(value); | 
| 562 |                   break; | 
| 563 |                case 31: compareTitle = atoi(value); | 
| 564 |                   break; | 
| 565 | 	       case 32: compareSubtitle = atoi(value)>0?1:0; | 
| 566 |                   break; | 
| 567 |                case 33: compareSummary = atoi(value); | 
| 568 |                   break; | 
| 569 |                case 34: catvaluesAvoidRepeat = atol(value); | 
| 570 |                   break; | 
| 571 |                case 35: repeatsWithinDays = atoi(value); | 
| 572 |                   break; | 
| 573 |                case 36: delAfterDays = atoi(value); | 
| 574 |                   break; | 
| 575 |                case 37:  recordingsKeep = atoi(value); | 
| 576 |                   break; | 
| 577 |                case 38: switchMinsBefore = atoi(value); | 
| 578 |                   break; | 
| 579 |                case 39: pauseOnNrRecordings = atoi(value); | 
| 580 |                   break; | 
| 581 |                case 40: blacklistMode = atoi(value); | 
| 582 |                   break; | 
| 583 |                case 41: | 
| 584 |                   if (blacklistMode == blacklistsSelection && !ParseBlacklistIDs(value)) | 
| 585 |                   { | 
| 586 |                      LogFile.eSysLog("ERROR parsing blacklist IDs"); | 
| 587 |                      free(line); | 
| 588 |                      return false; | 
| 589 |                   } | 
| 590 |                   break; | 
| 591 |                case 42: fuzzyTolerance = atoi(value); | 
| 592 |                   break; | 
| 593 |                case 43: useInFavorites = atoi(value); | 
| 594 |                   break; | 
| 595 |                case 44: menuTemplate = atoi(value); | 
| 596 |                   break; | 
| 597 |                case 45: delMode = atoi(value); | 
| 598 |                   break; | 
| 599 |                case 46: delAfterCountRecs = atoi(value); | 
| 600 |                   break; | 
| 601 |                case 47: delAfterDaysOfFirstRec = atoi(value); | 
| 602 |                   break; | 
| 603 | 	    case 48: | 
| 604 | 	      useAsSearchTimerFrom = atol(value); | 
| 605 | 	      break; | 
| 606 | 	    case 49: | 
| 607 | 	      useAsSearchTimerTil = atol(value); | 
| 608 | 	      break; | 
| 609 | 	    case 50: | 
| 610 | 	      ignoreMissingEPGCats = atoi(value); | 
| 611 | 	      break; | 
| 612 | 	    case 51: | 
| 613 | 	      unmuteSoundOnSwitch = atoi(value); | 
| 614 | 	      break; | 
| 615 | 	    case 52: | 
| 616 | 	      compareSummaryMatchInPercent = atoi(value); | 
| 617 | 	      break; | 
| 618 | 	    case 53: | 
| 619 | 	      contentsFilter = value; | 
| 620 | 	      break; | 
| 621 | 	    case 54: | 
| 622 | 	      compareDate = atoi(value); | 
| 623 | 	      break; | 
| 624 | 	    default: | 
| 625 | 	      break; | 
| 626 |             }  | 
| 627 |          } | 
| 628 |          parameter++; | 
| 629 |       } | 
| 630 |       if (*pos) pos++; | 
| 631 |    }  | 
| 632 |   | 
| 633 |    strreplace(directory, '|', ':'); | 
| 634 |    strreplace(search, '|', ':'); | 
| 635 |    strreplace(contentsFilter, "|", ":"); | 
| 636 |   | 
| 637 |    while(strstr(search, "!^pipe^!")) | 
| 638 |       strreplace(search, "!^pipe^!", "|"); | 
| 639 |    while(strstr(directory, "!^pipe^!")) | 
| 640 |       strreplace(directory, "!^pipe^!", "|"); | 
| 641 |    strreplace(contentsFilter, "!^pipe^!", "|"); | 
| 642 |   | 
| 643 |    if (disableSearchtimer && useAsSearchTimer) | 
| 644 |    { | 
| 645 |       useAsSearchTimer = false; | 
| 646 |       LogFile.Log(1, "search timer '%s' disabled", search); | 
| 647 |    } | 
| 648 |   | 
| 649 |    free(line); | 
| 650 |    return (parameter >= 11) ? true : false; | 
| 651 | } | 
| 652 |   | 
| 653 | char* cSearchExt::BuildFile(const cEvent* pEvent) const | 
| 654 | { | 
| 655 |    char* file = NULL__null; | 
| 656 |   | 
| 657 |    if (!pEvent) | 
| 658 |       return file; | 
| 659 |   | 
| 660 |    const char *Subtitle = pEvent ? pEvent->ShortText() : NULL__null; | 
| 661 |    char SubtitleBuffer[Utf8BufSize(MAX_SUBTITLE_LENGTH)((40) * 4)]; | 
| 662 |    if (isempty(Subtitle)) | 
| 663 |    { | 
| 664 |      time_t Start = pEvent->StartTime(); | 
| 665 |      struct tm tm_r; | 
| 666 |      strftime(SubtitleBuffer, sizeof(SubtitleBuffer), "%Y.%m.%d-%R-%a", localtime_r(&Start, &tm_r)); | 
| 667 |      Subtitle = SubtitleBuffer; | 
| 668 |    } | 
| 669 |    else if (Utf8StrLen(Subtitle) > MAX_SUBTITLE_LENGTH40) | 
| 670 |    { | 
| 671 |       Utf8Strn0Cpy(SubtitleBuffer, Subtitle, sizeof(SubtitleBuffer)); | 
| 672 |       SubtitleBuffer[Utf8SymChars(SubtitleBuffer, MAX_SUBTITLE_LENGTH40)] = 0; | 
| 673 |       Subtitle = SubtitleBuffer; | 
| 674 |    } | 
| 675 |   | 
| 676 |    if (useEpisode) | 
| 677 |    { | 
| 678 |      cString pFile = cString::sprintf("%s~%s", pEvent->Title(), Subtitle); | 
| 679 |      if (file) free(file); | 
| 680 |      file = strdup(pFile); | 
| 681 |    } | 
| 682 |    else if (pEvent->Title()) | 
| 683 |      file = strdup(pEvent->Title()); | 
| 684 |   | 
| 685 |    if (!isempty(directory)) | 
| 686 |    { | 
| 687 |       char* pFile = NULL__null; | 
| 688 |   | 
| 689 |       cVarExpr varExprDir(directory); | 
| 690 |       if (!varExprDir.DependsOnVar("%title%", pEvent) && !varExprDir.DependsOnVar("%subtitle%", pEvent)) | 
| 691 |          msprintf(&pFile, "%s~%s", directory, file?file:""); | 
| 692 |       else | 
| 693 |           | 
| 694 |          msprintf(&pFile, "%s", directory); | 
| 695 |   | 
| 696 |        | 
| 697 |       cVarExpr varExprFile(pFile); | 
| 698 |       if (pFile) free(pFile); | 
| 699 |       pFile = strdup(varExprFile.Evaluate(pEvent).c_str()); | 
| 700 |   | 
| 701 |       cVarExpr varExprSearchFile(pFile); | 
| 702 |       if (pFile) free(pFile); | 
| 703 |       pFile = strdup(varExprSearchFile.Evaluate(this).c_str()); | 
| 704 |   | 
| 705 |       if (file) free(file); | 
| 706 |       file = strdup(pFile); | 
| 707 |       free(pFile); | 
| 708 |    } | 
| 709 |  | 
| 710 |    if (file) | 
| 711 |    { | 
| 712 |       while(strstr(file, "|")) file = strreplace(file, "|", "!^pipe^!"); | 
| 713 |       while(strstr(file, ":")) file = strreplace(file, ':', '|'); | 
| 714 |       while(strstr(file, " ~")) file = strreplace(file, " ~", "~"); | 
| 715 |       while(strstr(file, "~ ")) file = strreplace(file, "~ ", "~"); | 
| 716 |    } | 
| 717 |    return file; | 
| 718 | } | 
| 719 |   | 
| 720 | bool cSearchExt::ParseBlacklistIDs(const char *s) | 
| 721 | { | 
| 722 |    char *line; | 
| 723 |    char *pos; | 
| 724 |    char *pos_next; | 
| 725 |    int valuelen; | 
| 726 |    char value[MaxFileName255]; | 
| 727 |   | 
| 728 |    cMutexLock BlacklistLock(&Blacklists); | 
| 729 |    blacklists.Clear(); | 
| 730 |   | 
| 731 |    pos = line = strdup(s); | 
| 732 |    pos_next = pos + strlen(pos); | 
| 733 |    if (*pos_next == '\n') *pos_next = 0; | 
| 734 |    while (*pos) { | 
| 735 |       while (*pos == ' ') pos++; | 
| 736 |       if (*pos) { | 
| 737 |          if (*pos != '|') { | 
| 738 |             pos_next = strchr(pos, '|'); | 
| 739 |             if (!pos_next) | 
| 740 |                pos_next = pos + strlen(pos); | 
| 741 |             valuelen = pos_next - pos + 1; | 
| 742 |             if (valuelen > MaxFileName255) valuelen = MaxFileName255; | 
| 743 |             strn0cpy(value, pos, valuelen); | 
| 744 |             pos = pos_next; | 
| 745 |             cBlacklist* blacklist = Blacklists.GetBlacklistFromID(atoi(value)); | 
| 746 |             if (!blacklist) | 
| 747 |                LogFile.eSysLog("blacklist ID %s missing, will be skipped", value); | 
| 748 |             else | 
| 749 |                blacklists.Add(new cBlacklistObject(blacklist)); | 
| 750 |          } | 
| 751 |       } | 
| 752 |       if (*pos) pos++; | 
| 753 |    }  | 
| 754 |   | 
| 755 |    free(line); | 
| 756 |    return true; | 
| 757 | } | 
| 758 |   | 
| 759 | bool cSearchExt::ParseExtEPGValues(const char *s) | 
| 760 | { | 
| 761 |    char *line; | 
| 762 |    char *pos; | 
| 763 |    char *pos_next; | 
| 764 |    int valuelen; | 
| 765 |    char value[MaxFileName255]; | 
| 766 |   | 
| 767 |    pos = line = strdup(s); | 
| 768 |    pos_next = pos + strlen(pos); | 
| 769 |    if (*pos_next == '\n') *pos_next = 0; | 
| 770 |    while (*pos) { | 
| 771 |       while (*pos == ' ') pos++; | 
| 772 |       if (*pos) { | 
| 773 |          if (*pos != '|') { | 
| 774 |             pos_next = strchr(pos, '|'); | 
| 775 |             if (!pos_next) | 
| 776 |                pos_next = pos + strlen(pos); | 
| 777 |             valuelen = pos_next - pos + 1; | 
| 778 |             if (valuelen > MaxFileName255) valuelen = MaxFileName255; | 
| 779 |             strn0cpy(value, pos, valuelen); | 
| 780 |             pos = pos_next; | 
| 781 |             if (!ParseExtEPGEntry(value)) | 
| 782 |             { | 
| 783 |                LogFile.eSysLog("ERROR reading ext. EPG value: %s", value); | 
| 784 |                free(line); | 
| 785 |                return false; | 
| 786 |             } | 
| 787 |          } | 
| 788 |       } | 
| 789 |       if (*pos) pos++; | 
| 790 |    }  | 
| 791 |   | 
| 792 |    free(line); | 
| 793 |    return true; | 
| 794 | } | 
| 795 |   | 
| 796 | bool cSearchExt::ParseExtEPGEntry(const char *s) | 
| 797 | { | 
| 798 |    char *line; | 
| 799 |    char *pos; | 
| 800 |    char *pos_next; | 
| 801 |    int parameter = 1; | 
| 802 |    int valuelen; | 
| 803 |    char value[MaxFileName255]; | 
| 804 |    int currentid = -1; | 
| 805 |   | 
| 806 |    pos = line = strdup(s); | 
| 807 |    pos_next = pos + strlen(pos); | 
| 808 |    if (*pos_next == '\n') *pos_next = 0; | 
| 809 |    while (*pos) { | 
| 810 |       while (*pos == ' ') pos++; | 
| 811 |       if (*pos) { | 
| 812 |          if (*pos != '#') { | 
| 813 |             pos_next = strchr(pos, '#'); | 
| 814 |             if (!pos_next) | 
| 815 |                pos_next = pos + strlen(pos); | 
| 816 |             valuelen = pos_next - pos + 1; | 
| 817 |             if (valuelen > MaxFileName255) valuelen = MaxFileName255; | 
| 818 |             strn0cpy(value, pos, valuelen); | 
| 819 |             pos = pos_next; | 
| 820 |             switch (parameter) { | 
| 821 |                case 1: | 
| 822 |                { | 
| 823 |                   currentid = atoi(value); | 
| 824 |                   int index = SearchExtCats.GetIndexFromID(currentid); | 
| 825 |                   if (index > -1 && index < SearchExtCats.Count()) | 
| 826 |                      strcpy(catvalues[index], ""); | 
| 827 |                } | 
| 828 |                break; | 
| 829 |                case 2: | 
| 830 |                   if (currentid > -1) | 
| 831 |                   { | 
| 832 |                      int index = SearchExtCats.GetIndexFromID(currentid); | 
| 833 |                      if (index > -1 && index < SearchExtCats.Count()) | 
| 834 |                      { | 
| 835 |                         while(strstr(value, "!^colon^!")) | 
| 836 |                            strreplace(value, "!^colon^!", ":"); | 
| 837 |                         while(strstr(value, "!^pipe^!")) | 
| 838 |                            strreplace(value, "!^pipe^!", "|"); | 
| 839 |                         strcpy(catvalues[index], value); | 
| 840 |                      } | 
| 841 |                   } | 
| 842 |                   break; | 
| 843 | 	    default: | 
| 844 | 	      break; | 
| 845 |             }  | 
| 846 |          } | 
| 847 |          parameter++; | 
| 848 |       } | 
| 849 |       if (*pos) pos++; | 
| 850 |    }  | 
| 851 |   | 
| 852 |    free(line); | 
| 853 |    return (parameter >= 2) ? true : false; | 
| 854 | } | 
| 855 |   | 
| 856 | bool cSearchExt::Save(FILE *f) | 
| 857 | { | 
| 858 |    return fprintf(f, "%s\n", ToText()) > 0; | 
| 859 | } | 
| 860 |   | 
| 861 | cEvent * cSearchExt::GetEventBySearchExt(const cSchedule *schedules, const cEvent *Start, bool inspectTimerMargin) | 
| 862 | { | 
| 863 |    if (!schedules) return NULL__null; | 
| 864 |   | 
| 865 |    cEvent *pe = NULL__null; | 
| 866 |    cEvent *p1 = NULL__null; | 
| 867 |   | 
| 868 |    const cList<cEvent>* Events = schedules->Events(); | 
| 869 |    if (Start) | 
| 870 |       p1 = Events->Next(Start); | 
| 871 |    else | 
| 872 |       p1 = Events->First(); | 
| 873 |   | 
| 874 |    time_t tNow=time(NULL__null); | 
| 875 |    char* searchText = strdup(search); | 
| 876 |   | 
| 877 |    int searchStart = 0, searchStop = 0; | 
| 878 |    if (useTime) | 
| 879 |    { | 
| 880 |       searchStart = startTime; | 
| 881 |       searchStop = stopTime; | 
| 882 |       if (searchStop < searchStart) | 
| 883 |          searchStop += 2400; | 
| 884 |    } | 
| 885 |    int minSearchDuration = 0; | 
| 886 |    int maxSearchDuration = 0; | 
| 887 |    if (useDuration) | 
| 888 |    { | 
| 889 |       minSearchDuration = minDuration/100*60 + minDuration%100; | 
| 890 |       maxSearchDuration = maxDuration/100*60 + maxDuration%100; | 
| 891 |    } | 
| 892 |   | 
| 893 |    if (!useCase) | 
| 894 |       ToLower(searchText); | 
| 895 |   | 
| 896 |    for (cEvent *p = p1; p; p = Events->Next(p)) | 
| 897 |    { | 
| 898 |       if(!p) | 
| 899 |       { | 
| 900 |          break; | 
| 901 |       } | 
| 902 |   | 
| 903 |       if (skipRunningEvents && tNow > p->StartTime()) | 
| 904 | 	continue; | 
| 905 |   | 
| 906 |        | 
| 907 |       if (!p->Title() || !*p->Title()) | 
| 908 |          continue; | 
| 909 |   | 
| 910 |       if (tNow < p->EndTime() + (inspectTimerMargin?(MarginStop * 60):0)) | 
| 911 |       { | 
| 912 |          if (useTime) | 
| 913 |          { | 
| 914 |             time_t tEvent = p->StartTime(); | 
| 915 |             struct tm tmEvent; | 
| 916 |             localtime_r(&tEvent, &tmEvent); | 
| 917 |   | 
| 918 |             int eventStart = tmEvent.tm_hour*100 + tmEvent.tm_min; | 
| 919 |             int eventStart2 = eventStart + 2400; | 
| 920 |             if ((eventStart < searchStart || eventStart > searchStop) && | 
| 921 |                 (eventStart2 < searchStart || eventStart2 > searchStop)) | 
| 922 |                continue; | 
| 923 |   | 
| 924 |             if (useDayOfWeek) | 
| 925 |             { | 
| 926 |                if (DayOfWeek >= 0) | 
| 927 |                { | 
| 928 |                   if (( DayOfWeek != tmEvent.tm_wday || (DayOfWeek == tmEvent.tm_wday && eventStart < searchStart)) && | 
| 929 |                       (!((DayOfWeek+1)%7 == tmEvent.tm_wday && eventStart2 < searchStop))) | 
| 930 |                      continue; | 
| 931 |                } | 
| 932 |                else | 
| 933 |                { | 
| 934 |                   int iFound = 0; | 
| 935 |                   for(int i=0; i<7; i++) | 
| 936 |                   { | 
| 937 |                      if ((abs(DayOfWeek) & (int)pow(2,i)) && ((i == tmEvent.tm_wday && eventStart >= searchStart) || | 
| 938 |                                                               ((i+1)%7 == tmEvent.tm_wday && eventStart2 < searchStop))) | 
| 939 |                      { | 
| 940 |                         iFound = 1; | 
| 941 |                         break; | 
| 942 |                      } | 
| 943 |                   } | 
| 944 |                   if (!iFound) | 
| 945 |                      continue; | 
| 946 |                } | 
| 947 |             } | 
| 948 |          } | 
| 949 |          if (useDuration) | 
| 950 |          { | 
| 951 |             int duration = p->Duration()/60; | 
| 952 |             if (minSearchDuration > duration || maxSearchDuration < duration) | 
| 953 |                continue; | 
| 954 |          } | 
| 955 |   | 
| 956 |          if (!useTime && useDayOfWeek) | 
| 957 |          { | 
| 958 |             time_t tEvent = p->StartTime(); | 
| 959 |             struct tm tmEvent; | 
| 960 |             localtime_r(&tEvent, &tmEvent); | 
| 961 |             if (DayOfWeek >= 0 && DayOfWeek != tmEvent.tm_wday) | 
| 962 |                continue; | 
| 963 |             if (DayOfWeek < 0) | 
| 964 |             { | 
| 965 |                int iFound = 0; | 
| 966 |                for(int i=0; i<7; i++) | 
| 967 |                   if (abs(DayOfWeek) & (int)pow(2,i) && i == tmEvent.tm_wday) | 
| 968 |                   { | 
| 969 |                      iFound = 1; | 
| 970 |                      break; | 
| 971 |                   } | 
| 972 |                if (!iFound) | 
| 973 |                   continue; | 
| 974 |             } | 
| 975 |          } | 
| 976 |   | 
| 977 | 	 char* szTest = NULL__null; | 
| 978 | 	 msprintf(&szTest, "%s%s%s%s%s", (useTitle?(p->Title()?p->Title():""):""), (useSubtitle||useDescription)?"~":"", | 
| 979 |                (useSubtitle?(p->ShortText()?p->ShortText():""):""),useDescription?"~":"", | 
| 980 |                (useDescription?(p->Description()?p->Description():""):"")); | 
| 981 |   | 
| 982 |          if (!useCase) | 
| 983 |             ToLower(szTest); | 
| 984 |   | 
| 985 |          if (szTest && *szTest) | 
| 986 |          { | 
| 987 |             if (!MatchesSearchMode(szTest, searchText, mode," ,;|~", fuzzyTolerance)) | 
| 988 | 	    { | 
| 989 | 	       free(szTest); | 
| 990 |                continue; | 
| 991 | 	    } | 
| 992 |          } | 
| 993 | 	 if (szTest) | 
| 994 | 	   free(szTest); | 
| 995 |   | 
| 996 | 	 if (contentsFilter.size() > 0 && !MatchesContentsFilter(p)) | 
| 997 | 	    continue; | 
| 998 |   | 
| 999 |          if (useExtEPGInfo && !MatchesExtEPGInfo(p)) | 
| 1000 |             continue; | 
| 1001 |          pe=p; | 
| 1002 |          break; | 
| 1003 |       } | 
| 1004 |    } | 
| 1005 |    free(searchText); | 
| 1006 |    return pe; | 
| 1007 | } | 
| 1008 |   | 
| 1009 |  | 
| 1010 | cSearchResults* cSearchExt::Run(int PayTVMode, bool inspectTimerMargin, int evalLimitMins, cSearchResults* pPrevResults, bool suppressRepeatCheck) | 
| 1011 | { | 
| 1012 |    LogFile.Log(3,"start search for search timer '%s'", search); | 
| 1013 |   | 
| 1014 |    cSchedulesLock schedulesLock; | 
| 1015 |    const cSchedules *schedules; | 
| 1016 |    schedules = cSchedules::Schedules(schedulesLock); | 
| 1017 |    if(!schedules) { | 
| 1018 |       LogFile.Log(1,"schedules are currently locked! try again later."); | 
| 1019 |       return NULL__null; | 
| 1020 |    } | 
| 1021 |   | 
| 1022 |    bool noPayTV = false; | 
| 1023 |    if (PayTVMode == -1)  | 
| 1024 |       noPayTV = (useChannel == 3); | 
| 1025 |    else | 
| 1026 |       noPayTV = (PayTVMode == 1); | 
| 1027 |   | 
| 1028 |    time_t tNow=time(NULL__null); | 
| 1029 |    const cSchedule *Schedule = schedules->First(); | 
| 1030 |    cSearchResults* pSearchResults = pPrevResults; | 
| 1031 |    cSearchResults* pBlacklistResults = GetBlacklistEvents(inspectTimerMargin?MarginStop:0); | 
| 1032 |   | 
| 1033 |    int counter = 0; | 
| 1034 |    while (Schedule) { | 
| 1035 |       cChannel* channel = Channels.GetByChannelID(Schedule->ChannelID(),true,true); | 
| 1036 |       if (!channel) | 
| 1037 |       { | 
| 1038 |          Schedule = (const cSchedule *)schedules->Next(Schedule); | 
| 1039 |          continue; | 
| 1040 |       } | 
| 1041 |   | 
| 1042 |       if (useChannel == 1 && channelMin && channelMax) | 
| 1043 |       { | 
| 1044 |          if (channelMin->Number() > channel->Number() || channelMax->Number() < channel->Number()) | 
| 1045 |          { | 
| 1046 |             Schedule = (const cSchedule *)schedules->Next(Schedule); | 
| 1047 |             continue; | 
| 1048 |          } | 
| 1049 |       } | 
| 1050 |       if (useChannel == 2 && channelGroup) | 
| 1051 |       { | 
| 1052 |          cChannelGroup* group = ChannelGroups.GetGroupByName(channelGroup); | 
| 1053 |          if (!group || !group->ChannelInGroup(channel)) | 
| 1054 |          { | 
| 1055 |             Schedule = (const cSchedule *)schedules->Next(Schedule); | 
| 1056 |             continue; | 
| 1057 |          } | 
| 1058 |       } | 
| 1059 |   | 
| 1060 |       if (useChannel == 3 && noPayTV) | 
| 1061 |       { | 
| 1062 |          if (channel->Ca() >= CA_ENCRYPTED_MIN0x0100) | 
| 1063 |          { | 
| 1064 |             Schedule = (const cSchedule *)schedules->Next(Schedule); | 
| 1065 |             continue; | 
| 1066 |          } | 
| 1067 |       } | 
| 1068 |   | 
| 1069 |       if (noPayTV)  | 
| 1070 |       { | 
| 1071 |          if (channel->Ca() >= CA_ENCRYPTED_MIN0x0100) | 
| 1072 |          { | 
| 1073 |             Schedule = (const cSchedule *)schedules->Next(Schedule); | 
| 1074 |             continue; | 
| 1075 |          } | 
| 1076 |       } | 
| 1077 |   | 
| 1078 |       const cEvent *pPrevEvent = NULL__null; | 
| 1079 |       do { | 
| 1080 |          const cEvent* event = GetEventBySearchExt(Schedule, pPrevEvent,inspectTimerMargin); | 
| 1081 |          pPrevEvent = event; | 
| 1082 |          if (evalLimitMins && event)  | 
| 1083 |          { | 
| 1084 |             if (tNow + evalLimitMins*60 <= event->EndTime()) | 
| 1085 |                break; | 
| 1086 |          } | 
| 1087 |          if (event && Channels.GetByChannelID(event->ChannelID(),true,true)) | 
| 1088 |          { | 
| 1089 |             if (pBlacklistResults && pBlacklistResults->Lookup(event)) | 
| 1090 |             { | 
| 1091 |                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)*(event->GetDateString()), GETTIMESTRING(event)*(event->GetTimeString()), ChannelNrFromEvent(event)); | 
| 1092 |                continue; | 
| 1093 |             } | 
| 1094 |             if (!pSearchResults) pSearchResults = new cSearchResults; | 
| 1095 |             pSearchResults->Add(new cSearchResult(event, this)); | 
| 1096 |             counter++; | 
| 1097 |          } | 
| 1098 |       } while(pPrevEvent); | 
| 1099 |       Schedule = (const cSchedule *)schedules->Next(Schedule); | 
| 1100 |    } | 
| 1101 |    LogFile.Log(3,"found %d event(s) for search timer '%s'", counter, search); | 
| 1102 |   | 
| 1103 |    if (pBlacklistResults) delete pBlacklistResults; | 
| 1104 |   | 
| 1105 |    if (useAsSearchTimer && avoidRepeats && pSearchResults && !suppressRepeatCheck) | 
| 1106 |    { | 
| 1107 |       pSearchResults->SortBy(CompareEventTime);  | 
| 1108 |       CheckRepeatTimers(pSearchResults); | 
| 1109 |    } | 
| 1110 |   | 
| 1111 |    skipRunningEvents = false; | 
| 1112 |    return pSearchResults; | 
| 1113 | } | 
| 1114 |   | 
| 1115 | cSearchResults* cSearchExt::GetBlacklistEvents(int MarginStop) | 
| 1116 | { | 
| 1117 |    if (blacklistMode == blacklistsNone) return NULL__null; | 
| 1118 |   | 
| 1119 |    cMutexLock BlacklistLock(&Blacklists); | 
| 1120 |    cSearchResults* blacklistEvents = NULL__null; | 
| 1121 |    if (blacklistMode == blacklistsOnlyGlobal) | 
| 1122 |    { | 
| 1123 |       cBlacklist* tmpblacklist = Blacklists.First(); | 
| 1124 |       while(tmpblacklist) | 
| 1125 |       { | 
| 1126 | 	if (tmpblacklist->isGlobal) | 
| 1127 | 	    blacklistEvents = tmpblacklist->Run(blacklistEvents, MarginStop); | 
| 1128 | 	tmpblacklist = Blacklists.Next(tmpblacklist); | 
| 1129 |       } | 
| 1130 |    } | 
| 1131 |    if (blacklistMode == blacklistsAll) | 
| 1132 |    { | 
| 1133 |       cBlacklist* tmpblacklist = Blacklists.First(); | 
| 1134 |       while(tmpblacklist) | 
| 1135 |       { | 
| 1136 |          blacklistEvents = tmpblacklist->Run(blacklistEvents, MarginStop); | 
| 1137 |          tmpblacklist = Blacklists.Next(tmpblacklist); | 
| 1138 |       } | 
| 1139 |    } | 
| 1140 |    if (blacklistMode == blacklistsSelection) | 
| 1141 |    { | 
| 1142 |       cBlacklistObject* tmpblacklistObj = blacklists.First(); | 
| 1143 |       while(tmpblacklistObj) | 
| 1144 |       { | 
| 1145 |          blacklistEvents = tmpblacklistObj->blacklist->Run(blacklistEvents, MarginStop); | 
| 1146 |          tmpblacklistObj = blacklists.Next(tmpblacklistObj); | 
| 1147 |       } | 
| 1148 |    } | 
| 1149 |    return blacklistEvents; | 
| 1150 |   | 
| 1151 | } | 
| 1152 |   | 
| 1153 | void cSearchExt::CheckRepeatTimers(cSearchResults* pResults) | 
| 1154 | { | 
| 1155 |    if (!pResults) | 
| 1156 |       return; | 
| 1157 |    if (avoidRepeats == 0) | 
| 1158 |       return; | 
| 1159 |   | 
| 1160 |    LogFile.Log(2,"analysing repeats for search timer '%s'...", search); | 
| 1161 |    if (action != searchTimerActionRecord) | 
| 1162 |    { | 
| 1163 |       LogFile.Log(3,"search timer not set to 'record', so skip all"); | 
| 1164 |       return; | 
| 1165 |    } | 
| 1166 |   | 
| 1167 |    cSearchResult* pResultObj = NULL__null; | 
| 1168 |    for (pResultObj = pResults->First(); pResultObj; pResultObj = pResults->Next(pResultObj)) | 
| 1169 |    { | 
| 1170 |       if (action != searchTimerActionRecord)  | 
| 1171 |       { | 
| 1172 |          pResultObj->needsTimer = false; | 
| 1173 |          continue; | 
| 1174 |       } | 
| 1175 |   | 
| 1176 |       const cEvent* pEvent = pResultObj->event; | 
| 1177 |        | 
| 1178 |       int records; | 
| 1179 |       cRecDone* firstRec = NULL__null; | 
| 1180 |       LogFile.Log(3,"get count recordings with %d%% match", compareSummaryMatchInPercent); | 
| 1181 |       records = RecsDone.GetCountRecordings(pEvent, this, &firstRec, compareSummaryMatchInPercent); | 
| 1182 |       LogFile.Log(3,"recordings: %d", records); | 
| 1183 |   | 
| 1184 |       if (records > allowedRepeats)  | 
| 1185 |       { | 
| 1186 |          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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent), records); | 
| 1187 |          pResultObj->needsTimer = false;  | 
| 1188 |          continue; | 
| 1189 |       } | 
| 1190 |   | 
| 1191 |       int plannedTimers = 0; | 
| 1192 |       LogFile.Log(3,"get planned recordings"); | 
| 1193 |       cSearchResult* pFirstResultMatching = NULL__null; | 
| 1194 |        | 
| 1195 |       for (cSearchResult* pResultObjP = pResults->First(); pResultObjP; pResultObjP = pResults->Next(pResultObjP)) | 
| 1196 |       { | 
| 1197 |          if (pResultObj == pResultObjP) break; | 
| 1198 |   | 
| 1199 |          const cEvent* pEventP = pResultObjP->event; | 
| 1200 |          if (!pEventP) continue; | 
| 1201 |   | 
| 1202 |          if (!pResultObjP->needsTimer) continue; | 
| 1203 |   | 
| 1204 |          if (EventsMatch(pEvent, pEventP, compareTitle, compareSubtitle, compareSummary, compareDate, catvaluesAvoidRepeat, compareSummaryMatchInPercent)) | 
| 1205 |          { | 
| 1206 |             if (!pFirstResultMatching) pFirstResultMatching = pResultObjP; | 
| 1207 |             plannedTimers++; | 
| 1208 |          } | 
| 1209 |       } | 
| 1210 |       LogFile.Log(3,"planned: %d", plannedTimers); | 
| 1211 |   | 
| 1212 |       if (plannedTimers + records > allowedRepeats) | 
| 1213 |       { | 
| 1214 |          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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent), plannedTimers, records, allowedRepeats); | 
| 1215 |          pResultObj->needsTimer = false; | 
| 1216 |          continue; | 
| 1217 |       } | 
| 1218 |       else if (allowedRepeats > 0 && repeatsWithinDays > 0)  | 
| 1219 |       { | 
| 1220 |          if (firstRec)  | 
| 1221 |          { | 
| 1222 |             if (firstRec->startTime > pEvent->StartTime() - pEvent->Duration())  | 
| 1223 |             { | 
| 1224 |                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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent), DAYDATETIME(firstRec->startTime)*DayDateTime(firstRec->startTime), firstRec->ChannelNr()); | 
| 1225 |                pResultObj->needsTimer = false; | 
| 1226 |                continue; | 
| 1227 |             } | 
| 1228 |             int daysFromFirstRec = int(double((pEvent->StartTime() - firstRec->startTime)) / (60*60*24) + 0.5); | 
| 1229 |             if (daysFromFirstRec  > repeatsWithinDays) | 
| 1230 |             { | 
| 1231 |                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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent), DAYDATETIME(firstRec->startTime)*DayDateTime(firstRec->startTime),daysFromFirstRec, repeatsWithinDays); | 
| 1232 |                pResultObj->needsTimer = false; | 
| 1233 |                continue; | 
| 1234 |             } | 
| 1235 |          } | 
| 1236 |          if (plannedTimers > 0 && pFirstResultMatching) | 
| 1237 |          { | 
| 1238 |             const cEvent* pFirst = pFirstResultMatching->event; | 
| 1239 |             if (pFirst->StartTime() > pEvent->StartTime() - pEvent->Duration())  | 
| 1240 |             { | 
| 1241 |                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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent), GETDATESTRING(pFirst)*(pFirst->GetDateString()), GETTIMESTRING(pFirst)*(pFirst->GetTimeString()), ChannelNrFromEvent(pFirst)); | 
| 1242 |                pResultObj->needsTimer = false; | 
| 1243 |                continue; | 
| 1244 |             } | 
| 1245 |   | 
| 1246 |             int daysBetween = int(double((pEvent->StartTime() - pFirst->StartTime())) / (60*60*24) + 0.5); | 
| 1247 |             if (daysBetween  > repeatsWithinDays) | 
| 1248 |             { | 
| 1249 |                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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent), GETDATESTRING(pFirst)*(pFirst->GetDateString()), GETTIMESTRING(pFirst)*(pFirst->GetTimeString()),daysBetween, repeatsWithinDays); | 
| 1250 |                pResultObj->needsTimer = false; | 
| 1251 |                continue; | 
| 1252 |             } | 
| 1253 |          } | 
| 1254 |       } | 
| 1255 |       bool dummy; | 
| 1256 |       cTimer* timer = cSearchTimerThread::GetTimer(this, pEvent, dummy); | 
| 1257 |       if (timer && !timer->HasFlags(tfActive)) | 
| 1258 |       { | 
| 1259 |          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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent)); | 
| 1260 |          pResultObj->needsTimer = false; | 
| 1261 |          continue; | 
| 1262 |       } | 
| 1263 |       else | 
| 1264 |          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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent)); | 
| 1265 |    } | 
| 1266 |    int needsTimer = 0; | 
| 1267 |    for (pResultObj = pResults->First(); pResultObj; pResultObj = pResults->Next(pResultObj)) | 
| 1268 |       if (pResultObj->needsTimer) needsTimer++; | 
| 1269 |   | 
| 1270 |    LogFile.Log(2,"%d/%d events need a timer for search timer '%s'", needsTimer, pResults->Count(), search); | 
| 1271 | } | 
| 1272 |   | 
| 1273 | void cSearchExt::CheckExistingRecordings(cSearchResults* pResults) | 
| 1274 | { | 
| 1275 |    if (!pResults) | 
| 1276 |       return; | 
| 1277 |   | 
| 1278 |    LogFile.Log(3,"analysing existing recordings for search timer '%s'...", search); | 
| 1279 |   | 
| 1280 |     | 
| 1281 |    int num = GetCountRecordings(); | 
| 1282 |   | 
| 1283 |    cSearchResult* pResultObj = NULL__null; | 
| 1284 |    int remain = pauseOnNrRecordings - num; | 
| 1285 |    for (pResultObj = pResults->First(); pResultObj; pResultObj = pResults->Next(pResultObj), remain--) | 
| 1286 |    { | 
| 1287 |       if (!pResultObj->needsTimer) | 
| 1288 | 	{ | 
| 1289 | 	  remain++; | 
| 1290 | 	  continue;  | 
| 1291 | 	} | 
| 1292 |       pResultObj->needsTimer = (remain > 0); | 
| 1293 |       if (remain <= 0) | 
| 1294 |       { | 
| 1295 |          const cEvent* pEvent = pResultObj->event; | 
| 1296 |          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)*(pEvent->GetDateString()), GETTIMESTRING(pEvent)*(pEvent->GetTimeString()), ChannelNrFromEvent(pEvent), pauseOnNrRecordings); | 
| 1297 |       } | 
| 1298 |    } | 
| 1299 | } | 
| 1300 |   | 
| 1301 | bool cSearchExt::MatchesExtEPGInfo(const cEvent* e) | 
| 1302 | { | 
| 1303 |    if (!e || !e->Description()) | 
| 1304 |       return false; | 
| 1305 |    cSearchExtCat* SearchExtCat = SearchExtCats.First(); | 
| 1306 |    while (SearchExtCat) | 
| 1307 |    { | 
| 1308 |       char* value = NULL__null; | 
| 1309 |       int index = SearchExtCats.GetIndexFromID(SearchExtCat->id); | 
| 1310 |       if (index > -1) | 
| 1311 |          value = catvalues[index]; | 
| 1312 |       if (value && SearchExtCat->searchmode >= 10 && atol(value) == 0)  | 
| 1313 | 	value = NULL__null; | 
| 1314 |       if (value && *value) | 
| 1315 |       { | 
| 1316 |          char* testvalue = GetExtEPGValue(e, SearchExtCat); | 
| 1317 |          if (!testvalue) | 
| 1318 | 	   return (ignoreMissingEPGCats?true:false); | 
| 1319 |   | 
| 1320 |           | 
| 1321 |          char* valueLower = strdup(value); | 
| 1322 |          ToLower(valueLower); | 
| 1323 |          ToLower(testvalue); | 
| 1324 |          if (!MatchesSearchMode(testvalue, valueLower, SearchExtCat->searchmode, ",;|~", fuzzyTolerance)) | 
| 1325 |          { | 
| 1326 |             free(testvalue); | 
| 1327 |             free(valueLower); | 
| 1328 |             return false; | 
| 1329 |          } | 
| 1330 |          free(testvalue); | 
| 1331 |          free(valueLower); | 
| 1332 |       } | 
| 1333 |       SearchExtCat = SearchExtCats.Next(SearchExtCat); | 
| 1334 |    } | 
| 1335 |    return true; | 
| 1336 | } | 
| 1337 |   | 
| 1338 | void cSearchExt::OnOffTimers(bool bOn) | 
| 1339 | { | 
| 1340 |    for (cTimer *ti = Timers.First(); ti; ti = Timers.Next(ti)) | 
| 1341 |    { | 
| 1342 |       if (((!bOn && ti->HasFlags(tfActive)) || (bOn && !ti->HasFlags(tfActive))) && TriggeredFromSearchTimerID(ti) == ID) | 
| 1343 |          ti->OnOff(); | 
| 1344 |    } | 
| 1345 |    Timers.SetModified(); | 
| 1346 | } | 
| 1347 |   | 
| 1348 | void cSearchExt::DeleteAllTimers() | 
| 1349 | { | 
| 1350 |    cList<cTimerObj> DelTimers; | 
| 1351 |    cTimer *ti = Timers.First(); | 
| 1352 |    while(ti) | 
| 1353 |    { | 
| 1354 |       if (!ti->Recording() && TriggeredFromSearchTimerID(ti) == ID) | 
| 1355 |       { | 
| 1356 |          cTimer* tiNext = Timers.Next(ti); | 
| 1357 |          LogFile.iSysLog("deleting timer %s", *ti->ToDescr()); | 
| 1358 |          Timers.Del(ti); | 
| 1359 |          Timers.SetModified(); | 
| 1360 |          ti = tiNext; | 
| 1361 |       } | 
| 1362 |       else | 
| 1363 |          ti = Timers.Next(ti); | 
| 1364 |    }; | 
| 1365 | } | 
| 1366 |   | 
| 1367 | cTimerObjList* cSearchExt::GetTimerList(cTimerObjList* timerList) | 
| 1368 | { | 
| 1369 |    if (!timerList) | 
| 1370 |       timerList = new cTimerObjList; | 
| 1371 |   | 
| 1372 |    for (cTimer *ti = Timers.First(); ti; ti = Timers.Next(ti)) | 
| 1373 |    { | 
| 1374 |       if (TriggeredFromSearchTimerID(ti) == ID) | 
| 1375 |       { | 
| 1376 |           | 
| 1377 |          bool found = false; | 
| 1378 |          for (cTimerObj *tObj = timerList->First(); tObj; tObj = timerList->Next(tObj)) | 
| 1379 |          { | 
| 1380 |             if (tObj->timer == ti) | 
| 1381 |             { | 
| 1382 |                found = true; | 
| 1383 |                break; | 
| 1384 |             } | 
| 1385 |          } | 
| 1386 |          if (!found) | 
| 1387 |             timerList->Add(new cTimerObj(ti)); | 
| 1388 |       } | 
| 1389 |    } | 
| 1390 |    return timerList; | 
| 1391 | } | 
| 1392 |   | 
| 1393 |  | 
| 1394 | int cSearchExt::GetCountRecordings() | 
| 1395 | { | 
| 1396 |    int countRecs = 0; | 
| 1397 |   | 
| 1398 |    for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) | 
| 1399 |    { | 
| 1400 |       if (recording->IsEdited()) continue;  | 
| 1401 |       if (!recording->Info()) continue; | 
| 1402 |       char* searchID = GetAuxValue(recording, "s-id"); | 
| 1403 |   | 
| 1404 |       if (!searchID) continue; | 
| 1405 |       if (ID == atoi(searchID)) | 
| 1406 |          countRecs++; | 
| 1407 |       free(searchID); | 
| 1408 |    } | 
| 1409 |    LogFile.Log(3, "found %d recordings for search '%s'", countRecs, search); | 
| 1410 |    return countRecs; | 
| 1411 | } | 
| 1412 |   | 
| 1413 | bool cSearchExt::IsActiveAt(time_t t) | 
| 1414 | { | 
| 1415 |   if (useAsSearchTimer == 0) return false; | 
| 1416 |   if (useAsSearchTimer == 2) | 
| 1417 |     { | 
| 1418 |       if (useAsSearchTimerFrom > 0 && t < useAsSearchTimerFrom) return false; | 
| 1419 |       if (useAsSearchTimerTil > 0 && t > useAsSearchTimerTil) return false; | 
| 1420 |     } | 
| 1421 |   return true; | 
| 1422 | } | 
| 1423 |   | 
| 1424 | bool cSearchExt::HasContent(int contentID) | 
| 1425 | { | 
| 1426 |   for(unsigned int i=0; i<contentsFilter.size();i+=2) | 
| 1427 |   { | 
| 1428 |     std::string hexContentID = contentsFilter.substr(i,2); | 
| 1429 |     if(hexContentID.size()!=2) return false; | 
| 1430 |     std::istringstream iss(hexContentID); | 
| 1431 |     int tmpContentID =0; | 
| 1432 |     if(!(iss>>std::noshowbase>>std::hex>>tmpContentID)) return false; | 
| 1433 |     if (contentID == tmpContentID) return true; | 
| 1434 |   } | 
| 1435 |   return false; | 
| 1436 | } | 
| 1437 |   | 
| 1438 | void cSearchExt::SetContentFilter(int* contentStringsFlags) | 
| 1439 | { | 
| 1440 |    | 
| 1441 |   string tmp; | 
| 1442 |   contentsFilter = ""; | 
| 1443 |   for(unsigned int i=0; contentStringsFlags && i<=CONTENT_DESCRIPTOR_MAX255; i++) | 
| 1444 |     { | 
| 1445 |       if (contentStringsFlags[i]) | 
| 1446 | 	{ | 
| 1447 | 	  std::ostringstream oss; | 
| 1448 | 	  oss<<std::hex<<std::noshowbase<<i; | 
| 1449 | 	  contentsFilter += oss.str(); | 
| 1450 | 	} | 
| 1451 |     } | 
| 1452 | } | 
| 1453 |   | 
| 1454 | bool cSearchExt::MatchesContentsFilter(const cEvent* e) | 
| 1455 | { | 
| 1456 | #if APIVERSNUM20000 < 10711 | 
| 1457 |   return true; | 
| 1458 | #else | 
| 1459 |   if (!e) return false; | 
| 1460 |    | 
| 1461 |   for(unsigned int i=0; i<contentsFilter.size();i+=2) | 
| 1462 |   { | 
| 1463 |     std::string hexContentID = contentsFilter.substr(i,2); | 
| 1464 |     if(hexContentID.size()!=2) return false; | 
| 1465 |     std::istringstream iss(hexContentID); | 
| 1466 |     int searchContentID =0; | 
| 1467 |     if(!(iss>>std::hex>>searchContentID)) return false; | 
| 1468 |     int c=0, eventContentID=0; | 
| 1469 |     bool found = false; | 
| 1470 |     while((eventContentID=e->Contents(c++)) > 0) | 
| 1471 |       if (eventContentID == searchContentID) | 
| 1472 | 	{ | 
| 1473 | 	  found = true; | 
| 1474 | 	  break; | 
| 1475 | 	} | 
| 1476 |     if (!found) return false; | 
| 1477 |   } | 
| 1478 |   return true; | 
| 1479 | #endif | 
| 1480 | } | 
| 1481 |   | 
| 1482 |  | 
| 1483 | bool cSearchExts::Load(const char *FileName) | 
| 1484 | { | 
| 1485 |    cMutexLock SearchExtsLock(this); | 
| 1486 |    Clear(); | 
| 1487 |    if (FileName) { | 
| 1488 |       free(fileName); | 
| 1489 |       fileName = strdup(FileName); | 
| 1490 |    } | 
| 1491 |   | 
| 1492 |    bool result = true; | 
| 1493 |    if (fileName && access(fileName, F_OK0) == 0) { | 
| 1494 |       LogFile.iSysLog("loading %s", fileName); | 
| 1495 |       FILE *f = fopen(fileName, "r"); | 
| 1496 |       if (f) { | 
| 1497 |          int line = 0; | 
| 1498 |          char buffer[MAXPARSEBUFFER((10) * 1024)]; | 
| 1499 |          result = true; | 
| 1500 |          while (fgets(buffer, sizeof(buffer), f) > 0) { | 
| 1501 |             line++; | 
| 1502 |             char *p = strchr(buffer, '#'); | 
| 1503 |             if (p == buffer) *p = 0; | 
| 1504 |   | 
| 1505 |             stripspace(buffer); | 
| 1506 |             if (!isempty(buffer)) { | 
| 1507 |                cSearchExt* search = new cSearchExt; | 
| 1508 |                if (search->Parse(buffer)) | 
| 1509 |                   Add(search); | 
| 1510 |                else { | 
| 1511 |                   LogFile.eSysLog("error in '%s', line %d\n", fileName, line); | 
| 1512 |                   delete search; | 
| 1513 |                   result = false; | 
| 1514 |                   break; | 
| 1515 |                } | 
| 1516 |             } | 
| 1517 |          } | 
| 1518 |          fclose(f); | 
| 1519 |       } | 
| 1520 |       else { | 
| 1521 |          LOG_ERROR_STR(fileName)void( (SysLogLevel > 0) ? syslog_with_tid(3, "ERROR (%s,%d): %s: %m" , "epgsearchext.c", 1521, fileName) : void() ); | 
| 1522 |          result = false; | 
| 1523 |       } | 
| 1524 |    } | 
| 1525 |   | 
| 1526 |    if (!result) | 
| 1527 |       fprintf(stderrstderr, "vdr: error while reading '%s'\n", fileName); | 
| 1528 |    LogFile.Log(2,"loaded searches from %s (count: %d)", fileName, Count()); | 
| 1529 |    return result; | 
| 1530 | } | 
| 1531 |   | 
| 1532 | int cSearchExts::GetNewID() | 
| 1533 | { | 
| 1534 |    cMutexLock SearchExtsLock(this); | 
| 1535 |    int newID = -1; | 
| 1536 |    cSearchExt *l = (cSearchExt *)First(); | 
| 1537 |    while (l) { | 
| 1538 |       newID = max(newID, l->ID); | 
| 1539 |       l = (cSearchExt *)l->Next(); | 
| 1540 |    } | 
| 1541 |    return newID+1; | 
| 1542 | } | 
| 1543 |   | 
| 1544 | void cSearchExts::Update(void) | 
| 1545 | { | 
| 1546 |    cMutexLock SearchExtsLock(this); | 
| 1547 |    cSearchExt *l = (cSearchExt *)First(); | 
| 1548 |    while (l) { | 
| 1549 |        | 
| 1550 |       if (l->ID == -1) | 
| 1551 |          l->ID = GetNewID(); | 
| 1552 |       l = (cSearchExt *)l->Next(); | 
| 1553 |    } | 
| 1554 | } | 
| 1555 |   | 
| 1556 | bool cSearchExts::Save(void) | 
| 1557 | { | 
| 1558 |    cMutexLock SearchExtsLock(this); | 
| 1559 |    bool result = true; | 
| 1560 |    cSearchExt *l = (cSearchExt *)this->First(); | 
| 1561 |    cSafeFile f(fileName); | 
| 1562 |    if (f.Open()) { | 
| 1563 |       while (l) { | 
| 1564 |          if (!l->Save(f)) { | 
| 1565 |             result = false; | 
| 1566 |             break; | 
| 1567 |          } | 
| 1568 |          l = (cSearchExt *)l->Next(); | 
| 1569 |       } | 
| 1570 |       if (!f.Close()) | 
| 1571 |          result = false; | 
| 1572 |    } | 
| 1573 |    else | 
| 1574 |       result = false; | 
| 1575 |    return result; | 
| 1576 | } | 
| 1577 |   | 
| 1578 | cSearchExt* cSearchExts::GetSearchFromID(int ID) | 
| 1579 | { | 
| 1580 |    if (ID == -1) | 
| 1581 |       return NULL__null; | 
| 1582 |    cMutexLock SearchExtsLock(this); | 
| 1583 |    cSearchExt *l = (cSearchExt *)First(); | 
| 1584 |    while (l) { | 
| 1585 |       if (l->ID == ID) | 
| 1586 |          return l; | 
| 1587 |       l = (cSearchExt *)l->Next(); | 
| 1588 |    } | 
| 1589 |    return NULL__null; | 
| 1590 | } | 
| 1591 |   | 
| 1592 | void cSearchExts::RemoveBlacklistID(int ID) | 
| 1593 | { | 
| 1594 |    bool changed = false; | 
| 1595 |    cMutexLock SearchExtsLock(this); | 
| 1596 |    cSearchExt *l = (cSearchExt *)First(); | 
| 1597 |    while (l) | 
| 1598 |    { | 
| 1599 |       cBlacklistObject* blacklistObj = l->blacklists.First(); | 
| 1600 |       while(blacklistObj) | 
| 1601 |       { | 
| 1602 |          cBlacklistObject* blacklistObjNext = l->blacklists.Next(blacklistObj); | 
| 1603 |          if (blacklistObj->blacklist->ID == ID) | 
| 1604 |          { | 
| 1605 |             l->blacklists.Del(blacklistObj); | 
| 1606 |             changed = true; | 
| 1607 |          } | 
| 1608 |          blacklistObj = blacklistObjNext; | 
| 1609 |       } | 
| 1610 |       l = (cSearchExt *)l->Next(); | 
| 1611 |    } | 
| 1612 |    if (changed) | 
| 1613 |       Save(); | 
| 1614 | } | 
| 1615 |   | 
| 1616 | bool cSearchExts::Exists(const cSearchExt* SearchExt) | 
| 1617 | { | 
| 1618 |    cMutexLock SearchExtsLock(this); | 
| 1619 |    cSearchExt *l = (cSearchExt *)First(); | 
| 1620 |    while (l) | 
| 1621 |    { | 
| 1622 |       if (l == SearchExt) | 
| 1623 |          return true; | 
| 1624 |       l = (cSearchExt *)l->Next(); | 
| 1625 |    } | 
| 1626 |    return false; | 
| 1627 | } | 
| 1628 |   | 
| 1629 | cSearchExts* cSearchExts::Clone() | 
| 1630 | { | 
| 1631 |    cSearchExts* clonedList = new cSearchExts(); | 
| 1632 |   | 
| 1633 |    cMutexLock SearchExtsLock(this); | 
| 1634 |    cSearchExt *l = (cSearchExt *)First(); | 
| 1635 |    while (l) | 
| 1636 |    { | 
| 1637 |       cSearchExt* clone = new cSearchExt(); | 
| 1638 |       *clone = *l; | 
| 1639 |       clonedList->Add(clone); | 
| 1640 |       l = (cSearchExt *)l->Next(); | 
| 1641 |    } | 
| 1642 |    return clonedList; | 
| 1643 | } | 
| 1644 |   | 
| 1645 | bool cSearchExts::CheckForAutoDelete(cSearchExt* SearchExt) | 
| 1646 | { | 
| 1647 |    if (!SearchExt || SearchExt->delMode == 0) return false; | 
| 1648 |   | 
| 1649 |    cRecDone* firstRec = NULL__null; | 
| 1650 |    bool delSearch = false; | 
| 1651 |    int recs = RecsDone.GetTotalCountRecordings(SearchExt, &firstRec); | 
| 1652 |    if (SearchExt->delMode == 1 && SearchExt->delAfterCountRecs > 0) | 
| 1653 |       delSearch = recs >= SearchExt->delAfterCountRecs; | 
| 1654 |    if (SearchExt->delMode == 2 && SearchExt->delAfterDaysOfFirstRec && firstRec) | 
| 1655 |       delSearch = (time(NULL__null) - firstRec->startTime) > SearchExt->delAfterDaysOfFirstRec * 24 * 60 * 60; | 
| 1656 |    if (delSearch) | 
| 1657 |    { | 
| 1658 |       int DelID = SearchExt->ID; | 
| 1659 |       LogFile.Log(1,"auto deleting search '%s' (ID: %d)", SearchExt->search, DelID); | 
| 1660 |       cMutexLock SearchExtsLock(&SearchExts); | 
| 1661 |       SearchExts.Del(SearchExt); | 
| 1662 |       SearchExts.Save(); | 
| 1663 |       RecsDone.RemoveSearchID(DelID); | 
| 1664 |    } | 
| 1665 |    return delSearch; | 
| 1666 | } | 
| 1667 |   | 
| 1668 | void cSearchExts::SortBy(int(*compar)(const void *, const void *)) | 
| 1669 | { | 
| 1670 |   int n = Count(); | 
| 1671 |   cListObject *a[n]; | 
| 1672 |   cListObject *object = objects; | 
| 1673 |   int i = 0; | 
| 1674 |   while (object && i < n) { | 
| 1675 |     a[i++] = object; | 
| 1676 |     object = object->Next(); | 
| 1677 |   } | 
| 1678 |   qsort(a, n, sizeof(cListObject *), compar); | 
| 1679 |   objects = lastObject = NULL__null; | 
| 1680 |   for (i = 0; i < n; i++) { | 
| 1681 |     a[i]->Unlink(); | 
| 1682 |     count--; | 
| 1683 |     Add(a[i]); | 
| 1684 |   } | 
| 1685 | } | 
| 1686 |   | 
| 1687 | cSearchResult::cSearchResult(const cEvent* Event, int searchID) : event(Event), blacklist(NULL__null), needsTimer(true) | 
| 1688 | { | 
| 1689 |   search = SearchExts.GetSearchFromID(searchID); | 
| 1690 | } |