#include #include #include "recmanager.h" static int CompareRecording(const void *p1, const void *p2) { return (int)((*(cRecording **)p1)->Start() - (*(cRecording **)p2)->Start()); } bool TVGuideTimerConflict::timerInvolved(int involvedID) { int numConflicts = timerIDs.size(); for (int i=0; iToDescr()); return timer; } void cRecManager::DeleteTimer(const cEvent *event) { cTimer *t = Timers.GetMatch(event); if (!t) return; DeleteTimer(t); } void cRecManager::DeleteTimer(int timerID) { cTimer *t = Timers.Get(timerID); if (!t) return; DeleteTimer(t); } void cRecManager::DeleteTimer(cTimer *timer) { if (timer->Recording()) { timer->Skip(); cRecordControls::Process(time(NULL)); } isyslog("timer %s deleted", *timer->ToDescr()); Timers.Del(timer, true); Timers.SetModified(); } void cRecManager::SaveTimer(cTimer *timer, cRecMenu *menu) { if (!timer) return; bool active = menu->GetBoolValue(1); int prio = menu->GetIntValue(2); int lifetime = menu->GetIntValue(3); time_t day = menu->GetTimeValue(4); int start = menu->GetIntValue(5); int stop = menu->GetIntValue(6); timer->SetDay(day); timer->SetStart(start); timer->SetStop(stop); timer->SetPriority(prio); timer->SetLifetime(lifetime); if (timer->HasFlags(tfActive) && !active) timer->ClrFlags(tfActive); else if (!timer->HasFlags(tfActive) && active) timer->SetFlags(tfActive); timer->SetEventFromSchedule(); Timers.SetModified(); } bool cRecManager::IsRecorded(const cEvent *event) { cTimer *timer = Timers.GetMatch(event); if (!timer) return false; return timer->Recording(); } std::vector cRecManager::CheckTimerConflict(void) { /* TIMERCONFLICT FORMAT: The result list looks like this for example when we have 2 timer conflicts at one time: 1190232780:152|30|50#152#45:45|10|50#152#45 '1190232780' is the time of the conflict in seconds since 1970-01-01. It's followed by list of timers that have a conflict at this time: '152|30|50#1 int editTimer(cTimer *timer, bool active, int prio, int start, int stop); 52#45' is the description of the first conflicting timer. Here: '152' is VDR's timer id of this timer as returned from VDR's LSTT command '30' is the percentage of recording that would be done (0...100) '50#152#45' is the list of concurrent timers at this conflict '45|10|50#152#45' describes the next conflict */ std::vector results; if (!epgSearchAvailable) return results; Epgsearch_services_v1_1 *epgSearch = new Epgsearch_services_v1_1; if (epgSearchPlugin->Service("Epgsearch-services-v1.1", epgSearch)) { std::list conflicts = epgSearch->handler->TimerConflictList(); int numConflicts = conflicts.size(); if (numConflicts > 0) { for (std::list::iterator it=conflicts.begin(); it != conflicts.end(); ++it) { TVGuideTimerConflict sConflict; splitstring s(it->c_str()); std::vector flds = s.split(':'); if (flds.size() < 2) continue; sConflict.time = atoi(flds[0].c_str()); splitstring s2(flds[1].c_str()); std::vector flds2 = s2.split('|'); if (flds2.size() < 3) continue; sConflict.timerID = atoi(flds2[0].c_str()); sConflict.percentPossible = atoi(flds2[1].c_str()); splitstring s3(flds2[2].c_str()); std::vector flds3 = s3.split('#'); std::vector timerIDs; for (int k = 0; k < flds3.size(); k++) { timerIDs.push_back(atoi(flds3[k].c_str()) - 1); } sConflict.timerIDs = timerIDs; results.push_back(sConflict); } } } delete epgSearch; int numConflicts = results.size(); time_t startTime = 0; time_t endTime = 0; for (int i=0; i < numConflicts; i++) { cTimeInterval *unionSet = NULL; int numTimers = results[i].timerIDs.size(); for (int j=0; j < numTimers; j++) { const cTimer *timer = Timers.Get(results[i].timerIDs[j]); if (timer) { if (!unionSet) { unionSet = new cTimeInterval(timer->StartTime(), timer->StopTime()); } else { cTimeInterval *timerInterval = new cTimeInterval(timer->StartTime(), timer->StopTime()); cTimeInterval *newUnion = unionSet->Union(timerInterval); delete unionSet; delete timerInterval; unionSet = newUnion; } } } results[i].timeStart = unionSet->Start(); results[i].timeStop = unionSet->Stop(); delete unionSet; cTimeInterval *intersect = NULL; for (int j=0; j < numTimers; j++) { const cTimer *timer = Timers.Get(results[i].timerIDs[j]); if (timer) { if (!intersect) { intersect = new cTimeInterval(timer->StartTime(), timer->StopTime()); } else { cTimeInterval *timerInterval = new cTimeInterval(timer->StartTime(), timer->StopTime()); cTimeInterval *newIntersect = intersect->Intersect(timerInterval); if (newIntersect) { delete intersect; intersect = newIntersect; } delete timerInterval; } } } results[i].overlapStart = intersect->Start(); results[i].overlapStop = intersect->Stop(); delete intersect; } return results; } cTimer *cRecManager::CreateSeriesTimer(cRecMenu *menu) { bool active = menu->GetBoolValue(1); int channelNumber = menu->GetIntValue(2); int start = menu->GetIntValue(3); int stop = menu->GetIntValue(4); int weekdays = menu->GetIntValue(5); time_t tday = menu->GetTimeValue(6); int prio = menu->GetIntValue(7); int lifetime = menu->GetIntValue(8); cChannel *channel = Channels.GetByNumber(channelNumber); cTimer *seriesTimer = new cTimer(false, false, channel); seriesTimer->SetDay(tday); seriesTimer->SetStart(start); seriesTimer->SetStop(stop); seriesTimer->SetPriority(prio); seriesTimer->SetLifetime(lifetime); seriesTimer->SetWeekDays(weekdays); seriesTimer->SetFile("TITLE EPISODE"); if (active) seriesTimer->SetFlags(tfActive); else seriesTimer->SetFlags(tfNone); seriesTimer->SetEventFromSchedule(); Timers.Add(seriesTimer); Timers.SetModified(); return seriesTimer; } std::vector cRecManager::ReadEPGSearchTemplates(void) { cString ConfigDir = cPlugin::ConfigDirectory("epgsearch"); cString epgsearchConf = "epgsearchtemplates.conf"; cString fileName = AddDirectory(*ConfigDir, *epgsearchConf); std::vector epgTemplates; if (access(fileName, F_OK) == 0) { FILE *f = fopen(fileName, "r"); if (f) { char *s; cReadLine ReadLine; while ((s = ReadLine.Read(f)) != NULL) { char *p = strchr(s, '#'); if (p) *p = 0; stripspace(s); try { if (!isempty(s)) { std::string templ = s; int posID = templ.find_first_of(":"); int posName = templ.find_first_of(":", posID+1); std::string name = templ.substr(posID+1, posName - posID - 1); std::string templValue = templ.substr(posName); TVGuideEPGSearchTemplate tmp; tmp.name = name; tmp.templValue = templValue; epgTemplates.push_back(tmp); } } catch (...){} } } } return epgTemplates; } std::string cRecManager::BuildEPGSearchString(cString searchString, std::string templValue) { std::stringstream searchTimerString; searchTimerString << "0:"; searchTimerString << *searchString; searchTimerString << templValue; return searchTimerString.str(); } std::string cRecManager::BuildEPGSearchString(cString searchString, cRecMenu *menu) { int searchMode = menu->GetIntValue(0); bool useTitle = menu->GetBoolValue(1); bool useSubTitle = menu->GetBoolValue(2); bool useDescription = menu->GetBoolValue(3); bool limitChannels = menu->GetBoolValue(4); int startChannel = -1; int stopChannel = -1; if (limitChannels) { startChannel = menu->GetIntValue(5); stopChannel = menu->GetIntValue(6); } int after = 0; int before = 0; bool limitTime = (limitChannels)?menu->GetBoolValue(7):menu->GetBoolValue(5); if (limitTime) { after = (limitChannels)?menu->GetIntValue(8):menu->GetIntValue(6); before = (limitChannels)?menu->GetIntValue(9):menu->GetIntValue(7); } std::stringstream searchTimerString; //1 - unique search timer id searchTimerString << "0:"; //2 - the search term searchTimerString << *searchString; //3 - use time? 0/1 //4 - start time in HHMM //5 - stop time in HHMM if (limitTime) { searchTimerString << ":1:" << after << ":" << before << ":"; } else { searchTimerString << ":0:::"; } //6 - use channel? 0 = no, 1 = Interval, 2 = Channel group, 3 = FTA only //7 - if 'use channel' = 1 then channel id[|channel id] in VDR format, // one entry or min/max entry separated with |, if 'use channel' = 2 // then the channel group name if (limitChannels) { searchTimerString << "1:"; cChannel *startChan = Channels.GetByNumber(startChannel); cChannel *stopChan = Channels.GetByNumber(stopChannel); searchTimerString << *(startChan->GetChannelID().ToString()); searchTimerString << "|"; searchTimerString << *(stopChan->GetChannelID().ToString()) << ":"; } else { searchTimerString << "0::"; } //8 - match case? 0/1 searchTimerString << ":0"; /*9 - search mode: 0 - the whole term must appear as substring 1 - all single terms (delimiters are blank,',', ';', '|' or '~') must exist as substrings. 2 - at least one term (delimiters are blank, ',', ';', '|' or '~') must exist as substring. 3 - matches exactly 4 - regular expression */ searchTimerString << searchMode << ":"; //10 - use title? 0/1 if (useTitle) searchTimerString << "1:"; else searchTimerString << "0:"; //11 - use subtitle? 0/1 if (useSubTitle) searchTimerString << "1:"; else searchTimerString << "0:"; // 12 - use description? 0/1 if (useDescription) searchTimerString << "1:"; else searchTimerString << "0:"; //13 - use duration? 0/1 //14 - min duration in hhmm //15 - max duration in hhmm searchTimerString << "0:::"; //16 - use as search timer? 0/1 searchTimerString << "1:"; //17 - use day of week? 0/1 //18 - day of week (0 = Sunday, 1 = Monday...; // -1 Sunday, -2 Monday, -4 Tuesday, ...; -7 Sun, Mon, Tue) searchTimerString << "0::"; //19 - use series recording? 0/1 searchTimerString << "1:"; //20 - directory for recording searchTimerString << ":"; //21 - priority of recording //22 - lifetime of recording searchTimerString << "99:99:"; //23 - time margin for start in minutes //24 - time margin for stop in minutes searchTimerString << "5:5:"; //25 - use VPS? 0/1 searchTimerString << "0:"; /*26 - action: 0 = create a timer 1 = announce only via OSD (no timer) 2 = switch only (no timer) 3 = announce via OSD and switch (no timer) 4 = announce via mail*/ searchTimerString << "0:"; /*27 - use extended EPG info? 0/1 28 - extended EPG info values. This entry has the following format (delimiter is '|' for each category, '#' separates id and value): 1 - the id of the extended EPG info category as specified in epgsearchcats.conf 2 - the value of the extended EPG info category (a ':' will be translated to "!^colon^!", e.g. in "16:9") */ searchTimerString << "0::"; /*29 - avoid repeats? 0/1 30 - allowed repeats 31 - compare title when testing for a repeat? 0/1 32 - compare subtitle when testing for a repeat? 0/1/2 0 - no 1 - yes 2 - yes, if present 33 - compare description when testing for a repeat? 0/1 34 - compare extended EPG info when testing for a repeat? This entry is a bit field of the category IDs. 35 - accepts repeats only within x days */ searchTimerString << "1:1:1:2:1:::"; /*36 - delete a recording automatically after x days 37 - but keep this number of recordings anyway 38 - minutes before switch (if action = 2) 39 - pause if x recordings already exist 40 - blacklist usage mode (0 none, 1 selection, 2 all) 41 - selected blacklist IDs separated with '|' 42 - fuzzy tolerance value for fuzzy searching 43 - use this search in favorites menu (0 no, 1 yes) 44 - id of a menu search template 45 - auto deletion mode (0 don't delete search timer, 1 delete after given count of recordings, 2 delete after given days after first recording) 46 - count of recordings after which to delete the search timer 47 - count of days after the first recording after which to delete the search timer 48 - first day where the search timer is active (see parameter 16) 49 - last day where the search timer is active (see parameter 16) 50 - ignore missing EPG categories? 0/1 51 - unmute sound if off when used as switch timer 52 - percentage of match when comparing the summary of two events (with 'avoid repeats') 53 - HEX representation of the content descriptors, each descriptor ID is represented with 2 chars 54 - compare date when testing for a repeat? (0=no, 1=same day, 2=same week, 3=same month) */ searchTimerString << "0::::0:::0::0:::::::::0"; //esyslog("tvguide: epgsearch String: %s", searchTimerString.str().c_str()); return searchTimerString.str(); } const cEvent **cRecManager::PerformSearchTimerSearch(std::string epgSearchString, int &numResults) { if (!epgSearchAvailable) return NULL; const cEvent **searchResults = NULL; Epgsearch_services_v1_1 *epgSearch = new Epgsearch_services_v1_1; if (epgSearchPlugin->Service("Epgsearch-services-v1.1", epgSearch)) { std::list results = epgSearch->handler->QuerySearch(epgSearchString); numResults = results.size(); if (numResults > 0) { searchResults = new const cEvent *[numResults]; cSchedulesLock *schedulesLock; const cSchedules *schedules; schedules = cSchedules::Schedules(*schedulesLock); const cEvent *event = NULL; int index=0; for (std::list::iterator it=results.begin(); it != results.end(); ++it) { try { splitstring s(it->c_str()); std::vector flds = s.split(':', 1); int eventID = atoi(flds[1].c_str()); std::string channelID = flds[7]; tChannelID chanID = tChannelID::FromString(channelID.c_str()); cChannel *channel = Channels.GetByChannelID(chanID); if (channel) { const cSchedule *Schedule = NULL; Schedule = schedules->GetSchedule(channel); event = Schedule->GetEvent(eventID); if (event) { searchResults[index] = event; } else return NULL; } else return NULL; index++; } catch (...){} } } } return searchResults; } const cEvent **cRecManager::PerformSearch(cRecMenu *menu, bool withOptions, int &numResults) { if (epgSearchAvailable) { cString searchString = menu->GetStringValue(1); Epgsearch_searchresults_v1_0 data; data.query = (char *)*searchString; int mode = 0; int channelNr = 0; bool useTitle = true; bool useSubTitle = true; bool useDescription = false; if (withOptions) { mode = menu->GetIntValue(2); channelNr = menu->GetIntValue(3); useTitle = menu->GetBoolValue(4); useSubTitle = menu->GetBoolValue(5); useDescription = menu->GetBoolValue(6); } data.mode = mode; data.channelNr = channelNr; data.useTitle = useTitle; data.useSubTitle = useSubTitle; data.useDescription = useDescription; if (epgSearchPlugin->Service("Epgsearch-searchresults-v1.0", &data)) { cList *list = data.pResultList; int numElements = list->Count(); const cEvent **searchResults = NULL; if (numElements > 0) { searchResults = new const cEvent *[numElements]; numResults = numElements; int index = 0; for (Epgsearch_searchresults_v1_0::cServiceSearchResult *r = list->First(); r ; r = list->Next(r)) { searchResults[index] = r->event; index++; } } delete list; return searchResults; } } return NULL; } int cRecManager::CreateSearchTimer(std::string epgSearchString) { int timerID = -1; if (!epgSearchAvailable) return timerID; Epgsearch_services_v1_1 *epgSearch = new Epgsearch_services_v1_1; if (epgSearchPlugin->Service("Epgsearch-services-v1.1", epgSearch)) { timerID = epgSearch->handler->AddSearchTimer(epgSearchString); } return timerID; } void cRecManager::UpdateSearchTimers(void) { if (epgSearchAvailable) { Epgsearch_updatesearchtimers_v1_0 data; data.showMessage = false; epgSearchPlugin->Service("Epgsearch-updatesearchtimers-v1.0", &data); } } // announceOnly: 0 = switch, 1 = announce only, 2 = ask for switch bool cRecManager::CreateSwitchTimer(const cEvent *event, cRecMenu *menu) { int switchMinsBefore = menu->GetIntValue(1); int announceOnly = menu->GetIntValue(2); if (epgSearchAvailable) { Epgsearch_switchtimer_v1_0 data; data.event = event; data.mode = 1; data.switchMinsBefore = switchMinsBefore; data.announceOnly = announceOnly; data.success = false; epgSearchPlugin->Service("Epgsearch-switchtimer-v1.0", &data); cSwitchTimer *t = new cSwitchTimer(event); SwitchTimers.Add(t); return data.success; } return false; } void cRecManager::DeleteSwitchTimer(const cEvent *event) { SwitchTimers.DeleteSwitchTimer(event); if (epgSearchAvailable) { Epgsearch_switchtimer_v1_0 data; data.event = event; data.mode = 2; data.switchMinsBefore = 0; data.announceOnly = 0; data.success = false; epgSearchPlugin->Service("Epgsearch-switchtimer-v1.0", &data); } } cRecording **cRecManager::SearchForRecordings(cString searchString, int &numResults) { cRecording **matchingRecordings = NULL; int num = 0; numResults = 0; for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { std::string s1 = recording->Name(); std::string s2 = *searchString; if (s1.empty() || s2.empty()) continue; // tolerance for fuzzy searching: 90% of the shorter text length, but at least 1 int tolerance = std::max(1, (int)std::min(s1.size(), s2.size()) / 10); bool match = FindIgnoreCase(s1, s2) >= 0 || FindIgnoreCase(s2, s1) >= 0; if (!match) { AFUZZY af = { NULL, NULL, NULL, NULL, NULL, NULL, { 0 }, { 0 }, 0, 0, 0, 0, 0, 0 }; if (s1.size() > 32) s1 = s1.substr(0, 32); afuzzy_init(s1.c_str(), tolerance, 0, &af); /* Checking substring */ int res = afuzzy_checkSUB(s2.c_str(), &af); afuzzy_free(&af); match = (res > 0); } if (!match) { AFUZZY af = { NULL, NULL, NULL, NULL, NULL, NULL, { 0 }, { 0 }, 0, 0, 0, 0, 0, 0 }; if (s2.size() > 32) s2 = s2.substr(0, 32); afuzzy_init(s2.c_str(), tolerance, 0, &af); /* Checking substring */ int res = afuzzy_checkSUB(s1.c_str(), &af); afuzzy_free(&af); match = (res > 0); } if (match) { matchingRecordings = (cRecording **)realloc(matchingRecordings, (num + 1) * sizeof(cRecording *)); matchingRecordings[num++] = recording; } } if (num > 0) { qsort(matchingRecordings, num, sizeof(cRecording *), CompareRecording); numResults = num; return matchingRecordings; } return NULL; }