17 #include <vdr/plugin.h>
18 #include <vdr/videodir.h>
19 #include <vdr/recording.h>
21 #include <vdr/timers.h>
22 #include <vdr/channels.h>
24 /* Locking information from VDR:
25 + If a plugin needs to access several of the global lists in parallel, locking must
26 always be done in the sequence Timers, Channels, Recordings, Schedules.
29 VDRClient::VDRClient()
31 logger = spd::get("jsonserver_spdlog");
34 VDRClient::~VDRClient()
36 logger->debug("VDRClient destructor");
39 bool VDRClient::process(std::string& request, PFMap& postFields, std::string& returnString)
41 Json::Value returnJSON;
46 if (request == "gettime") success = gettime(postFields, returnJSON);
47 else if (request == "diskstats") success = diskstats(postFields, returnJSON);
48 else if (request == "channellist") success = channellist(postFields, returnJSON);
49 else if (request == "reclist") success = reclist(postFields, returnJSON);
50 else if (request == "timerlist") success = timerlist(postFields, returnJSON);
51 else if (request == "epgdownload") success = epgdownload(postFields, returnJSON);
52 else if (request == "tunersstatus") success = tunersstatus(postFields, returnJSON);
53 else if (request == "epgfilterget") success = epgfilterget(postFields, returnJSON);
55 else if (request == "channelschedule") success = channelschedule(postFields, returnJSON);
56 else if (request == "getscheduleevent") success = getscheduleevent(postFields, returnJSON);
57 else if (request == "epgsearch") success = epgsearch(postFields, returnJSON);
58 else if (request == "epgsearchsame") success = epgsearchsame(postFields, returnJSON);
59 else if (request == "epgsearchotherhalf")success = epgsearchotherhalf(postFields, returnJSON);
60 else if (request == "timerset") success = timerset(postFields, returnJSON);
61 else if (request == "recinfo") success = recinfo(postFields, returnJSON);
62 else if (request == "recstop") success = recstop(postFields, returnJSON);
63 else if (request == "recdel") success = recdel(postFields, returnJSON);
64 else if (request == "recrename") success = recrename(postFields, returnJSON);
65 else if (request == "recmove") success = recmove(postFields, returnJSON);
66 else if (request == "timersetactive") success = timersetactive(postFields, returnJSON);
67 else if (request == "timerdel") success = timerdel(postFields, returnJSON);
68 else if (request == "timerisrecording") success = timerisrecording(postFields, returnJSON);
69 else if (request == "recresetresume") success = recresetresume(postFields, returnJSON);
70 else if (request == "timeredit") success = timeredit(postFields, returnJSON);
71 else if (request == "epgfilteradd") success = epgfilteradd(postFields, returnJSON);
72 else if (request == "epgfilterdel") success = epgfilterdel(postFields, returnJSON);
74 catch (const BadParamException& e)
76 logger->error("Bad parameter in call, paramName: {}", e.param);
77 returnJSON["Result"] = false;
78 returnJSON["Error"] = "Bad request parameter";
79 returnJSON["Detail"] = e.param;
83 if (!success) return false;
85 Json::StyledWriter sw;
86 returnString = sw.write(returnJSON);
87 logger->debug("Done sw write");
91 bool VDRClient::gettime(PFMap& postFields, Json::Value& js)
93 logger->debug("get_time");
96 gettimeofday(&tv, NULL);
98 js["Time"] = (Json::UInt64)tv.tv_sec;
99 js["MTime"] = (Json::UInt)(tv.tv_usec/1000);
104 bool VDRClient::diskstats(PFMap& postFields, Json::Value& js)
106 logger->debug("diskstats");
110 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
112 js["FreeMiB"] = FreeMB;
113 js["UsedMiB"] = UsedMB;
114 js["Percent"] = Percent;
119 bool VDRClient::channellist(PFMap& postFields, Json::Value& js)
121 logger->debug("channellist");
123 Json::Value jschannels(Json::arrayValue);
127 for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel))
129 if (!channel->GroupSep())
131 Json::Value oneChannel;
132 oneChannel["ID"] = (const char *)channel->GetChannelID().ToString();
133 oneChannel["Number"] = channel->Number();
134 oneChannel["Name"] = channel->Name();
135 jschannels.append(oneChannel);
138 js["Channels"] = jschannels;
143 bool VDRClient::reclist(PFMap& postFields, Json::Value& js)
145 logger->debug("reclist");
147 Json::Value jsrecordings(Json::arrayValue);
149 LOCK_RECORDINGS_READ;
151 for (const cRecording *recording = Recordings->First(); recording; recording = Recordings->Next(recording))
154 oneRec["StartTime"] = (Json::UInt)recording->Start();
155 oneRec["Length"] = (Json::UInt)recording->LengthInSeconds();
156 oneRec["IsNew"] = recording->IsNew();
157 oneRec["Name"] = recording->Name();
158 oneRec["Filename"] = recording->FileName();
159 oneRec["FileSizeMB"] = recording->FileSizeMB();
161 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
162 if (rc) oneRec["CurrentlyRecording"] = true;
163 else oneRec["CurrentlyRecording"] = false;
165 jsrecordings.append(oneRec);
167 js["Recordings"] = jsrecordings;
172 bool VDRClient::timerlist(PFMap& postFields, Json::Value& js)
174 logger->debug("timerlist");
176 Json::Value jstimers(Json::arrayValue);
184 int numTimers = Timers->Count();
186 for (int i = 0; i < numTimers; i++)
188 timer = Timers->Get(i);
189 Json::Value oneTimer;
190 oneTimer["Active"] = timer->HasFlags(tfActive);
191 oneTimer["Recording"] = timer->Recording();
192 oneTimer["Pending"] = timer->Pending();
193 oneTimer["Priority"] = timer->Priority();
194 oneTimer["Lifetime"] = timer->Lifetime();
195 oneTimer["ChannelNumber"] = timer->Channel()->Number();
196 oneTimer["ChannelID"] = (const char *)timer->Channel()->GetChannelID().ToString();
197 oneTimer["StartTime"] = (int)timer->StartTime();
198 oneTimer["StopTime"] = (int)timer->StopTime();
199 oneTimer["Day"] = (int)timer->Day();
200 oneTimer["WeekDays"] = timer->WeekDays();
201 oneTimer["Name"] = timer->File();
203 const cEvent* event = timer->Event();
206 oneTimer["EventID"] = event->EventID();
210 int channelNumber = timer->Channel()->Number();
211 int aroundTime = timer->StartTime() + 1;
212 const cEvent* eventAround = getEvent(Channels, Schedules, js, channelNumber, 0, aroundTime);
215 oneTimer["EventID"] = eventAround->EventID();
219 oneTimer["EventID"] = 0;
223 jstimers.append(oneTimer);
226 js["Timers"] = jstimers;
227 js["NumTuners"] = cDevice::NumDevices();
232 bool VDRClient::channelschedule(PFMap& postFields, Json::Value& js) // RETHROWS
234 logger->debug("channelschedule");
235 int channelNumber = getVarInt(postFields, "channelnumber");
236 int startTime = getVarInt(postFields, "starttime");
237 int duration = getVarInt(postFields, "duration");
239 Json::Value jsevents(Json::arrayValue);
243 const cChannel* channel = NULL;
244 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
246 if (channel->GroupSep()) continue;
247 if (channel->Number() == channelNumber) break;
252 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
253 js["Result"] = false;
254 js["Error"] = "Could not find channel";
260 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
263 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
264 js["Result"] = false;
265 js["Error"] = "Internal schedules error (2)";
269 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
271 if ((event->StartTime() + event->Duration()) < time(NULL)) continue; //in the past filter
272 if ((event->StartTime() + event->Duration()) <= startTime) continue; //start time filter
273 if (event->StartTime() >= (startTime + duration)) continue; //duration filter
275 Json::Value oneEvent;
276 oneEvent["ID"] = event->EventID();
277 oneEvent["Time"] = (Json::UInt)event->StartTime();
278 oneEvent["Duration"] = event->Duration();
279 oneEvent["Title"] = event->Title() ? event->Title() : "";
280 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
281 oneEvent["HasTimer"] = event->HasTimer();
282 jsevents.append(oneEvent);
286 js["Events"] = jsevents;
290 bool VDRClient::getscheduleevent(PFMap& postFields, Json::Value& js) // RETHROWS
292 logger->debug("getscheduleevent");
294 int channelNumber = getVarInt(postFields, "channelnumber");
295 int eventID = getVarInt(postFields, "eventid");
300 const cEvent* event = getEvent(Channels, Schedules, js, channelNumber, eventID, 0);
303 js["Result"] = false;
307 Json::Value oneEvent;
308 oneEvent["ID"] = event->EventID();
309 oneEvent["Time"] = (Json::UInt)event->StartTime();
310 oneEvent["Duration"] = event->Duration();
311 oneEvent["Title"] = event->Title() ? event->Title() : "";
312 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
313 oneEvent["Description"] = event->Description() ? event->Description() : "";
314 oneEvent["HasTimer"] = event->HasTimer();
315 oneEvent["RunningStatus"] = event->RunningStatus();
318 js["Event"] = oneEvent;
322 bool VDRClient::epgdownload(PFMap& postFields, Json::Value& js)
324 logger->debug("epgdownload");
325 Json::Value jsevents(Json::arrayValue);
330 for (const cChannel* channel = Channels->First(); channel; channel = Channels->Next(channel))
332 if (channel->GroupSep()) continue;
334 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
335 if (!Schedule) continue;
337 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
339 Json::Value oneEvent;
340 oneEvent["ChannelNumber"] = channel->Number();
341 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
342 oneEvent["ID"] = event->EventID();
343 oneEvent["Time"] = (Json::UInt)event->StartTime();
344 oneEvent["Duration"] = event->Duration();
345 oneEvent["Title"] = event->Title() ? event->Title() : "";
346 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
347 oneEvent["HasTimer"] = event->HasTimer();
348 oneEvent["Description"] = event->Description() ? event->Description() : "";
349 jsevents.append(oneEvent);
354 js["Events"] = jsevents;
358 bool VDRClient::epgsearch(PFMap& postFields, Json::Value& js) // RETHROWS
360 logger->debug("epgsearch");
362 std::string searchfor = getVarString(postFields, "searchfor");
363 logger->debug("epgsearch: search for: {}", searchfor);
365 Json::Value jsevents(Json::arrayValue);
370 for (const cChannel* channel = Channels->First(); channel; channel = Channels->Next(channel))
372 if (channel->GroupSep()) continue;
374 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
375 if (!Schedule) continue;
378 bool founddescription;
379 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
382 founddescription = false;
384 if (event->Title() && strcasestr(event->Title(), searchfor.c_str())) foundtitle = true;
386 if (!foundtitle && event->Description())
387 if (strcasestr(event->Description(), searchfor.c_str())) founddescription = true;
389 if (foundtitle || founddescription)
391 Json::Value oneEvent;
392 oneEvent["ChannelNumber"] = channel->Number();
393 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
394 oneEvent["ID"] = event->EventID();
395 oneEvent["Time"] = (Json::UInt)event->StartTime();
396 oneEvent["Duration"] = event->Duration();
397 oneEvent["Title"] = event->Title() ? event->Title() : "";
398 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
399 oneEvent["HasTimer"] = event->HasTimer();
400 oneEvent["Description"] = event->Description() ? event->Description() : "";
401 if (founddescription)
402 oneEvent["FoundInDesc"] = true;
404 oneEvent["FoundInDesc"] = false;
405 jsevents.append(oneEvent);
411 js["Events"] = jsevents;
413 logger->debug("epgsearch: search for: {} done", searchfor);
418 bool VDRClient::epgsearchsame(PFMap& postFields, Json::Value& js) // RETHROWS
420 logger->debug("epgsearchsame");
422 int atTime = getVarInt(postFields, "time");
423 std::string sTitle = getVarString(postFields, "title");
425 logger->debug("epgsearchsame: request time: {}, title: {}", atTime, sTitle);
427 Json::Value jsevents(Json::arrayValue);
432 for (const cSchedule *schedule = Schedules->First(); (schedule != NULL); schedule = Schedules->Next(schedule))
434 event = schedule->GetEventAround(atTime);
435 if (!event) continue; // nothing found on this schedule(channel)
437 if (!strcmp(event->Title(), sTitle.c_str()))
439 Json::Value oneEvent;
440 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
441 oneEvent["ID"] = event->EventID();
442 oneEvent["Time"] = (Json::UInt)event->StartTime();
443 oneEvent["Duration"] = event->Duration();
444 oneEvent["Title"] = event->Title() ? event->Title() : "";
445 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
446 oneEvent["HasTimer"] = event->HasTimer();
447 //oneEvent["Description"] = event->Description() ? event->Description() : "";
448 jsevents.append(oneEvent);
452 js["Events"] = jsevents;
457 bool VDRClient::epgsearchotherhalf(PFMap& postFields, Json::Value& js) // RETHROWS
459 logger->debug("epgsearchotherhalf");
460 int channelNumber = getVarInt(postFields, "channelnumber");
461 int eventID = getVarInt(postFields, "eventid");
462 const cChannel* channel = NULL;
466 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
468 if (channel->GroupSep()) continue;
469 if (channel->Number() == channelNumber) break;
474 logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
475 js["Result"] = false;
476 js["Error"] = "Could not find channel";
482 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
485 logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
486 js["Result"] = false;
487 js["Error"] = "Internal schedules error (2)";
491 js["OtherEventFound"] = false;
493 const cEvent* eventM1 = NULL;
494 const cEvent* eventM2 = NULL;
495 const cEvent* otherEvent = NULL;
497 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
499 if (event->EventID() == (unsigned long)eventID)
501 if ((eventM2 != NULL) && (!strcmp(eventM2->Title(), event->Title())))
503 otherEvent = eventM2;
507 const cEvent* eventP1 = Schedule->Events()->Next(event);
510 const cEvent* eventP2 = Schedule->Events()->Next(eventP1);
512 if (eventP2 && (!strcmp(eventP2->Title(), event->Title())))
514 otherEvent = eventP2;
521 Json::Value oneEvent;
522 oneEvent["ID"] = otherEvent->EventID();
523 oneEvent["ChannelNumber"] = channel->Number();
524 oneEvent["Time"] = (Json::UInt)otherEvent->StartTime();
525 oneEvent["Duration"] = otherEvent->Duration();
526 oneEvent["Title"] = otherEvent->Title() ? otherEvent->Title() : "";
527 oneEvent["HasTimer"] = otherEvent->HasTimer();
528 js["Event"] = oneEvent;
529 js["OtherEventFound"] = true;
543 bool VDRClient::tunersstatus(PFMap& postFields, Json::Value& js)
545 logger->debug("tunerstatus");
547 js["NumDevices"] = cDevice::NumDevices();
549 Json::Value jsdevices(Json::arrayValue);
551 for (int i = 0; i < cDevice::NumDevices(); i++)
553 Json::Value oneDevice;
554 cDevice *d = cDevice::GetDevice(i);
555 oneDevice["Number"] = d->DeviceNumber();
556 oneDevice["Type"] = (const char*)d->DeviceType();
557 oneDevice["Name"] = (const char*)d->DeviceName();
558 oneDevice["IsPrimary"] = d->IsPrimaryDevice();
560 const cChannel* cchannel = d->GetCurrentlyTunedTransponder();
563 oneDevice["Frequency"] = cchannel->Frequency();
564 oneDevice["SignalStrength"] = d->SignalStrength();
565 oneDevice["SignalQuality"] = d->SignalQuality();
570 oneDevice["Frequency"] = 0;
571 oneDevice["SignalStrength"] = 0;
572 oneDevice["SignalQuality"] = 0;
575 jsdevices.append(oneDevice);
578 js["Devices"] = jsdevices;
581 Json::Value jstimers(Json::arrayValue);
584 LOCK_CHANNELS_READ; // Because this calls timer->Channel() .. necessary?
586 int numTimers = Timers->Count();
588 for (int i = 0; i < numTimers; i++)
590 timer = Timers->Get(i);
592 if (timer->Recording())
594 Json::Value oneTimer;
595 oneTimer["Recording"] = timer->Recording();
596 oneTimer["StartTime"] = (int)timer->StartTime();
597 oneTimer["StopTime"] = (int)timer->StopTime();
598 oneTimer["File"] = timer->File();
600 cRecordControl* crc = cRecordControls::GetRecordControl(timer);
603 cDevice* crcd = crc->Device();
604 oneTimer["DeviceNumber"] = crcd->DeviceNumber();
608 oneTimer["DeviceNumber"] = Json::Value::null;
611 const cChannel* channel = timer->Channel();
614 oneTimer["ChannelName"] = channel->Name();
618 oneTimer["ChannelName"] = Json::Value::null;
621 jstimers.append(oneTimer);
625 js["CurrentRecordings"] = jstimers;
632 bool VDRClient::timerset(PFMap& postFields, Json::Value& js) // RETHROWS
634 logger->debug("timerset");
636 std::string sTimerString = getVarString(postFields, "timerstring");
638 logger->debug("timerset: '{}'", sTimerString);
639 cTimer *timer = new cTimer;
640 if (!timer->Parse(sTimerString.c_str()))
643 js["Result"] = false;
644 js["Error"] = "Failed to parse timer request details";
649 Timers->SetExplicitModify();
651 cTimer *t = Timers->GetTimer(timer);
655 js["Result"] = false;
656 js["Error"] = "Timer already exists";
661 Timers->SetModified();
667 bool VDRClient::recinfo(PFMap& postFields, Json::Value& js) // RETHROWS
669 logger->debug("recinfo");
671 std::string reqfilename = getVarString(postFields, "filename");
672 logger->debug("recinfo: {}", reqfilename);
674 LOCK_RECORDINGS_READ;
676 const cRecording *recording = Recordings->GetByName(reqfilename.c_str());
680 logger->error("recinfo: recinfo found no recording");
681 js["Result"] = false;
685 js["IsNew"] = recording->IsNew();
686 js["LengthInSeconds"] = recording->LengthInSeconds();
687 js["FileSizeMB"] = recording->FileSizeMB();
688 js["Name"] = recording->Name() ? recording->Name() : Json::Value::null;
689 js["Priority"] = recording->Priority();
690 js["LifeTime"] = recording->Lifetime();
691 js["Start"] = (Json::UInt)recording->Start();
693 js["CurrentlyRecordingStart"] = 0;
694 js["CurrentlyRecordingStop"] = 0;
695 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
698 js["CurrentlyRecordingStart"] = (Json::UInt)rc->Timer()->StartTime();
699 js["CurrentlyRecordingStop"] = (Json::UInt)rc->Timer()->StopTime();
702 js["ResumePoint"] = 0;
704 const cRecordingInfo *info = recording->Info();
707 js["ChannelName"] = info->ChannelName() ? info->ChannelName() : Json::Value::null;
708 js["Title"] = info->Title() ? info->Title() : Json::Value::null;
709 js["ShortText"] = info->ShortText() ? info->ShortText() : Json::Value::null;
710 js["Description"] = info->Description() ? info->Description() : Json::Value::null;
712 const cComponents* components = info->Components();
715 js["Components"] = Json::Value::null;
719 Json::Value jscomponents;
721 tComponent* component;
722 for (int i = 0; i < components->NumComponents(); i++)
724 component = components->Component(i);
726 Json::Value oneComponent;
727 oneComponent["Stream"] = component->stream;
728 oneComponent["Type"] = component->type;
729 oneComponent["Language"] = component->language ? component->language : Json::Value::null;
730 oneComponent["Description"] = component->description ? component->description : Json::Value::null;
731 jscomponents.append(oneComponent);
734 js["Components"] = jscomponents;
737 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
738 if (ResumeFile.Read() >= 0) js["ResumePoint"] = floor(ResumeFile.Read() / info->FramesPerSecond());
745 bool VDRClient::recstop(PFMap& postFields, Json::Value& js) // RETHROWS
747 logger->debug("recstop");
749 std::string reqfilename = getVarString(postFields, "filename");
750 logger->debug("recstop: {}", reqfilename);
753 LOCK_RECORDINGS_WRITE; // May not need write here, but to be safe..
755 cRecording *recording = Recordings->GetByName(reqfilename.c_str());
759 logger->error("recstop: recstop found no recording");
760 js["Result"] = false;
764 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
767 logger->error("recstop: not currently recording");
768 js["Result"] = false;
772 cTimer* timer = rc->Timer();
775 logger->error("recstop: timer not found");
776 js["Result"] = false;
780 timer->ClrFlags(tfActive);
787 bool VDRClient::recdel(PFMap& postFields, Json::Value& js) // RETHROWS
789 logger->debug("recdel");
791 std::string reqfilename = getVarString(postFields, "filename");
792 logger->debug("recdel: {}", reqfilename);
794 LOCK_RECORDINGS_WRITE;
796 cRecording *recording = Recordings->GetByName(reqfilename.c_str());
800 js["Result"] = false;
801 js["Error"] = "Could not find recording to delete";
805 logger->debug("recdel: Deleting recording: {}", recording->Name());
806 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
809 js["Result"] = false;
810 js["Error"] = "This recording is still recording.. ho ho";
814 if (recording->Delete())
816 Recordings->DelByName(recording->FileName());
821 js["Result"] = false;
822 js["Error"] = "Failed to delete recording";
828 void VDRClient::pathsForRecordingName(const cRecordings* Recordings, const std::string& recordingName,
829 std::string& dirNameSingleDate,
830 std::string& dirNameSingleTitle,
831 std::string& dirNameSingleFolder,
832 std::string& dirNameFullPathTitle,
833 std::string& dirNameFullPathDate) // throws int
837 const cRecording* recordingObj = Recordings->GetByName(recordingName.c_str());
838 if (!recordingObj) throw 2;
839 t = recordingObj->FileName();
841 cRecordControl *rc = cRecordControls::GetRecordControl(recordingObj->FileName());
844 logger->debug("paths: recording: {}", recordingObj->Name());
845 } // unlock recordings
847 logger->debug("paths: recording: {}", t);
849 dirNameFullPathDate = t;
852 // Find the datedirname
853 for(k = strlen(t) - 1; k >= 0; k--)
857 logger->debug("recmoverename: l1: {}", strlen(&t[k+1]) + 1);
858 dirNameSingleDate.assign(&t[k+1], strlen(t) - k - 1);
859 logger->debug("paths: dirNameSingleDate: '{}'", dirNameSingleDate);
864 // Find the titledirname
866 for(j = k-1; j >= 0; j--)
870 logger->debug("recmoverename: l2: {}", k - j);
871 dirNameSingleTitle.assign(&t[j+1], k - j - 1);
872 logger->debug("paths: dirNameSingleTitle: '{}'", dirNameSingleTitle);
877 // Find the foldername
879 const char* vidDirStr = cVideoDirectory::Name();
880 int vidDirStrLen = strlen(vidDirStr);
882 logger->debug("recmoverename: j = {}, strlenvd = {}", j, vidDirStrLen);
883 if (j > vidDirStrLen) // Rec is in a subfolder now
885 for(m = j-1; m >= 0; m--)
889 logger->debug("recmoverename: l3: {}", j - m);
890 dirNameSingleFolder.assign(&t[m+1], j - m - 1);
891 logger->debug("paths: dirNameSingleFolder: '{}'", dirNameSingleFolder);
897 dirNameFullPathTitle.assign(t, k);
898 logger->debug("paths: dirNameFullPathTitle: '{}'", dirNameFullPathTitle);
901 bool VDRClient::recrename(PFMap& postFields, Json::Value& js) // RETHROWS
903 logger->debug("recrename");
905 std::string fileNameToAffect = getVarString(postFields, "filename");
906 std::string requestedNewStr = getVarString(postFields, "newname");
908 std::string dirNameSingleDate;
909 std::string dirNameSingleTitle;
910 std::string dirNameSingleFolder;
911 std::string dirNameFullPathTitle;
912 std::string dirNameFullPathDate;
914 #warning Switch this to Recording->ChangeName ?
918 LOCK_RECORDINGS_WRITE;
920 pathsForRecordingName(Recordings,
921 fileNameToAffect, dirNameSingleDate,
922 dirNameSingleTitle, dirNameSingleFolder,
923 dirNameFullPathTitle, dirNameFullPathDate);
925 char* requestedNewSingleTitle = (char*)malloc(requestedNewStr.size() + 1);
926 strcpy(requestedNewSingleTitle, requestedNewStr.c_str());
927 logger->debug("recmoverename: to: {}", requestedNewSingleTitle);
929 requestedNewSingleTitle = ExchangeChars(requestedNewSingleTitle, true);
930 if (!strlen(requestedNewSingleTitle)) throw 9;
931 logger->debug("recmoverename: EC: {}", requestedNewSingleTitle);
933 const char* vidDirStr = cVideoDirectory::Name();
934 logger->debug("recmoverename: viddir: {}", vidDirStr);
936 // Could be a new path - construct that first and test
938 std::string newDirNameFullPathTitle = vidDirStr;
939 newDirNameFullPathTitle.append("/");
941 if (!dirNameSingleFolder.empty())
943 newDirNameFullPathTitle.append(dirNameSingleFolder);
944 newDirNameFullPathTitle.append("/");
947 newDirNameFullPathTitle.append(requestedNewSingleTitle);
948 free(requestedNewSingleTitle);
950 logger->debug("recrename: NPT2: {}", newDirNameFullPathTitle);
953 int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
954 if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
956 logger->debug("recrename: new path does not exist (1)");
957 int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
958 if (mkdirret != 0) throw 4;
960 else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
962 // Something exists but it's not a dir
966 // New path now created or was there already
968 std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
969 newDirNameFullPathDate.append(dirNameSingleDate);
971 logger->debug("recrename: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
972 if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
974 // Success. Test for remove old dir containter
975 rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
977 Recordings->Update();
979 js["NewRecordingFileName"] = newDirNameFullPathDate;
983 js["Result"] = false;
986 logger->error("recrename: Bad parameters");
987 js["Error"] = "Bad request parameters";
991 logger->error("recrename: Could not find recording to move");
992 js["Error"] = "Bad filename";
996 logger->error("recrename: Could not move recording, it is still recording");
997 js["Error"] = "Cannot move recording in progress";
1001 logger->error("recrename: Failed to make new dir (1)");
1002 js["Error"] = "Failed to create new directory (1)";
1006 logger->error("recrename: Something already exists? (1)");
1007 js["Error"] = "Something already exists at the new path (1)";
1011 logger->error("recrename: Rename failed");
1012 js["Error"] = "Rename failed";
1016 logger->error("recrename: ExchangeChars lost our string");
1017 js["Error"] = "Rename failed";
1024 bool VDRClient::recmove(PFMap& postFields, Json::Value& js) // RETHROWS
1026 logger->debug("recmove");
1028 std::string fileNameToAffect = getVarString(postFields, "filename");
1029 std::string requestedNewStr = getVarString(postFields, "newpath");
1031 std::string dirNameSingleDate;
1032 std::string dirNameSingleTitle;
1033 std::string dirNameSingleFolder;
1034 std::string dirNameFullPathTitle;
1035 std::string dirNameFullPathDate;
1039 LOCK_RECORDINGS_WRITE;
1041 pathsForRecordingName(Recordings,
1042 fileNameToAffect, dirNameSingleDate,
1043 dirNameSingleTitle, dirNameSingleFolder,
1044 dirNameFullPathTitle, dirNameFullPathDate);
1046 char* requestedNewSinglePath = (char*)malloc(requestedNewStr.size() + 1);
1047 strcpy(requestedNewSinglePath, requestedNewStr.c_str());
1048 logger->debug("recmoverename: to: {}", requestedNewSinglePath);
1050 requestedNewSinglePath = ExchangeChars(requestedNewSinglePath, true);
1051 if (!strlen(requestedNewSinglePath)) throw 9;
1052 logger->debug("recmoverename: EC: {}", requestedNewSinglePath);
1054 const char* vidDirStr = cVideoDirectory::Name();
1055 logger->debug("recmoverename: viddir: {}", vidDirStr);
1057 // Could be a new path - construct that first and test
1059 std::string newDirNameFullPathTitle = vidDirStr;
1060 newDirNameFullPathTitle.append(requestedNewSinglePath);
1061 free(requestedNewSinglePath);
1063 logger->debug("recmove: NPT: {}", newDirNameFullPathTitle);
1066 int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
1067 if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
1069 logger->debug("recmove: new path does not exist (1)");
1070 int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
1071 if (mkdirret != 0) throw 4;
1073 else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
1075 // Something exists but it's not a dir
1079 // New path now created or was there already
1081 newDirNameFullPathTitle.append(dirNameSingleTitle);
1082 logger->debug("recmove: {}", newDirNameFullPathTitle);
1084 statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
1085 if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
1087 logger->debug("recmove: new dir does not exist (2)");
1088 int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
1089 if (mkdirret != 0) throw 6;
1091 else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
1093 // Something exists but it's not a dir
1097 // Ok, the directory container has been made, or it pre-existed.
1099 std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
1100 newDirNameFullPathDate.append(dirNameSingleDate);
1102 logger->debug("recmove: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
1103 if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
1105 // Success. Test for remove old dir containter
1106 rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
1108 // Test for remove old foldername
1109 if (!dirNameSingleFolder.empty())
1111 std::string dirNameFullPathFolder = vidDirStr;
1112 dirNameFullPathFolder.append("/");
1113 dirNameFullPathFolder.append(dirNameSingleFolder);
1115 logger->debug("recmove: oldfoldername: {}", dirNameFullPathFolder);
1118 rmdir() deletes a directory, which must be empty.
1119 ENOTEMPTY - pathname contains entries other than . and ..
1120 So, should be safe to call rmdir on non-empty dir
1122 rmdir(dirNameFullPathFolder.c_str()); // can't do anything about a fail result at this point.
1125 Recordings->Update();
1126 js["Result"] = true;
1127 js["NewRecordingFileName"] = newDirNameFullPathDate;
1131 js["Result"] = false;
1134 logger->error("recmove: Bad parameters");
1135 js["Error"] = "Bad request parameters";
1139 logger->error("recmove: Could not find recording to move");
1140 js["Error"] = "Bad filename";
1144 logger->error("recmove: Could not move recording, it is still recording");
1145 js["Error"] = "Cannot move recording in progress";
1149 logger->error("recmove: Failed to make new dir (1)");
1150 js["Error"] = "Failed to create new directory (1)";
1154 logger->error("recmove: Something already exists? (1)");
1155 js["Error"] = "Something already exists at the new path (1)";
1159 logger->error("recmove: Failed to make new dir (2)");
1160 js["Error"] = "Failed to create new directory (2)";
1164 logger->error("recmove: Something already exists?");
1165 js["Error"] = "Something already exists at the new path";
1169 logger->error("recmove: Rename failed");
1170 js["Error"] = "Move failed";
1174 logger->error("recrename: ExchangeChars lost our string");
1175 js["Error"] = "Rename failed";
1182 bool VDRClient::timersetactive(PFMap& postFields, Json::Value& js) // RETHROWS
1184 logger->debug("timersetactive");
1186 std::string rChannelID = getVarString(postFields, "ChannelID");
1187 std::string rName = getVarString(postFields, "Name");
1188 std::string rStartTime = getVarString(postFields, "StartTime");
1189 std::string rStopTime = getVarString(postFields, "StopTime");
1190 std::string rWeekDays = getVarString(postFields, "WeekDays");
1191 std::string tNewActive = getVarString(postFields, "SetActive");
1193 logger->debug("timersetactive: {} {}:{}:{}:{}:{}", tNewActive, rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1195 if ((tNewActive != "true") && (tNewActive != "false"))
1197 js["Result"] = false;
1198 js["Error"] = "Bad request parameters";
1204 Timers->SetExplicitModify();
1206 cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1209 if (tNewActive == "true") timer->SetFlags(tfActive);
1210 else timer->ClrFlags(tfActive);
1212 js["Result"] = true;
1213 Timers->SetModified();
1217 js["Result"] = false;
1218 js["Error"] = "Timer not found";
1222 bool VDRClient::timerdel(PFMap& postFields, Json::Value& js) // RETHROWS
1224 logger->debug("timerdel");
1226 std::string rChannelID = getVarString(postFields, "ChannelID");
1227 std::string rName = getVarString(postFields, "Name");
1228 std::string rStartTime = getVarString(postFields, "StartTime");
1229 std::string rStopTime = getVarString(postFields, "StopTime");
1230 std::string rWeekDays = getVarString(postFields, "WeekDays");
1232 logger->debug("timerdel: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1235 Timers->SetExplicitModify();
1237 cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1240 if (timer->Recording())
1242 logger->debug("timerdel: Unable to delete timer - timer is running");
1243 js["Result"] = false;
1244 js["Error"] = "Timer is running";
1249 Timers->SetModified();
1250 js["Result"] = true;
1254 js["Result"] = false;
1255 js["Error"] = "Timer not found";
1259 bool VDRClient::timerisrecording(PFMap& postFields, Json::Value& js) // RETHROWS
1261 logger->debug("timerisrecording");
1263 std::string rChannelID = getVarString(postFields, "ChannelID");
1264 std::string rName = getVarString(postFields, "Name");
1265 std::string rStartTime = getVarString(postFields, "StartTime");
1266 std::string rStopTime = getVarString(postFields, "StopTime");
1267 std::string rWeekDays = getVarString(postFields, "WeekDays");
1269 logger->debug("timerisrecording: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1273 const cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1276 js["Recording"] = timer->Recording();
1277 js["Pending"] = timer->Pending();
1278 js["Result"] = true;
1282 js["Result"] = false;
1283 js["Error"] = "Timer not found";
1287 bool VDRClient::recresetresume(PFMap& postFields, Json::Value& js) // RETHROWS
1289 logger->debug("recresetresume");
1291 std::string reqfilename = getVarString(postFields, "filename");
1292 logger->debug("recresetresume: {}", reqfilename);
1296 const cRecordings* Recordings = cRecordings::GetRecordingsRead(StateKey);
1298 const cRecording* recording = Recordings->GetByName(reqfilename.c_str());
1304 js["Result"] = false;
1305 js["Error"] = "Could not find recording to reset resume";
1309 logger->debug("recresetresume: Reset resume for: {}", recording->Name());
1311 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
1315 if (ResumeFile.Read() >= 0)
1317 ResumeFile.Delete();
1318 js["Result"] = true;
1323 js["Result"] = false;
1324 js["Error"] = "Recording has no resume point";
1329 bool VDRClient::timeredit(PFMap& postFields, Json::Value& js) // RETHROWS
1331 logger->debug("timeredit");
1333 std::string oldName = getVarString(postFields, "OldName");
1334 std::string oldActive = getVarString(postFields, "OldActive");
1335 std::string oldChannelID = getVarString(postFields, "OldChannelID");
1336 std::string oldDay = getVarString(postFields, "OldDay");
1337 std::string oldWeekDays = getVarString(postFields, "OldWeekDays");
1338 std::string oldStartTime = getVarString(postFields, "OldStartTime");
1339 std::string oldStopTime = getVarString(postFields, "OldStopTime");
1340 std::string oldPriority = getVarString(postFields, "OldPriority");
1341 std::string oldLifetime = getVarString(postFields, "OldLifetime");
1343 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", oldName, oldActive, oldChannelID, oldDay, oldWeekDays, oldStartTime, oldStopTime, oldPriority, oldLifetime);
1345 std::string newName = getVarString(postFields, "NewName");
1346 std::string newActive = getVarString(postFields, "NewActive");
1347 std::string newChannelID = getVarString(postFields, "NewChannelID");
1348 std::string newDay = getVarString(postFields, "NewDay");
1349 std::string newWeekDays = getVarString(postFields, "NewWeekDays");
1350 std::string newStartTime = getVarString(postFields, "NewStartTime");
1351 std::string newStopTime = getVarString(postFields, "NewStopTime");
1352 std::string newPriority = getVarString(postFields, "NewPriority");
1353 std::string newLifetime = getVarString(postFields, "NewLifetime");
1355 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", newName, newActive, newChannelID, newDay, newWeekDays, newStartTime, newStopTime, newPriority, newLifetime);
1358 Timers->SetExplicitModify();
1360 cTimer* timer = findTimer2(Timers, oldName.c_str(), oldActive.c_str(), oldChannelID.c_str(), oldDay.c_str(), oldWeekDays.c_str(), oldStartTime.c_str(), oldStopTime.c_str(), oldPriority.c_str(), oldLifetime.c_str());
1363 js["Result"] = false;
1364 js["Error"] = "Timer not found";
1368 Timers->SetModified();
1370 // Old version commented below (now removed) used to set each thing individually based on whether it had changed. However, since
1371 // the only way to change the timer channel appears to be with the cTimer::Parse function, might as well use that
1372 // for everything it supports
1373 // Except flags. Get current flags, set using Parse, then add/remove active as needed the other way.
1375 time_t nstt = std::stoi(newStartTime);
1377 localtime_r(&nstt, &nstm);
1378 int nssf = (nstm.tm_hour * 100) + nstm.tm_min;
1380 time_t nztt = std::stoi(newStopTime);
1382 localtime_r(&nztt, &nztm);
1383 int nzsf = (nztm.tm_hour * 100) + nztm.tm_min;
1385 std::replace(newName.begin(), newName.end(), ':', '|');
1387 // ? Convert to std::string?
1388 cString parseBuffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s",
1389 timer->Flags(), newChannelID.c_str(), *(cTimer::PrintDay(std::stoi(newDay), std::stoi(newWeekDays), true)),
1390 nssf, nzsf, std::stoi(newPriority), std::stoi(newLifetime), newName.c_str(), timer->Aux() ? timer->Aux() : "");
1392 logger->debug("timeredit: new parse: {}", *parseBuffer);
1394 bool parseResult = timer->Parse(*parseBuffer);
1397 js["Result"] = false;
1398 js["Error"] = "Timer parsing failed";
1402 if (timer->HasFlags(tfActive) != !(strcasecmp(newActive.c_str(), "true")))
1404 logger->debug("timeredit: {} {} set new active: {}", timer->HasFlags(tfActive), !(strcasecmp(newActive.c_str(), "true")), newActive);
1406 if (strcasecmp(newActive.c_str(), "true") == 0)
1408 timer->SetFlags(tfActive);
1410 else if (strcasecmp(newActive.c_str(), "false") == 0)
1412 timer->ClrFlags(tfActive);
1416 js["Result"] = false;
1417 js["Error"] = "Bad request parameters";
1422 js["Result"] = true;
1426 bool VDRClient::epgfilteradd(PFMap& postFields, Json::Value& js) // RETHROWS
1428 std::string channel = getVarString(postFields, "channel");
1429 std::string programme = getVarString(postFields, "programme");
1431 logger->debug("epgFilterAdd: {} {}", channel, programme);
1433 libconfig::Config epgFilter;
1434 if (!loadEpgFilter(epgFilter))
1436 js["Result"] = false;
1437 js["Error"] = "Error initialising EPG filter";
1441 libconfig::Setting& setting = epgFilter.lookup("filters");
1442 libconfig::Setting& newPair = setting.add(libconfig::Setting::Type::TypeGroup);
1443 libconfig::Setting& newChannel = newPair.add("c", libconfig::Setting::Type::TypeString);
1444 newChannel = channel;
1445 libconfig::Setting& newProgramme = newPair.add("p", libconfig::Setting::Type::TypeString);
1446 newProgramme = programme;
1448 if (!saveEpgFilter(epgFilter))
1450 js["Result"] = false;
1451 js["Error"] = "Failed to save EPG filter";
1455 js["Result"] = true;
1459 bool VDRClient::epgfilterdel(PFMap& postFields, Json::Value& js) // RETHROWS
1461 std::string channel = getVarString(postFields, "channel");
1462 std::string programme = getVarString(postFields, "programme");
1464 logger->debug("epgFilterDel: {} {}", channel, programme);
1466 libconfig::Config epgFilter;
1467 if (!loadEpgFilter(epgFilter))
1469 js["Result"] = false;
1470 js["Error"] = "Error initialising EPG filter";
1476 libconfig::Setting& setting = epgFilter.lookup("filters");
1477 int numFilters = setting.getLength();
1479 for (x = 0; x < numFilters; x++)
1481 libconfig::Setting& pair = setting[x];
1484 if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1486 js["Result"] = false;
1487 js["Error"] = "Filter file format error";
1490 logger->debug("loop: {} {}", c, p);
1491 if ((c == channel) && (p == programme))
1495 if (!saveEpgFilter(epgFilter))
1497 js["Result"] = false;
1498 js["Error"] = "Failed to save EPG filter";
1502 logger->debug("Found and deleted: {} {}", c, p);
1503 js["Result"] = true;
1508 js["Result"] = false;
1509 js["Error"] = "Channel/Programme not found";
1512 catch (const std::exception& e)
1514 js["Result"] = false;
1515 js["Error"] = "Unknown error";
1520 bool VDRClient::epgfilterget(PFMap& postFields, Json::Value& js) // RETHROWS
1522 logger->debug("epgFilterget");
1524 libconfig::Config epgFilter;
1525 if (!loadEpgFilter(epgFilter))
1527 js["Result"] = false;
1528 js["Error"] = "Error initialising EPG filter";
1534 libconfig::Setting& setting = epgFilter.lookup("filters");
1535 int numFilters = setting.getLength();
1537 Json::Value jsfilters(Json::arrayValue);
1538 for (x = 0; x < numFilters; x++)
1540 libconfig::Setting& pair = setting[x];
1543 if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1545 js["Result"] = false;
1546 js["Error"] = "Filter file format error";
1549 logger->debug("loop: {} {}", c, p);
1550 Json::Value oneFilter;
1553 jsfilters.append(oneFilter);
1555 js["EPGFilters"] = jsfilters;
1557 catch (const std::exception& e)
1559 js["Result"] = false;
1560 js["Error"] = "Unknown error";
1564 js["Result"] = true;
1568 //////////////////////////////////////////////////////////////////////////////////////////////////
1570 const cEvent* VDRClient::getEvent(const cChannels* Channels, const cSchedules* Schedules,
1571 Json::Value& js, int channelNumber, int eventID, int aroundTime)
1573 const cChannel* channel = NULL;
1575 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
1577 if (channel->GroupSep()) continue;
1578 if (channel->Number() == channelNumber) break;
1583 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1584 js["Error"] = "Could not find channel";
1588 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
1591 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1592 js["Error"] = "Internal schedules error (2)";
1596 const cEvent* event = NULL;
1598 event = Schedule->GetEvent(eventID);
1600 event = Schedule->GetEventAround(aroundTime);
1604 logger->error("getevent: Could not find requested event: {}", eventID);
1605 js["Error"] = "Internal schedules error (3)";
1612 cTimer* VDRClient::findTimer(cTimers* Timers,
1613 const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1615 int numTimers = Timers->Count();
1617 for (int i = 0; i < numTimers; i++)
1619 timer = Timers->Get(i);
1621 logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1622 logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1623 logger->debug("findtimer: {}", rChannelID);
1624 logger->debug("findtimer: {}", timer->File());
1625 logger->debug("findtimer: {}", rName);
1626 logger->debug("findtimer: {}", timer->StartTime());
1627 logger->debug("findtimer: {}", rStartTime);
1628 logger->debug("findtimer: {}", timer->StopTime());
1629 logger->debug("findtimer: {}", rStopTime);
1630 logger->debug("findtimer: {}", timer->WeekDays());
1631 logger->debug("findtimer: {}", rWeekDays);
1634 (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1635 && (strcmp(timer->File(), rName) == 0)
1636 && (timer->StartTime() == atoi(rStartTime))
1637 && (timer->StopTime() == atoi(rStopTime))
1638 && (timer->WeekDays() == atoi(rWeekDays))
1641 logger->debug("findtimer: found");
1645 logger->debug("findtimer: no timer found");
1649 // Differs only from above by taking const Timers and returning const cTimer
1650 const cTimer* VDRClient::findTimer(const cTimers* Timers,
1651 const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1653 int numTimers = Timers->Count();
1654 const cTimer* timer;
1655 for (int i = 0; i < numTimers; i++)
1657 timer = Timers->Get(i);
1659 logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1660 logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1661 logger->debug("findtimer: {}", rChannelID);
1662 logger->debug("findtimer: {}", timer->File());
1663 logger->debug("findtimer: {}", rName);
1664 logger->debug("findtimer: {}", timer->StartTime());
1665 logger->debug("findtimer: {}", rStartTime);
1666 logger->debug("findtimer: {}", timer->StopTime());
1667 logger->debug("findtimer: {}", rStopTime);
1668 logger->debug("findtimer: {}", timer->WeekDays());
1669 logger->debug("findtimer: {}", rWeekDays);
1672 (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1673 && (strcmp(timer->File(), rName) == 0)
1674 && (timer->StartTime() == atoi(rStartTime))
1675 && (timer->StopTime() == atoi(rStopTime))
1676 && (timer->WeekDays() == atoi(rWeekDays))
1679 logger->debug("findtimer: found");
1683 logger->debug("findtimer: no timer found");
1687 cTimer* VDRClient::findTimer2(cTimers* Timers, const char* rName, const char* rActive, const char* rChannelID, const char* rDay, const char* rWeekDays, const char* rStartTime, const char* rStopTime, const char* rPriority, const char* rLifetime)
1689 int numTimers = Timers->Count();
1691 logger->debug("findtimer2: {} {} {} {} {} {} {} {} {}", rName, rActive, rChannelID, rDay, rWeekDays, rStartTime, rStopTime, rPriority, rLifetime);
1692 for (int i = 0; i < numTimers; i++)
1694 timer = Timers->Get(i);
1696 logger->debug("findtimer2: search: {} {} {} {} {} {} {} {} {}", timer->File(), timer->HasFlags(tfActive), (const char*)timer->Channel()->GetChannelID().ToString(),
1697 (int)timer->Day(), timer->WeekDays(), (int)timer->StartTime(), (int)timer->StopTime(),
1698 timer->Priority(), timer->Lifetime());
1700 if ( (strcmp(timer->File(), rName) == 0)
1701 && (timer->HasFlags(tfActive) == !(strcasecmp(rActive, "true")))
1702 && (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1703 && (timer->Day() == atoi(rDay))
1704 && (timer->WeekDays() == atoi(rWeekDays))
1705 && (timer->StartTime() == atoi(rStartTime))
1706 && (timer->StopTime() == atoi(rStopTime))
1707 && (timer->Priority() == atoi(rPriority))
1708 && (timer->Lifetime() == atoi(rLifetime))
1711 logger->debug("findtimer2: found");
1715 logger->debug("findtimer2: no timer found");
1719 int VDRClient::getVarInt(PFMap& postFields, const char* paramName) // THROWS
1721 auto i = postFields.find(paramName);
1722 if (i == postFields.end()) throw BadParamException(paramName);
1724 try { r = std::stoi(i->second); }
1725 catch (const std::exception& e) { throw BadParamException(paramName); }
1729 std::string VDRClient::getVarString(PFMap& postFields, const char* paramName) // THROWS
1731 auto i = postFields.find(paramName);
1732 if (i == postFields.end()) throw BadParamException(paramName);
1733 if (i->second.empty()) throw BadParamException(paramName);
1737 bool VDRClient::loadEpgFilter(libconfig::Config& epgFilter)
1739 const char* configDir = cPlugin::ConfigDirectory("jsonserver");
1742 logger->error("loadEpgFilter: Error: Could not get config dir from VDR");
1746 std::string epgFilterFile(std::string(configDir) + std::string("/epgfilter.conf"));
1747 FILE* fp = fopen(epgFilterFile.c_str(), "a");
1750 logger->error("loadEpgFilter: Error: Failed to fopen epgfilter.conf");
1757 epgFilter.readFile(epgFilterFile.c_str());
1759 catch (const libconfig::FileIOException &fioex)
1761 logger->error("loadEpgFilter: Error: Failed to read filter file");
1764 catch(const libconfig::ParseException &pex)
1766 logger->error("loadEpgFilter: Parse error at {}: {} - {}", pex.getFile(), pex.getLine(), pex.getError());
1772 libconfig::Setting& setting = epgFilter.lookup("filters");
1774 if (!setting.isList())
1776 logger->error("loadEpgFilter: Error: Failed to read filter file (2)");
1780 catch (const libconfig::SettingNotFoundException& e)
1782 libconfig::Setting& setting = epgFilter.getRoot();
1783 setting.add("filters", libconfig::Setting::Type::TypeList);
1789 bool VDRClient::saveEpgFilter(libconfig::Config& epgFilter)
1791 const char* configDir = cPlugin::ConfigDirectory("jsonserver");
1794 logger->error("saveEpgFilter: Error: Could not get config dir from VDR");
1798 std::string epgFilterFile(std::string(configDir) + std::string("/epgfilter.conf"));
1801 epgFilter.writeFile(epgFilterFile.c_str());
1802 logger->debug("saveEpgFilter: EPG filter saved");
1805 catch (const libconfig::FileIOException& e)
1807 logger->error("saveEpgFilter: Error: File write error");