Bug Summary

File:epgsearchext.c
Location:line 449, column 48
Description:Potential leak of memory pointed to by 'pos'

Annotated Source Code

1/* -*- c++ -*-
2Copyright (C) 2004-2013 Christian Wieninger
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program; if not, write to the Free Software
16Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
18
19The author can be reached at cwieninger@gmx.de
20
21The project's page is at http://winni.vdr-developer.org/epgsearch
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
42cSearchExts SearchExts;
43cSearchExts SearchTemplates;
44
45#ifndef MAX_SUBTITLE_LENGTH40
46 #define MAX_SUBTITLE_LENGTH40 40
47#endif
48
49// -- cSearchExt -----------------------------------------------------------------
50char *cSearchExt::buffer = NULL__null;
51
52cSearchExt::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; // no blacklists
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
124cSearchExt::~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
146cSearchExt& 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
249bool cSearchExt::operator< (const cListObject &ListObject)
250{
251 cSearchExt *SE = (cSearchExt *)&ListObject;
252 return strcasecmp(search, SE->search) < 0;
253}
254
255char* replaceSpecialChars(const char* in)
256{
257 char* tmp_in = strdup(in);
258 while(strstr(tmp_in, "|"))
259 tmp_in = strreplace(tmp_in, "|", "!^pipe^!"); // ugly: replace a pipe with something,
260 strreplace(tmp_in, ':', '|');
261 return tmp_in;
262}
263
264const 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^!"); // ugly: replace with something, that should not happen to be part ofa category value
319 while(strstr(catvalue, "|"))
320 catvalue = strreplace(catvalue, "|", "!^pipe^!"); // ugly: replace with something, that should not happen to be part of a regular expression
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
420bool 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);
1
Memory is allocated
434 pos_next = pos + strlen(pos);
435 if (*pos_next == '\n') *pos_next = 0;
2
Taking false branch
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) {
5
Taking true branch
439 if (*pos != ':') {
6
Taking true branch
440 pos_next = strchr(pos, ':');
441 if (!pos_next)
7
Assuming 'pos_next' is non-null
8
Taking false branch
442 pos_next = pos + strlen(pos);
443 valuelen = pos_next - pos + 1;
444 if (valuelen > MaxFileName255) valuelen = MaxFileName255;
9
Assuming 'valuelen' is <= 255
10
Taking false branch
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;
12
Taking true branch
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) // stored with ID
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 } //switch
627 }
628 parameter++;
629 }
630 if (*pos) pos++;
631 } //while
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
653char* 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 // ignore existing title and subtitle in file if already used as variables in directory
694 msprintf(&pFile, "%s", directory);
695
696 // parse the epxression and evaluate it
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// replace some special chars
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
720bool 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 } //while
754
755 free(line);
756 return true;
757}
758
759bool 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 } //while
791
792 free(line);
793 return true;
794}
795
796bool 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 } //switch
846 }
847 parameter++;
848 }
849 if (*pos) pos++;
850 } //while
851
852 free(line);
853 return (parameter >= 2) ? true : false;
854}
855
856bool cSearchExt::Save(FILE *f)
857{
858 return fprintf(f, "%s\n", ToText()) > 0;
859}
860
861cEvent * 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 // ignore events without title
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// returns a pointer array to the matching search results
1010cSearchResults* 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) // use search's setting
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) // no paytv
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) // limit evaluation to now + limit
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); // sort before checking repeats to make sure the first event is selected
1108 CheckRepeatTimers(pSearchResults);
1109 }
1110
1111 skipRunningEvents = false;
1112 return pSearchResults;
1113}
1114
1115cSearchResults* 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
1153void 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) // only announce if there is no timer for the event
1171 {
1172 pResultObj->needsTimer = false;
1173 continue;
1174 }
1175
1176 const cEvent* pEvent = pResultObj->event;
1177 // check if this event was already recorded
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) // already recorded
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; // first assume we need no timer
1188 continue;
1189 }
1190
1191 int plannedTimers = 0;
1192 LogFile.Log(3,"get planned recordings");
1193 cSearchResult* pFirstResultMatching = NULL__null;
1194 // check other results, if they are already planned for equal events
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) // if we only allow repeats with in a given range
1219 {
1220 if (firstRec) // already recorded, check for allowed repeat within days
1221 {
1222 if (firstRec->startTime > pEvent->StartTime() - pEvent->Duration()) // no repeat
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()) // no repeat
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
1273void 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 // how many recordings do we already have?
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; // maybe already disabled because of done feature
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
1301bool 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) // numerical 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 // compare not case sensitive
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
1338void 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
1348void 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
1367cTimerObjList* 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 // check if already in list
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// counts the currently existent recordings triggered by this search timer
1394int 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; // ignore recordings edited
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
1413bool 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
1424bool 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
1438void cSearchExt::SetContentFilter(int* contentStringsFlags)
1439{
1440 // create the hex array of content descriptor IDs
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
1454bool cSearchExt::MatchesContentsFilter(const cEvent* e)
1455{
1456#if APIVERSNUM20000 < 10711
1457 return true;
1458#else
1459 if (!e) return false;
1460 // check if each content filter ID is contained in the events descriptors
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// -- cSearchExts ----------------------------------------------------------------
1483bool 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
1532int 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
1544void cSearchExts::Update(void)
1545{
1546 cMutexLock SearchExtsLock(this);
1547 cSearchExt *l = (cSearchExt *)First();
1548 while (l) {
1549 // check if ID is set
1550 if (l->ID == -1)
1551 l->ID = GetNewID();
1552 l = (cSearchExt *)l->Next();
1553 }
1554}
1555
1556bool 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
1578cSearchExt* 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
1592void 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
1616bool 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
1629cSearchExts* 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
1645bool 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
1668void 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
1687cSearchResult::cSearchResult(const cEvent* Event, int searchID) : event(Event), blacklist(NULL__null), needsTimer(true)
1688{
1689 search = SearchExts.GetSearchFromID(searchID);
1690}