diff options
Diffstat (limited to 'recmanager.c')
-rw-r--r-- | recmanager.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/recmanager.c b/recmanager.c new file mode 100644 index 0000000..df65cc3 --- /dev/null +++ b/recmanager.c @@ -0,0 +1,598 @@ +#include <string> +#include <vector> +#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; i<numConflicts; i++) { + if (timerIDs[i] == involvedID) + return true; + } + return false; +} + +cRecManager::cRecManager(void) { + epgSearchPlugin = NULL; + epgSearchAvailable = false; +} + +cRecManager::~cRecManager(void) { +} + +void cRecManager::SetEPGSearchPlugin(void) { + epgSearchPlugin = cPluginManager::GetPlugin("epgsearch"); + if (epgSearchPlugin) { + epgSearchAvailable = true; + } +} + +cTimer *cRecManager::createTimer(const cEvent *event) { + cTimer *timer = new cTimer(event); + Timers.Add(timer); + Timers.SetModified(); + isyslog("timer %s added (active)", *timer->ToDescr()); + 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<TVGuideTimerConflict> 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<TVGuideTimerConflict> 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<std::string> conflicts = epgSearch->handler->TimerConflictList(); + int numConflicts = conflicts.size(); + if (numConflicts > 0) { + for (std::list<std::string>::iterator it=conflicts.begin(); it != conflicts.end(); ++it) { + TVGuideTimerConflict sConflict; + splitstring s(it->c_str()); + std::vector<std::string> flds = s.split(':'); + if (flds.size() < 2) + continue; + sConflict.time = atoi(flds[0].c_str()); + splitstring s2(flds[1].c_str()); + std::vector<std::string> 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<std::string> flds3 = s3.split('#'); + std::vector<int> 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<TVGuideEPGSearchTemplate> cRecManager::ReadEPGSearchTemplates(void) { + cString ConfigDir = cPlugin::ConfigDirectory("epgsearch"); + cString epgsearchConf = "epgsearchtemplates.conf"; + cString fileName = AddDirectory(*ConfigDir, *epgsearchConf); + std::vector<TVGuideEPGSearchTemplate> 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<std::string> 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<std::string>::iterator it=results.begin(); it != results.end(); ++it) { + try { + splitstring s(it->c_str()); + std::vector<std::string> 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<Epgsearch_searchresults_v1_0::cServiceSearchResult> *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; +}
\ No newline at end of file |