summaryrefslogtreecommitdiff
path: root/recmanager.c
diff options
context:
space:
mode:
authorlouis <louis.braun@gmx.de>2013-07-09 00:17:42 +0200
committerlouis <louis.braun@gmx.de>2013-07-09 00:17:42 +0200
commit2a7a011055a44516ec981e525776394a8c04dcfe (patch)
tree55c9828c7b619622ec36da3f4b41318ed6c85ae0 /recmanager.c
parent6da4b610d98cafe7c20555c926359d7f89347c76 (diff)
downloadvdr-plugin-tvguide-2a7a011055a44516ec981e525776394a8c04dcfe.tar.gz
vdr-plugin-tvguide-2a7a011055a44516ec981e525776394a8c04dcfe.tar.bz2
Version 0.0.6
Diffstat (limited to 'recmanager.c')
-rw-r--r--recmanager.c598
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