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(const std::string& _configDir)
30 : configDir(_configDir)
32 logger = spd::get("jsonserver_spdlog");
34 Json::StreamWriterBuilder jsonWriterBuilder;
35 // jsonWriterBuilder["commentStyle"] = "None";
36 // jsonWriterBuilder["indentation"] = " ";
37 jsonwriter.reset(jsonWriterBuilder.newStreamWriter());
40 VDRClient::~VDRClient()
42 logger->debug("VDRClient destructor");
45 bool VDRClient::process(std::string& request, PFMap& postFields, std::stringstream* returnStringStream)
47 Json::Value returnJSON;
52 if (request == "gettime") success = gettime(postFields, returnJSON);
53 else if (request == "diskstats") success = diskstats(postFields, returnJSON);
54 else if (request == "channellist") success = channellist(postFields, returnJSON);
55 else if (request == "reclist") success = reclist(postFields, returnJSON);
56 else if (request == "timerlist") success = timerlist(postFields, returnJSON);
57 else if (request == "epgdownload") success = epgdownload(postFields, returnJSON);
58 else if (request == "tunersstatus") success = tunersstatus(postFields, returnJSON);
59 else if (request == "epgfilterget") success = epgfilterget(postFields, returnJSON);
61 else if (request == "channelschedule") success = channelschedule(postFields, returnJSON);
62 else if (request == "getscheduleevent") success = getscheduleevent(postFields, returnJSON);
63 else if (request == "epgsearch") success = epgsearch(postFields, returnJSON);
64 else if (request == "epgsearchsame") success = epgsearchsame(postFields, returnJSON);
65 else if (request == "epgsearchotherhalf")success = epgsearchotherhalf(postFields, returnJSON);
66 else if (request == "timerset") success = timerset(postFields, returnJSON);
67 else if (request == "recinfo") success = recinfo(postFields, returnJSON);
68 else if (request == "recstop") success = recstop(postFields, returnJSON);
69 else if (request == "recdel") success = recdel(postFields, returnJSON);
70 else if (request == "recrename") success = recrename(postFields, returnJSON);
71 else if (request == "recmove") success = recmove(postFields, returnJSON);
72 else if (request == "timersetactive") success = timersetactive(postFields, returnJSON);
73 else if (request == "timerdel") success = timerdel(postFields, returnJSON);
74 else if (request == "timerisrecording") success = timerisrecording(postFields, returnJSON);
75 else if (request == "recresetresume") success = recresetresume(postFields, returnJSON);
76 else if (request == "timeredit") success = timeredit(postFields, returnJSON);
77 else if (request == "epgfilteradd") success = epgfilteradd(postFields, returnJSON);
78 else if (request == "epgfilterdel") success = epgfilterdel(postFields, returnJSON);
80 catch (const BadParamException& e)
82 logger->error("Bad parameter in call, paramName: {}", e.param);
83 returnJSON["Result"] = false;
84 returnJSON["Error"] = "Bad request parameter";
85 returnJSON["Detail"] = e.param;
89 if (!success) return false;
91 jsonwriter->write(returnJSON, returnStringStream);
92 logger->debug("Done jsonwriter write");
96 bool VDRClient::gettime(PFMap& postFields, Json::Value& js)
98 logger->debug("get_time");
101 gettimeofday(&tv, NULL);
103 js["Time"] = (Json::UInt64)tv.tv_sec;
104 js["MTime"] = (Json::UInt)(tv.tv_usec/1000);
109 bool VDRClient::diskstats(PFMap& postFields, Json::Value& js)
111 logger->debug("diskstats");
115 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
117 js["FreeMiB"] = FreeMB;
118 js["UsedMiB"] = UsedMB;
119 js["Percent"] = Percent;
124 bool VDRClient::channellist(PFMap& postFields, Json::Value& js)
126 logger->debug("channellist");
128 Json::Value jschannels(Json::arrayValue);
132 for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel))
134 if (!channel->GroupSep())
136 Json::Value oneChannel;
137 oneChannel["ID"] = (const char *)channel->GetChannelID().ToString();
138 oneChannel["Number"] = channel->Number();
139 oneChannel["Name"] = channel->Name();
140 jschannels.append(oneChannel);
143 js["Channels"] = jschannels;
148 bool VDRClient::reclist(PFMap& postFields, Json::Value& js)
150 logger->debug("reclist");
152 Json::Value jsrecordings(Json::arrayValue);
154 LOCK_RECORDINGS_READ;
156 for (const cRecording *recording = Recordings->First(); recording; recording = Recordings->Next(recording))
159 oneRec["StartTime"] = (Json::UInt)recording->Start();
160 oneRec["Length"] = (Json::UInt)recording->LengthInSeconds();
161 oneRec["IsNew"] = recording->IsNew();
162 oneRec["Name"] = recording->Name();
163 oneRec["Filename"] = recording->FileName();
164 oneRec["FileSizeMB"] = recording->FileSizeMB();
166 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
167 if (rc) oneRec["CurrentlyRecording"] = true;
168 else oneRec["CurrentlyRecording"] = false;
170 jsrecordings.append(oneRec);
172 js["Recordings"] = jsrecordings;
177 bool VDRClient::timerlist(PFMap& postFields, Json::Value& js)
179 logger->debug("timerlist");
181 Json::Value jstimers(Json::arrayValue);
189 int numTimers = Timers->Count();
191 for (int i = 0; i < numTimers; i++)
193 timer = Timers->Get(i);
194 Json::Value oneTimer;
195 oneTimer["Active"] = timer->HasFlags(tfActive);
196 oneTimer["Recording"] = timer->Recording();
197 oneTimer["Pending"] = timer->Pending();
198 oneTimer["Priority"] = timer->Priority();
199 oneTimer["Lifetime"] = timer->Lifetime();
200 oneTimer["ChannelNumber"] = timer->Channel()->Number();
201 oneTimer["ChannelID"] = (const char *)timer->Channel()->GetChannelID().ToString();
202 oneTimer["StartTime"] = (int)timer->StartTime();
203 oneTimer["StopTime"] = (int)timer->StopTime();
204 oneTimer["Day"] = (int)timer->Day();
205 oneTimer["WeekDays"] = timer->WeekDays();
206 oneTimer["Name"] = timer->File();
208 const cEvent* event = timer->Event();
211 oneTimer["EventID"] = event->EventID();
215 int channelNumber = timer->Channel()->Number();
216 int aroundTime = timer->StartTime() + 1;
217 const cEvent* eventAround = getEvent(Channels, Schedules, js, channelNumber, 0, aroundTime);
220 oneTimer["EventID"] = eventAround->EventID();
224 oneTimer["EventID"] = 0;
228 jstimers.append(oneTimer);
231 js["Timers"] = jstimers;
232 js["NumTuners"] = cDevice::NumDevices();
237 bool VDRClient::channelschedule(PFMap& postFields, Json::Value& js) // RETHROWS
239 logger->debug("channelschedule");
240 int channelNumber = getVarInt(postFields, "channelnumber");
241 int startTime = getVarInt(postFields, "starttime");
242 int duration = getVarInt(postFields, "duration");
244 Json::Value jsevents(Json::arrayValue);
248 const cChannel* channel = NULL;
249 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
251 if (channel->GroupSep()) continue;
252 if (channel->Number() == channelNumber) break;
257 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
258 js["Result"] = false;
259 js["Error"] = "Could not find channel";
265 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
268 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
269 js["Result"] = false;
270 js["Error"] = "Internal schedules error (2)";
274 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
276 if ((event->StartTime() + event->Duration()) < time(NULL)) continue; //in the past filter
277 if ((event->StartTime() + event->Duration()) <= startTime) continue; //start time filter
278 if (event->StartTime() >= (startTime + duration)) continue; //duration filter
280 Json::Value oneEvent;
281 oneEvent["ID"] = event->EventID();
282 oneEvent["Time"] = (Json::UInt)event->StartTime();
283 oneEvent["Duration"] = event->Duration();
284 oneEvent["Title"] = event->Title() ? event->Title() : "";
285 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
286 oneEvent["HasTimer"] = event->HasTimer();
287 jsevents.append(oneEvent);
291 js["Events"] = jsevents;
295 bool VDRClient::getscheduleevent(PFMap& postFields, Json::Value& js) // RETHROWS
297 logger->debug("getscheduleevent");
299 int channelNumber = getVarInt(postFields, "channelnumber");
300 int eventID = getVarInt(postFields, "eventid");
305 const cEvent* event = getEvent(Channels, Schedules, js, channelNumber, eventID, 0);
308 js["Result"] = false;
312 Json::Value oneEvent;
313 oneEvent["ID"] = event->EventID();
314 oneEvent["Time"] = (Json::UInt)event->StartTime();
315 oneEvent["Duration"] = event->Duration();
316 oneEvent["Title"] = event->Title() ? event->Title() : "";
317 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
318 oneEvent["Description"] = event->Description() ? event->Description() : "";
319 oneEvent["HasTimer"] = event->HasTimer();
320 oneEvent["RunningStatus"] = event->RunningStatus();
323 js["Event"] = oneEvent;
327 bool VDRClient::epgdownload(PFMap& postFields, Json::Value& js)
329 logger->debug("epgdownload");
330 Json::Value jsevents(Json::arrayValue);
335 for (const cChannel* channel = Channels->First(); channel; channel = Channels->Next(channel))
337 if (channel->GroupSep()) continue;
339 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
340 if (!Schedule) continue;
342 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
344 Json::Value oneEvent;
345 oneEvent["ChannelNumber"] = channel->Number();
346 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
347 oneEvent["ID"] = event->EventID();
348 oneEvent["Time"] = (Json::UInt)event->StartTime();
349 oneEvent["Duration"] = event->Duration();
350 oneEvent["Title"] = event->Title() ? event->Title() : "";
351 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
352 oneEvent["HasTimer"] = event->HasTimer();
353 oneEvent["Description"] = event->Description() ? event->Description() : "";
354 jsevents.append(oneEvent);
359 js["Events"] = jsevents;
363 bool VDRClient::epgsearch(PFMap& postFields, Json::Value& js) // RETHROWS
365 logger->debug("epgsearch");
367 std::string searchfor = getVarString(postFields, "searchfor");
368 logger->debug("epgsearch: search for: {}", searchfor);
370 Json::Value jsevents(Json::arrayValue);
375 for (const cChannel* channel = Channels->First(); channel; channel = Channels->Next(channel))
377 if (channel->GroupSep()) continue;
379 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
380 if (!Schedule) continue;
383 bool founddescription;
384 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
387 founddescription = false;
389 if (event->Title() && strcasestr(event->Title(), searchfor.c_str())) foundtitle = true;
391 if (!foundtitle && event->Description())
392 if (strcasestr(event->Description(), searchfor.c_str())) founddescription = true;
394 if (foundtitle || founddescription)
396 Json::Value oneEvent;
397 oneEvent["ChannelNumber"] = channel->Number();
398 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
399 oneEvent["ID"] = event->EventID();
400 oneEvent["Time"] = (Json::UInt)event->StartTime();
401 oneEvent["Duration"] = event->Duration();
402 oneEvent["Title"] = event->Title() ? event->Title() : "";
403 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
404 oneEvent["HasTimer"] = event->HasTimer();
405 oneEvent["Description"] = event->Description() ? event->Description() : "";
406 if (founddescription)
407 oneEvent["FoundInDesc"] = true;
409 oneEvent["FoundInDesc"] = false;
410 jsevents.append(oneEvent);
416 js["Events"] = jsevents;
418 logger->debug("epgsearch: search for: {} done", searchfor);
423 bool VDRClient::epgsearchsame(PFMap& postFields, Json::Value& js) // RETHROWS
425 logger->debug("epgsearchsame");
427 int atTime = getVarInt(postFields, "time");
428 std::string sTitle = getVarString(postFields, "title");
430 logger->debug("epgsearchsame: request time: {}, title: {}", atTime, sTitle);
432 Json::Value jsevents(Json::arrayValue);
437 for (const cSchedule *schedule = Schedules->First(); (schedule != NULL); schedule = Schedules->Next(schedule))
439 event = schedule->GetEventAround(atTime);
440 if (!event) continue; // nothing found on this schedule(channel)
442 if (!strcmp(event->Title(), sTitle.c_str()))
444 Json::Value oneEvent;
445 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
446 oneEvent["ID"] = event->EventID();
447 oneEvent["Time"] = (Json::UInt)event->StartTime();
448 oneEvent["Duration"] = event->Duration();
449 oneEvent["Title"] = event->Title() ? event->Title() : "";
450 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
451 oneEvent["HasTimer"] = event->HasTimer();
452 //oneEvent["Description"] = event->Description() ? event->Description() : "";
453 jsevents.append(oneEvent);
457 js["Events"] = jsevents;
462 bool VDRClient::epgsearchotherhalf(PFMap& postFields, Json::Value& js) // RETHROWS
464 logger->debug("epgsearchotherhalf");
465 int channelNumber = getVarInt(postFields, "channelnumber");
466 int eventID = getVarInt(postFields, "eventid");
467 const cChannel* channel = NULL;
471 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
473 if (channel->GroupSep()) continue;
474 if (channel->Number() == channelNumber) break;
479 logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
480 js["Result"] = false;
481 js["Error"] = "Could not find channel";
487 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
490 logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
491 js["Result"] = false;
492 js["Error"] = "Internal schedules error (2)";
496 js["OtherEventFound"] = false;
498 const cEvent* eventM1 = NULL;
499 const cEvent* eventM2 = NULL;
500 const cEvent* otherEvent = NULL;
502 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
504 if (event->EventID() == (unsigned long)eventID)
506 if ((eventM2 != NULL) && (!strcmp(eventM2->Title(), event->Title())))
508 otherEvent = eventM2;
512 const cEvent* eventP1 = Schedule->Events()->Next(event);
515 const cEvent* eventP2 = Schedule->Events()->Next(eventP1);
517 if (eventP2 && (!strcmp(eventP2->Title(), event->Title())))
519 otherEvent = eventP2;
526 Json::Value oneEvent;
527 oneEvent["ID"] = otherEvent->EventID();
528 oneEvent["ChannelNumber"] = channel->Number();
529 oneEvent["Time"] = (Json::UInt)otherEvent->StartTime();
530 oneEvent["Duration"] = otherEvent->Duration();
531 oneEvent["Title"] = otherEvent->Title() ? otherEvent->Title() : "";
532 oneEvent["HasTimer"] = otherEvent->HasTimer();
533 js["Event"] = oneEvent;
534 js["OtherEventFound"] = true;
548 bool VDRClient::tunersstatus(PFMap& postFields, Json::Value& js)
550 logger->debug("tunerstatus");
552 js["NumDevices"] = cDevice::NumDevices();
554 Json::Value jsdevices(Json::arrayValue);
556 for (int i = 0; i < cDevice::NumDevices(); i++)
558 Json::Value oneDevice;
559 cDevice *d = cDevice::GetDevice(i);
560 oneDevice["Number"] = d->DeviceNumber();
561 oneDevice["Type"] = (const char*)d->DeviceType();
562 oneDevice["Name"] = (const char*)d->DeviceName();
563 oneDevice["IsPrimary"] = d->IsPrimaryDevice();
565 const cChannel* cchannel = d->GetCurrentlyTunedTransponder();
568 oneDevice["Frequency"] = cchannel->Frequency();
569 oneDevice["SignalStrength"] = d->SignalStrength();
570 oneDevice["SignalQuality"] = d->SignalQuality();
575 oneDevice["Frequency"] = 0;
576 oneDevice["SignalStrength"] = 0;
577 oneDevice["SignalQuality"] = 0;
580 jsdevices.append(oneDevice);
583 js["Devices"] = jsdevices;
586 Json::Value jstimers(Json::arrayValue);
589 LOCK_CHANNELS_READ; // Because this calls timer->Channel() .. necessary?
591 int numTimers = Timers->Count();
593 for (int i = 0; i < numTimers; i++)
595 timer = Timers->Get(i);
597 if (timer->Recording())
599 Json::Value oneTimer;
600 oneTimer["Recording"] = timer->Recording();
601 oneTimer["StartTime"] = (int)timer->StartTime();
602 oneTimer["StopTime"] = (int)timer->StopTime();
603 oneTimer["File"] = timer->File();
605 cRecordControl* crc = cRecordControls::GetRecordControl(timer);
608 cDevice* crcd = crc->Device();
609 oneTimer["DeviceNumber"] = crcd->DeviceNumber();
613 oneTimer["DeviceNumber"] = Json::Value::null;
616 const cChannel* channel = timer->Channel();
619 oneTimer["ChannelName"] = channel->Name();
623 oneTimer["ChannelName"] = Json::Value::null;
626 jstimers.append(oneTimer);
630 js["CurrentRecordings"] = jstimers;
637 bool VDRClient::timerset(PFMap& postFields, Json::Value& js) // RETHROWS
639 logger->debug("timerset");
641 std::string sTimerString = getVarString(postFields, "timerstring");
643 logger->debug("timerset: '{}'", sTimerString);
644 cTimer *timer = new cTimer;
645 if (!timer->Parse(sTimerString.c_str()))
648 js["Result"] = false;
649 js["Error"] = "Failed to parse timer request details";
654 Timers->SetExplicitModify();
656 cTimer *t = Timers->GetTimer(timer);
660 js["Result"] = false;
661 js["Error"] = "Timer already exists";
666 Timers->SetModified();
672 bool VDRClient::recinfo(PFMap& postFields, Json::Value& js) // RETHROWS
674 logger->debug("recinfo");
676 std::string reqfilename = getVarString(postFields, "filename");
677 logger->debug("recinfo: {}", reqfilename);
679 LOCK_RECORDINGS_READ;
681 const cRecording *recording = Recordings->GetByName(reqfilename.c_str());
685 logger->error("recinfo: recinfo found no recording");
686 js["Result"] = false;
690 js["IsNew"] = recording->IsNew();
691 js["LengthInSeconds"] = recording->LengthInSeconds();
692 js["FileSizeMB"] = recording->FileSizeMB();
693 js["Name"] = recording->Name() ? recording->Name() : Json::Value::null;
694 js["Priority"] = recording->Priority();
695 js["LifeTime"] = recording->Lifetime();
696 js["Start"] = (Json::UInt)recording->Start();
698 js["CurrentlyRecordingStart"] = 0;
699 js["CurrentlyRecordingStop"] = 0;
700 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
703 js["CurrentlyRecordingStart"] = (Json::UInt)rc->Timer()->StartTime();
704 js["CurrentlyRecordingStop"] = (Json::UInt)rc->Timer()->StopTime();
707 js["ResumePoint"] = 0;
709 const cRecordingInfo *info = recording->Info();
712 js["ChannelName"] = info->ChannelName() ? info->ChannelName() : Json::Value::null;
713 js["Title"] = info->Title() ? info->Title() : Json::Value::null;
714 js["ShortText"] = info->ShortText() ? info->ShortText() : Json::Value::null;
715 js["Description"] = info->Description() ? info->Description() : Json::Value::null;
717 const cComponents* components = info->Components();
720 js["Components"] = Json::Value::null;
724 Json::Value jscomponents;
726 tComponent* component;
727 for (int i = 0; i < components->NumComponents(); i++)
729 component = components->Component(i);
731 Json::Value oneComponent;
732 oneComponent["Stream"] = component->stream;
733 oneComponent["Type"] = component->type;
734 oneComponent["Language"] = component->language ? component->language : Json::Value::null;
735 oneComponent["Description"] = component->description ? component->description : Json::Value::null;
736 jscomponents.append(oneComponent);
739 js["Components"] = jscomponents;
742 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
743 if (ResumeFile.Read() >= 0) js["ResumePoint"] = floor(ResumeFile.Read() / info->FramesPerSecond());
750 bool VDRClient::recstop(PFMap& postFields, Json::Value& js) // RETHROWS
752 logger->debug("recstop");
754 std::string reqfilename = getVarString(postFields, "filename");
755 logger->debug("recstop: {}", reqfilename);
758 LOCK_RECORDINGS_WRITE; // May not need write here, but to be safe..
760 cRecording *recording = Recordings->GetByName(reqfilename.c_str());
764 logger->error("recstop: recstop found no recording");
765 js["Result"] = false;
769 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
772 logger->error("recstop: not currently recording");
773 js["Result"] = false;
777 cTimer* timer = rc->Timer();
780 logger->error("recstop: timer not found");
781 js["Result"] = false;
785 timer->ClrFlags(tfActive);
792 bool VDRClient::recdel(PFMap& postFields, Json::Value& js) // RETHROWS
794 logger->debug("recdel");
796 std::string reqfilename = getVarString(postFields, "filename");
797 logger->debug("recdel: {}", reqfilename);
799 LOCK_RECORDINGS_WRITE;
801 cRecording *recording = Recordings->GetByName(reqfilename.c_str());
805 js["Result"] = false;
806 js["Error"] = "Could not find recording to delete";
810 logger->debug("recdel: Deleting recording: {}", recording->Name());
811 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
814 js["Result"] = false;
815 js["Error"] = "This recording is still recording.. ho ho";
819 if (recording->Delete())
821 Recordings->DelByName(recording->FileName());
826 js["Result"] = false;
827 js["Error"] = "Failed to delete recording";
833 bool VDRClient::recrename(PFMap& postFields, Json::Value& js) // RETHROWS
835 logger->debug("recrename");
837 std::string fileNameToAffect = getVarString(postFields, "filename");
838 std::string requestedNewName = getVarString(postFields, "newname");
842 LOCK_RECORDINGS_WRITE;
843 cRecording* recordingObj = Recordings->GetByName(fileNameToAffect.c_str());
844 if (!recordingObj) throw 2;
846 std::string newName(recordingObj->Folder());
847 logger->debug("newname after folder copy: #{}#");
848 if (newName.length()) newName += FOLDERDELIMCHAR;
849 newName += requestedNewName;
851 logger->debug("RM2 NEWLOC: #{}#", newName);
853 bool moveSuccess = recordingObj->ChangeName(newName.c_str());
855 js["Result"] = moveSuccess;
856 js["NewRecordingFileName"] = recordingObj->FileName();
858 catch (BadParamException& e)
860 js["Result"] = false;
861 logger->error("recmove: Bad parameters");
862 js["Error"] = "Bad request parameters";
866 js["Result"] = false;
869 logger->error("recmove: Could not find recording to move");
870 js["Error"] = "Bad filename";
877 bool VDRClient::recmove(PFMap& postFields, Json::Value& js) // RETHROWS
879 logger->debug("recmove");
881 std::string fileNameToAffect = getVarString(postFields, "filename");
882 std::string requestedNewDirName = getVarString(postFields, "newpath", true /* empty new path is ok */);
886 LOCK_RECORDINGS_WRITE;
887 cRecording* recordingObj = Recordings->GetByName(fileNameToAffect.c_str());
888 if (!recordingObj) throw 2;
891 if (requestedNewDirName.length()) newName = requestedNewDirName + FOLDERDELIMCHAR;
892 newName += recordingObj->BaseName();
894 logger->debug("RM2 NEWLOC: #{}#", newName);
896 bool moveSuccess = recordingObj->ChangeName(newName.c_str());
898 js["Result"] = moveSuccess;
899 js["NewRecordingFileName"] = recordingObj->FileName();
901 catch (BadParamException& e)
903 js["Result"] = false;
904 logger->error("recmove: Bad parameters");
905 js["Error"] = "Bad request parameters";
909 js["Result"] = false;
912 logger->error("recmove: Could not find recording to move");
913 js["Error"] = "Bad filename";
920 bool VDRClient::timersetactive(PFMap& postFields, Json::Value& js) // RETHROWS
922 logger->debug("timersetactive");
924 std::string rChannelID = getVarString(postFields, "ChannelID");
925 std::string rName = getVarString(postFields, "Name");
926 std::string rStartTime = getVarString(postFields, "StartTime");
927 std::string rStopTime = getVarString(postFields, "StopTime");
928 std::string rWeekDays = getVarString(postFields, "WeekDays");
929 std::string tNewActive = getVarString(postFields, "SetActive");
931 logger->debug("timersetactive: {} {}:{}:{}:{}:{}", tNewActive, rChannelID, rName, rStartTime, rStopTime, rWeekDays);
933 if ((tNewActive != "true") && (tNewActive != "false"))
935 js["Result"] = false;
936 js["Error"] = "Bad request parameters";
942 Timers->SetExplicitModify();
944 cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
947 if (tNewActive == "true") timer->SetFlags(tfActive);
948 else timer->ClrFlags(tfActive);
951 Timers->SetModified();
955 js["Result"] = false;
956 js["Error"] = "Timer not found";
960 bool VDRClient::timerdel(PFMap& postFields, Json::Value& js) // RETHROWS
962 logger->debug("timerdel");
964 std::string rChannelID = getVarString(postFields, "ChannelID");
965 std::string rName = getVarString(postFields, "Name");
966 std::string rStartTime = getVarString(postFields, "StartTime");
967 std::string rStopTime = getVarString(postFields, "StopTime");
968 std::string rWeekDays = getVarString(postFields, "WeekDays");
970 logger->debug("timerdel: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
973 Timers->SetExplicitModify();
975 cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
978 if (timer->Recording())
980 logger->debug("timerdel: Unable to delete timer - timer is running");
981 js["Result"] = false;
982 js["Error"] = "Timer is running";
987 Timers->SetModified();
992 js["Result"] = false;
993 js["Error"] = "Timer not found";
997 bool VDRClient::timerisrecording(PFMap& postFields, Json::Value& js) // RETHROWS
999 logger->debug("timerisrecording");
1001 std::string rChannelID = getVarString(postFields, "ChannelID");
1002 std::string rName = getVarString(postFields, "Name");
1003 std::string rStartTime = getVarString(postFields, "StartTime");
1004 std::string rStopTime = getVarString(postFields, "StopTime");
1005 std::string rWeekDays = getVarString(postFields, "WeekDays");
1007 logger->debug("timerisrecording: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1011 const cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1014 js["Recording"] = timer->Recording();
1015 js["Pending"] = timer->Pending();
1016 js["Result"] = true;
1020 js["Result"] = false;
1021 js["Error"] = "Timer not found";
1025 bool VDRClient::recresetresume(PFMap& postFields, Json::Value& js) // RETHROWS
1027 logger->debug("recresetresume");
1029 std::string reqfilename = getVarString(postFields, "filename");
1030 logger->debug("recresetresume: {}", reqfilename);
1034 const cRecordings* Recordings = cRecordings::GetRecordingsRead(StateKey);
1036 const cRecording* recording = Recordings->GetByName(reqfilename.c_str());
1042 js["Result"] = false;
1043 js["Error"] = "Could not find recording to reset resume";
1047 logger->debug("recresetresume: Reset resume for: {}", recording->Name());
1049 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
1053 if (ResumeFile.Read() >= 0)
1055 ResumeFile.Delete();
1056 js["Result"] = true;
1061 js["Result"] = false;
1062 js["Error"] = "Recording has no resume point";
1067 bool VDRClient::timeredit(PFMap& postFields, Json::Value& js) // RETHROWS
1069 logger->debug("timeredit");
1071 std::string oldName = getVarString(postFields, "OldName");
1072 std::string oldActive = getVarString(postFields, "OldActive");
1073 std::string oldChannelID = getVarString(postFields, "OldChannelID");
1074 std::string oldDay = getVarString(postFields, "OldDay");
1075 std::string oldWeekDays = getVarString(postFields, "OldWeekDays");
1076 std::string oldStartTime = getVarString(postFields, "OldStartTime");
1077 std::string oldStopTime = getVarString(postFields, "OldStopTime");
1078 std::string oldPriority = getVarString(postFields, "OldPriority");
1079 std::string oldLifetime = getVarString(postFields, "OldLifetime");
1081 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", oldName, oldActive, oldChannelID, oldDay, oldWeekDays, oldStartTime, oldStopTime, oldPriority, oldLifetime);
1083 std::string newName = getVarString(postFields, "NewName");
1084 std::string newActive = getVarString(postFields, "NewActive");
1085 std::string newChannelID = getVarString(postFields, "NewChannelID");
1086 std::string newDay = getVarString(postFields, "NewDay");
1087 std::string newWeekDays = getVarString(postFields, "NewWeekDays");
1088 std::string newStartTime = getVarString(postFields, "NewStartTime");
1089 std::string newStopTime = getVarString(postFields, "NewStopTime");
1090 std::string newPriority = getVarString(postFields, "NewPriority");
1091 std::string newLifetime = getVarString(postFields, "NewLifetime");
1093 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", newName, newActive, newChannelID, newDay, newWeekDays, newStartTime, newStopTime, newPriority, newLifetime);
1096 Timers->SetExplicitModify();
1098 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());
1101 js["Result"] = false;
1102 js["Error"] = "Timer not found";
1106 Timers->SetModified();
1108 // Old version commented below (now removed) used to set each thing individually based on whether it had changed. However, since
1109 // the only way to change the timer channel appears to be with the cTimer::Parse function, might as well use that
1110 // for everything it supports
1111 // Except flags. Get current flags, set using Parse, then add/remove active as needed the other way.
1113 time_t nstt = std::stoi(newStartTime);
1115 localtime_r(&nstt, &nstm);
1116 int nssf = (nstm.tm_hour * 100) + nstm.tm_min;
1118 time_t nztt = std::stoi(newStopTime);
1120 localtime_r(&nztt, &nztm);
1121 int nzsf = (nztm.tm_hour * 100) + nztm.tm_min;
1123 std::replace(newName.begin(), newName.end(), ':', '|');
1125 // ? Convert to std::string?
1126 cString parseBuffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s",
1127 timer->Flags(), newChannelID.c_str(), *(cTimer::PrintDay(std::stoi(newDay), std::stoi(newWeekDays), true)),
1128 nssf, nzsf, std::stoi(newPriority), std::stoi(newLifetime), newName.c_str(), timer->Aux() ? timer->Aux() : "");
1130 logger->debug("timeredit: new parse: {}", *parseBuffer);
1132 bool parseResult = timer->Parse(*parseBuffer);
1135 js["Result"] = false;
1136 js["Error"] = "Timer parsing failed";
1140 if (timer->HasFlags(tfActive) != !(strcasecmp(newActive.c_str(), "true")))
1142 logger->debug("timeredit: {} {} set new active: {}", timer->HasFlags(tfActive), !(strcasecmp(newActive.c_str(), "true")), newActive);
1144 if (strcasecmp(newActive.c_str(), "true") == 0)
1146 timer->SetFlags(tfActive);
1148 else if (strcasecmp(newActive.c_str(), "false") == 0)
1150 timer->ClrFlags(tfActive);
1154 js["Result"] = false;
1155 js["Error"] = "Bad request parameters";
1160 js["Result"] = true;
1164 bool VDRClient::epgfilteradd(PFMap& postFields, Json::Value& js) // RETHROWS
1166 std::string channel = getVarString(postFields, "channel");
1167 std::string programme = getVarString(postFields, "programme");
1169 logger->debug("epgFilterAdd: {} {}", channel, programme);
1171 libconfig::Config epgFilter;
1172 if (!loadEpgFilter(epgFilter))
1174 js["Result"] = false;
1175 js["Error"] = "Error initialising EPG filter";
1179 libconfig::Setting& setting = epgFilter.lookup("filters");
1180 libconfig::Setting& newPair = setting.add(libconfig::Setting::Type::TypeGroup);
1181 libconfig::Setting& newChannel = newPair.add("c", libconfig::Setting::Type::TypeString);
1182 newChannel = channel;
1183 libconfig::Setting& newProgramme = newPair.add("p", libconfig::Setting::Type::TypeString);
1184 newProgramme = programme;
1186 if (!saveEpgFilter(epgFilter))
1188 js["Result"] = false;
1189 js["Error"] = "Failed to save EPG filter";
1193 js["Result"] = true;
1197 bool VDRClient::epgfilterdel(PFMap& postFields, Json::Value& js) // RETHROWS
1199 std::string channel = getVarString(postFields, "channel");
1200 std::string programme = getVarString(postFields, "programme");
1202 logger->debug("epgFilterDel: {} {}", channel, programme);
1204 libconfig::Config epgFilter;
1205 if (!loadEpgFilter(epgFilter))
1207 js["Result"] = false;
1208 js["Error"] = "Error initialising EPG filter";
1214 libconfig::Setting& setting = epgFilter.lookup("filters");
1215 int numFilters = setting.getLength();
1217 for (x = 0; x < numFilters; x++)
1219 libconfig::Setting& pair = setting[x];
1222 if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1224 js["Result"] = false;
1225 js["Error"] = "Filter file format error";
1228 if ((c == channel) && (p == programme))
1232 if (!saveEpgFilter(epgFilter))
1234 js["Result"] = false;
1235 js["Error"] = "Failed to save EPG filter";
1239 logger->debug("Found and deleted: {} {}", c, p);
1240 js["Result"] = true;
1245 js["Result"] = false;
1246 js["Error"] = "Channel/Programme not found";
1249 catch (const std::exception& e)
1251 js["Result"] = false;
1252 js["Error"] = "Unknown error";
1257 bool VDRClient::epgfilterget(PFMap& postFields, Json::Value& js) // RETHROWS
1259 logger->debug("epgFilterget");
1261 libconfig::Config epgFilter;
1262 if (!loadEpgFilter(epgFilter))
1264 js["Result"] = false;
1265 js["Error"] = "Error initialising EPG filter";
1271 libconfig::Setting& setting = epgFilter.lookup("filters");
1272 int numFilters = setting.getLength();
1274 Json::Value jsfilters(Json::arrayValue);
1275 for (x = 0; x < numFilters; x++)
1277 libconfig::Setting& pair = setting[x];
1280 if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1282 js["Result"] = false;
1283 js["Error"] = "Filter file format error";
1286 Json::Value oneFilter;
1289 jsfilters.append(oneFilter);
1291 js["EPGFilters"] = jsfilters;
1293 catch (const std::exception& e)
1295 js["Result"] = false;
1296 js["Error"] = "Unknown error";
1300 js["Result"] = true;
1304 //////////////////////////////////////////////////////////////////////////////////////////////////
1306 const cEvent* VDRClient::getEvent(const cChannels* Channels, const cSchedules* Schedules,
1307 Json::Value& js, int channelNumber, int eventID, int aroundTime)
1309 const cChannel* channel = NULL;
1311 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
1313 if (channel->GroupSep()) continue;
1314 if (channel->Number() == channelNumber) break;
1319 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1320 js["Error"] = "Could not find channel";
1324 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
1327 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1328 js["Error"] = "Internal schedules error (2)";
1332 const cEvent* event = NULL;
1334 event = Schedule->GetEvent(eventID);
1336 event = Schedule->GetEventAround(aroundTime);
1340 logger->error("getevent: Could not find requested event: {}", eventID);
1341 js["Error"] = "Internal schedules error (3)";
1348 cTimer* VDRClient::findTimer(cTimers* Timers,
1349 const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1351 int numTimers = Timers->Count();
1353 for (int i = 0; i < numTimers; i++)
1355 timer = Timers->Get(i);
1357 logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1358 logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1359 logger->debug("findtimer: {}", rChannelID);
1360 logger->debug("findtimer: {}", timer->File());
1361 logger->debug("findtimer: {}", rName);
1362 logger->debug("findtimer: {}", timer->StartTime());
1363 logger->debug("findtimer: {}", rStartTime);
1364 logger->debug("findtimer: {}", timer->StopTime());
1365 logger->debug("findtimer: {}", rStopTime);
1366 logger->debug("findtimer: {}", timer->WeekDays());
1367 logger->debug("findtimer: {}", rWeekDays);
1370 (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1371 && (strcmp(timer->File(), rName) == 0)
1372 && (timer->StartTime() == atoi(rStartTime))
1373 && (timer->StopTime() == atoi(rStopTime))
1374 && (timer->WeekDays() == atoi(rWeekDays))
1377 logger->debug("findtimer: found");
1381 logger->debug("findtimer: no timer found");
1385 // Differs only from above by taking const Timers and returning const cTimer
1386 const cTimer* VDRClient::findTimer(const cTimers* Timers,
1387 const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1389 int numTimers = Timers->Count();
1390 const cTimer* timer;
1391 for (int i = 0; i < numTimers; i++)
1393 timer = Timers->Get(i);
1395 logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1396 logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1397 logger->debug("findtimer: {}", rChannelID);
1398 logger->debug("findtimer: {}", timer->File());
1399 logger->debug("findtimer: {}", rName);
1400 logger->debug("findtimer: {}", timer->StartTime());
1401 logger->debug("findtimer: {}", rStartTime);
1402 logger->debug("findtimer: {}", timer->StopTime());
1403 logger->debug("findtimer: {}", rStopTime);
1404 logger->debug("findtimer: {}", timer->WeekDays());
1405 logger->debug("findtimer: {}", rWeekDays);
1408 (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1409 && (strcmp(timer->File(), rName) == 0)
1410 && (timer->StartTime() == atoi(rStartTime))
1411 && (timer->StopTime() == atoi(rStopTime))
1412 && (timer->WeekDays() == atoi(rWeekDays))
1415 logger->debug("findtimer: found");
1419 logger->debug("findtimer: no timer found");
1423 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)
1425 int numTimers = Timers->Count();
1427 logger->debug("findtimer2: {} {} {} {} {} {} {} {} {}", rName, rActive, rChannelID, rDay, rWeekDays, rStartTime, rStopTime, rPriority, rLifetime);
1428 for (int i = 0; i < numTimers; i++)
1430 timer = Timers->Get(i);
1432 logger->debug("findtimer2: search: {} {} {} {} {} {} {} {} {}", timer->File(), timer->HasFlags(tfActive), (const char*)timer->Channel()->GetChannelID().ToString(),
1433 (int)timer->Day(), timer->WeekDays(), (int)timer->StartTime(), (int)timer->StopTime(),
1434 timer->Priority(), timer->Lifetime());
1436 if ( (strcmp(timer->File(), rName) == 0)
1437 && (timer->HasFlags(tfActive) == !(strcasecmp(rActive, "true")))
1438 && (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1439 && (timer->Day() == atoi(rDay))
1440 && (timer->WeekDays() == atoi(rWeekDays))
1441 && (timer->StartTime() == atoi(rStartTime))
1442 && (timer->StopTime() == atoi(rStopTime))
1443 && (timer->Priority() == atoi(rPriority))
1444 && (timer->Lifetime() == atoi(rLifetime))
1447 logger->debug("findtimer2: found");
1451 logger->debug("findtimer2: no timer found");
1455 int VDRClient::getVarInt(PFMap& postFields, const char* paramName) // THROWS
1457 auto i = postFields.find(paramName);
1458 if (i == postFields.end()) throw BadParamException(paramName);
1460 try { r = std::stoi(i->second); }
1461 catch (const std::exception& e) { throw BadParamException(paramName); }
1465 std::string VDRClient::getVarString(PFMap& postFields, const char* paramName, bool emptyOk) // THROWS
1467 auto i = postFields.find(paramName);
1468 if (i == postFields.end()) throw BadParamException(paramName);
1469 if (!emptyOk && i->second.empty()) throw BadParamException(paramName);
1473 bool VDRClient::loadEpgFilter(libconfig::Config& epgFilter)
1475 std::string epgFilterFile(configDir + std::string("/epgfilter.conf"));
1476 FILE* fp = fopen(epgFilterFile.c_str(), "a");
1479 logger->error("loadEpgFilter: Error: Failed to fopen epgfilter.conf");
1486 epgFilter.readFile(epgFilterFile.c_str());
1488 catch (const libconfig::FileIOException &fioex)
1490 logger->error("loadEpgFilter: Error: Failed to read filter file");
1493 catch(const libconfig::ParseException &pex)
1495 logger->error("loadEpgFilter: Parse error at {}: {} - {}", pex.getFile(), pex.getLine(), pex.getError());
1501 libconfig::Setting& setting = epgFilter.lookup("filters");
1503 if (!setting.isList())
1505 logger->error("loadEpgFilter: Error: Failed to read filter file (2)");
1509 catch (const libconfig::SettingNotFoundException& e)
1511 libconfig::Setting& setting = epgFilter.getRoot();
1512 setting.add("filters", libconfig::Setting::Type::TypeList);
1518 bool VDRClient::saveEpgFilter(libconfig::Config& epgFilter)
1520 std::string epgFilterFile(configDir + std::string("/epgfilter.conf"));
1523 epgFilter.writeFile(epgFilterFile.c_str());
1524 logger->debug("saveEpgFilter: EPG filter saved");
1527 catch (const libconfig::FileIOException& e)
1529 logger->error("saveEpgFilter: Error: File write error");