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");
35 VDRClient::~VDRClient()
37 logger->debug("VDRClient destructor");
40 bool VDRClient::process(std::string& request, PFMap& postFields, std::string& returnString)
42 Json::Value returnJSON;
47 if (request == "gettime") success = gettime(postFields, returnJSON);
48 else if (request == "diskstats") success = diskstats(postFields, returnJSON);
49 else if (request == "channellist") success = channellist(postFields, returnJSON);
50 else if (request == "reclist") success = reclist(postFields, returnJSON);
51 else if (request == "timerlist") success = timerlist(postFields, returnJSON);
52 else if (request == "epgdownload") success = epgdownload(postFields, returnJSON);
53 else if (request == "tunersstatus") success = tunersstatus(postFields, returnJSON);
54 else if (request == "epgfilterget") success = epgfilterget(postFields, returnJSON);
56 else if (request == "channelschedule") success = channelschedule(postFields, returnJSON);
57 else if (request == "getscheduleevent") success = getscheduleevent(postFields, returnJSON);
58 else if (request == "epgsearch") success = epgsearch(postFields, returnJSON);
59 else if (request == "epgsearchsame") success = epgsearchsame(postFields, returnJSON);
60 else if (request == "epgsearchotherhalf")success = epgsearchotherhalf(postFields, returnJSON);
61 else if (request == "timerset") success = timerset(postFields, returnJSON);
62 else if (request == "recinfo") success = recinfo(postFields, returnJSON);
63 else if (request == "recstop") success = recstop(postFields, returnJSON);
64 else if (request == "recdel") success = recdel(postFields, returnJSON);
65 else if (request == "recrename") success = recrename(postFields, returnJSON);
66 else if (request == "recmove") success = recmove(postFields, returnJSON);
67 else if (request == "timersetactive") success = timersetactive(postFields, returnJSON);
68 else if (request == "timerdel") success = timerdel(postFields, returnJSON);
69 else if (request == "timerisrecording") success = timerisrecording(postFields, returnJSON);
70 else if (request == "recresetresume") success = recresetresume(postFields, returnJSON);
71 else if (request == "timeredit") success = timeredit(postFields, returnJSON);
72 else if (request == "epgfilteradd") success = epgfilteradd(postFields, returnJSON);
73 else if (request == "epgfilterdel") success = epgfilterdel(postFields, returnJSON);
75 catch (const BadParamException& e)
77 logger->error("Bad parameter in call, paramName: {}", e.param);
78 returnJSON["Result"] = false;
79 returnJSON["Error"] = "Bad request parameter";
80 returnJSON["Detail"] = e.param;
84 if (!success) return false;
86 Json::StyledWriter sw;
87 returnString = sw.write(returnJSON);
88 logger->debug("Done sw write");
92 bool VDRClient::gettime(PFMap& postFields, Json::Value& js)
94 logger->debug("get_time");
97 gettimeofday(&tv, NULL);
99 js["Time"] = (Json::UInt64)tv.tv_sec;
100 js["MTime"] = (Json::UInt)(tv.tv_usec/1000);
105 bool VDRClient::diskstats(PFMap& postFields, Json::Value& js)
107 logger->debug("diskstats");
111 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
113 js["FreeMiB"] = FreeMB;
114 js["UsedMiB"] = UsedMB;
115 js["Percent"] = Percent;
120 bool VDRClient::channellist(PFMap& postFields, Json::Value& js)
122 logger->debug("channellist");
124 Json::Value jschannels(Json::arrayValue);
128 for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel))
130 if (!channel->GroupSep())
132 Json::Value oneChannel;
133 oneChannel["ID"] = (const char *)channel->GetChannelID().ToString();
134 oneChannel["Number"] = channel->Number();
135 oneChannel["Name"] = channel->Name();
136 jschannels.append(oneChannel);
139 js["Channels"] = jschannels;
144 bool VDRClient::reclist(PFMap& postFields, Json::Value& js)
146 logger->debug("reclist");
148 Json::Value jsrecordings(Json::arrayValue);
150 LOCK_RECORDINGS_READ;
152 for (const cRecording *recording = Recordings->First(); recording; recording = Recordings->Next(recording))
155 oneRec["StartTime"] = (Json::UInt)recording->Start();
156 oneRec["Length"] = (Json::UInt)recording->LengthInSeconds();
157 oneRec["IsNew"] = recording->IsNew();
158 oneRec["Name"] = recording->Name();
159 oneRec["Filename"] = recording->FileName();
160 oneRec["FileSizeMB"] = recording->FileSizeMB();
162 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
163 if (rc) oneRec["CurrentlyRecording"] = true;
164 else oneRec["CurrentlyRecording"] = false;
166 jsrecordings.append(oneRec);
168 js["Recordings"] = jsrecordings;
173 bool VDRClient::timerlist(PFMap& postFields, Json::Value& js)
175 logger->debug("timerlist");
177 Json::Value jstimers(Json::arrayValue);
185 int numTimers = Timers->Count();
187 for (int i = 0; i < numTimers; i++)
189 timer = Timers->Get(i);
190 Json::Value oneTimer;
191 oneTimer["Active"] = timer->HasFlags(tfActive);
192 oneTimer["Recording"] = timer->Recording();
193 oneTimer["Pending"] = timer->Pending();
194 oneTimer["Priority"] = timer->Priority();
195 oneTimer["Lifetime"] = timer->Lifetime();
196 oneTimer["ChannelNumber"] = timer->Channel()->Number();
197 oneTimer["ChannelID"] = (const char *)timer->Channel()->GetChannelID().ToString();
198 oneTimer["StartTime"] = (int)timer->StartTime();
199 oneTimer["StopTime"] = (int)timer->StopTime();
200 oneTimer["Day"] = (int)timer->Day();
201 oneTimer["WeekDays"] = timer->WeekDays();
202 oneTimer["Name"] = timer->File();
204 const cEvent* event = timer->Event();
207 oneTimer["EventID"] = event->EventID();
211 int channelNumber = timer->Channel()->Number();
212 int aroundTime = timer->StartTime() + 1;
213 const cEvent* eventAround = getEvent(Channels, Schedules, js, channelNumber, 0, aroundTime);
216 oneTimer["EventID"] = eventAround->EventID();
220 oneTimer["EventID"] = 0;
224 jstimers.append(oneTimer);
227 js["Timers"] = jstimers;
228 js["NumTuners"] = cDevice::NumDevices();
233 bool VDRClient::channelschedule(PFMap& postFields, Json::Value& js) // RETHROWS
235 logger->debug("channelschedule");
236 int channelNumber = getVarInt(postFields, "channelnumber");
237 int startTime = getVarInt(postFields, "starttime");
238 int duration = getVarInt(postFields, "duration");
240 Json::Value jsevents(Json::arrayValue);
244 const cChannel* channel = NULL;
245 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
247 if (channel->GroupSep()) continue;
248 if (channel->Number() == channelNumber) break;
253 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
254 js["Result"] = false;
255 js["Error"] = "Could not find channel";
261 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
264 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
265 js["Result"] = false;
266 js["Error"] = "Internal schedules error (2)";
270 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
272 if ((event->StartTime() + event->Duration()) < time(NULL)) continue; //in the past filter
273 if ((event->StartTime() + event->Duration()) <= startTime) continue; //start time filter
274 if (event->StartTime() >= (startTime + duration)) continue; //duration filter
276 Json::Value oneEvent;
277 oneEvent["ID"] = event->EventID();
278 oneEvent["Time"] = (Json::UInt)event->StartTime();
279 oneEvent["Duration"] = event->Duration();
280 oneEvent["Title"] = event->Title() ? event->Title() : "";
281 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
282 oneEvent["HasTimer"] = event->HasTimer();
283 jsevents.append(oneEvent);
287 js["Events"] = jsevents;
291 bool VDRClient::getscheduleevent(PFMap& postFields, Json::Value& js) // RETHROWS
293 logger->debug("getscheduleevent");
295 int channelNumber = getVarInt(postFields, "channelnumber");
296 int eventID = getVarInt(postFields, "eventid");
301 const cEvent* event = getEvent(Channels, Schedules, js, channelNumber, eventID, 0);
304 js["Result"] = false;
308 Json::Value oneEvent;
309 oneEvent["ID"] = event->EventID();
310 oneEvent["Time"] = (Json::UInt)event->StartTime();
311 oneEvent["Duration"] = event->Duration();
312 oneEvent["Title"] = event->Title() ? event->Title() : "";
313 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
314 oneEvent["Description"] = event->Description() ? event->Description() : "";
315 oneEvent["HasTimer"] = event->HasTimer();
316 oneEvent["RunningStatus"] = event->RunningStatus();
319 js["Event"] = oneEvent;
323 bool VDRClient::epgdownload(PFMap& postFields, Json::Value& js)
325 logger->debug("epgdownload");
326 Json::Value jsevents(Json::arrayValue);
331 for (const cChannel* channel = Channels->First(); channel; channel = Channels->Next(channel))
333 if (channel->GroupSep()) continue;
335 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
336 if (!Schedule) continue;
338 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
340 Json::Value oneEvent;
341 oneEvent["ChannelNumber"] = channel->Number();
342 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
343 oneEvent["ID"] = event->EventID();
344 oneEvent["Time"] = (Json::UInt)event->StartTime();
345 oneEvent["Duration"] = event->Duration();
346 oneEvent["Title"] = event->Title() ? event->Title() : "";
347 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
348 oneEvent["HasTimer"] = event->HasTimer();
349 oneEvent["Description"] = event->Description() ? event->Description() : "";
350 jsevents.append(oneEvent);
355 js["Events"] = jsevents;
359 bool VDRClient::epgsearch(PFMap& postFields, Json::Value& js) // RETHROWS
361 logger->debug("epgsearch");
363 std::string searchfor = getVarString(postFields, "searchfor");
364 logger->debug("epgsearch: search for: {}", searchfor);
366 Json::Value jsevents(Json::arrayValue);
371 for (const cChannel* channel = Channels->First(); channel; channel = Channels->Next(channel))
373 if (channel->GroupSep()) continue;
375 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
376 if (!Schedule) continue;
379 bool founddescription;
380 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
383 founddescription = false;
385 if (event->Title() && strcasestr(event->Title(), searchfor.c_str())) foundtitle = true;
387 if (!foundtitle && event->Description())
388 if (strcasestr(event->Description(), searchfor.c_str())) founddescription = true;
390 if (foundtitle || founddescription)
392 Json::Value oneEvent;
393 oneEvent["ChannelNumber"] = channel->Number();
394 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
395 oneEvent["ID"] = event->EventID();
396 oneEvent["Time"] = (Json::UInt)event->StartTime();
397 oneEvent["Duration"] = event->Duration();
398 oneEvent["Title"] = event->Title() ? event->Title() : "";
399 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
400 oneEvent["HasTimer"] = event->HasTimer();
401 oneEvent["Description"] = event->Description() ? event->Description() : "";
402 if (founddescription)
403 oneEvent["FoundInDesc"] = true;
405 oneEvent["FoundInDesc"] = false;
406 jsevents.append(oneEvent);
412 js["Events"] = jsevents;
414 logger->debug("epgsearch: search for: {} done", searchfor);
419 bool VDRClient::epgsearchsame(PFMap& postFields, Json::Value& js) // RETHROWS
421 logger->debug("epgsearchsame");
423 int atTime = getVarInt(postFields, "time");
424 std::string sTitle = getVarString(postFields, "title");
426 logger->debug("epgsearchsame: request time: {}, title: {}", atTime, sTitle);
428 Json::Value jsevents(Json::arrayValue);
433 for (const cSchedule *schedule = Schedules->First(); (schedule != NULL); schedule = Schedules->Next(schedule))
435 event = schedule->GetEventAround(atTime);
436 if (!event) continue; // nothing found on this schedule(channel)
438 if (!strcmp(event->Title(), sTitle.c_str()))
440 Json::Value oneEvent;
441 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
442 oneEvent["ID"] = event->EventID();
443 oneEvent["Time"] = (Json::UInt)event->StartTime();
444 oneEvent["Duration"] = event->Duration();
445 oneEvent["Title"] = event->Title() ? event->Title() : "";
446 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
447 oneEvent["HasTimer"] = event->HasTimer();
448 //oneEvent["Description"] = event->Description() ? event->Description() : "";
449 jsevents.append(oneEvent);
453 js["Events"] = jsevents;
458 bool VDRClient::epgsearchotherhalf(PFMap& postFields, Json::Value& js) // RETHROWS
460 logger->debug("epgsearchotherhalf");
461 int channelNumber = getVarInt(postFields, "channelnumber");
462 int eventID = getVarInt(postFields, "eventid");
463 const cChannel* channel = NULL;
467 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
469 if (channel->GroupSep()) continue;
470 if (channel->Number() == channelNumber) break;
475 logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
476 js["Result"] = false;
477 js["Error"] = "Could not find channel";
483 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
486 logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
487 js["Result"] = false;
488 js["Error"] = "Internal schedules error (2)";
492 js["OtherEventFound"] = false;
494 const cEvent* eventM1 = NULL;
495 const cEvent* eventM2 = NULL;
496 const cEvent* otherEvent = NULL;
498 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
500 if (event->EventID() == (unsigned long)eventID)
502 if ((eventM2 != NULL) && (!strcmp(eventM2->Title(), event->Title())))
504 otherEvent = eventM2;
508 const cEvent* eventP1 = Schedule->Events()->Next(event);
511 const cEvent* eventP2 = Schedule->Events()->Next(eventP1);
513 if (eventP2 && (!strcmp(eventP2->Title(), event->Title())))
515 otherEvent = eventP2;
522 Json::Value oneEvent;
523 oneEvent["ID"] = otherEvent->EventID();
524 oneEvent["ChannelNumber"] = channel->Number();
525 oneEvent["Time"] = (Json::UInt)otherEvent->StartTime();
526 oneEvent["Duration"] = otherEvent->Duration();
527 oneEvent["Title"] = otherEvent->Title() ? otherEvent->Title() : "";
528 oneEvent["HasTimer"] = otherEvent->HasTimer();
529 js["Event"] = oneEvent;
530 js["OtherEventFound"] = true;
544 bool VDRClient::tunersstatus(PFMap& postFields, Json::Value& js)
546 logger->debug("tunerstatus");
548 js["NumDevices"] = cDevice::NumDevices();
550 Json::Value jsdevices(Json::arrayValue);
552 for (int i = 0; i < cDevice::NumDevices(); i++)
554 Json::Value oneDevice;
555 cDevice *d = cDevice::GetDevice(i);
556 oneDevice["Number"] = d->DeviceNumber();
557 oneDevice["Type"] = (const char*)d->DeviceType();
558 oneDevice["Name"] = (const char*)d->DeviceName();
559 oneDevice["IsPrimary"] = d->IsPrimaryDevice();
561 const cChannel* cchannel = d->GetCurrentlyTunedTransponder();
564 oneDevice["Frequency"] = cchannel->Frequency();
565 oneDevice["SignalStrength"] = d->SignalStrength();
566 oneDevice["SignalQuality"] = d->SignalQuality();
571 oneDevice["Frequency"] = 0;
572 oneDevice["SignalStrength"] = 0;
573 oneDevice["SignalQuality"] = 0;
576 jsdevices.append(oneDevice);
579 js["Devices"] = jsdevices;
582 Json::Value jstimers(Json::arrayValue);
585 LOCK_CHANNELS_READ; // Because this calls timer->Channel() .. necessary?
587 int numTimers = Timers->Count();
589 for (int i = 0; i < numTimers; i++)
591 timer = Timers->Get(i);
593 if (timer->Recording())
595 Json::Value oneTimer;
596 oneTimer["Recording"] = timer->Recording();
597 oneTimer["StartTime"] = (int)timer->StartTime();
598 oneTimer["StopTime"] = (int)timer->StopTime();
599 oneTimer["File"] = timer->File();
601 cRecordControl* crc = cRecordControls::GetRecordControl(timer);
604 cDevice* crcd = crc->Device();
605 oneTimer["DeviceNumber"] = crcd->DeviceNumber();
609 oneTimer["DeviceNumber"] = Json::Value::null;
612 const cChannel* channel = timer->Channel();
615 oneTimer["ChannelName"] = channel->Name();
619 oneTimer["ChannelName"] = Json::Value::null;
622 jstimers.append(oneTimer);
626 js["CurrentRecordings"] = jstimers;
633 bool VDRClient::timerset(PFMap& postFields, Json::Value& js) // RETHROWS
635 logger->debug("timerset");
637 std::string sTimerString = getVarString(postFields, "timerstring");
639 logger->debug("timerset: '{}'", sTimerString);
640 cTimer *timer = new cTimer;
641 if (!timer->Parse(sTimerString.c_str()))
644 js["Result"] = false;
645 js["Error"] = "Failed to parse timer request details";
650 Timers->SetExplicitModify();
652 cTimer *t = Timers->GetTimer(timer);
656 js["Result"] = false;
657 js["Error"] = "Timer already exists";
662 Timers->SetModified();
668 bool VDRClient::recinfo(PFMap& postFields, Json::Value& js) // RETHROWS
670 logger->debug("recinfo");
672 std::string reqfilename = getVarString(postFields, "filename");
673 logger->debug("recinfo: {}", reqfilename);
675 LOCK_RECORDINGS_READ;
677 const cRecording *recording = Recordings->GetByName(reqfilename.c_str());
681 logger->error("recinfo: recinfo found no recording");
682 js["Result"] = false;
686 js["IsNew"] = recording->IsNew();
687 js["LengthInSeconds"] = recording->LengthInSeconds();
688 js["FileSizeMB"] = recording->FileSizeMB();
689 js["Name"] = recording->Name() ? recording->Name() : Json::Value::null;
690 js["Priority"] = recording->Priority();
691 js["LifeTime"] = recording->Lifetime();
692 js["Start"] = (Json::UInt)recording->Start();
694 js["CurrentlyRecordingStart"] = 0;
695 js["CurrentlyRecordingStop"] = 0;
696 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
699 js["CurrentlyRecordingStart"] = (Json::UInt)rc->Timer()->StartTime();
700 js["CurrentlyRecordingStop"] = (Json::UInt)rc->Timer()->StopTime();
703 js["ResumePoint"] = 0;
705 const cRecordingInfo *info = recording->Info();
708 js["ChannelName"] = info->ChannelName() ? info->ChannelName() : Json::Value::null;
709 js["Title"] = info->Title() ? info->Title() : Json::Value::null;
710 js["ShortText"] = info->ShortText() ? info->ShortText() : Json::Value::null;
711 js["Description"] = info->Description() ? info->Description() : Json::Value::null;
713 const cComponents* components = info->Components();
716 js["Components"] = Json::Value::null;
720 Json::Value jscomponents;
722 tComponent* component;
723 for (int i = 0; i < components->NumComponents(); i++)
725 component = components->Component(i);
727 Json::Value oneComponent;
728 oneComponent["Stream"] = component->stream;
729 oneComponent["Type"] = component->type;
730 oneComponent["Language"] = component->language ? component->language : Json::Value::null;
731 oneComponent["Description"] = component->description ? component->description : Json::Value::null;
732 jscomponents.append(oneComponent);
735 js["Components"] = jscomponents;
738 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
739 if (ResumeFile.Read() >= 0) js["ResumePoint"] = floor(ResumeFile.Read() / info->FramesPerSecond());
746 bool VDRClient::recstop(PFMap& postFields, Json::Value& js) // RETHROWS
748 logger->debug("recstop");
750 std::string reqfilename = getVarString(postFields, "filename");
751 logger->debug("recstop: {}", reqfilename);
754 LOCK_RECORDINGS_WRITE; // May not need write here, but to be safe..
756 cRecording *recording = Recordings->GetByName(reqfilename.c_str());
760 logger->error("recstop: recstop found no recording");
761 js["Result"] = false;
765 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
768 logger->error("recstop: not currently recording");
769 js["Result"] = false;
773 cTimer* timer = rc->Timer();
776 logger->error("recstop: timer not found");
777 js["Result"] = false;
781 timer->ClrFlags(tfActive);
788 bool VDRClient::recdel(PFMap& postFields, Json::Value& js) // RETHROWS
790 logger->debug("recdel");
792 std::string reqfilename = getVarString(postFields, "filename");
793 logger->debug("recdel: {}", reqfilename);
795 LOCK_RECORDINGS_WRITE;
797 cRecording *recording = Recordings->GetByName(reqfilename.c_str());
801 js["Result"] = false;
802 js["Error"] = "Could not find recording to delete";
806 logger->debug("recdel: Deleting recording: {}", recording->Name());
807 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
810 js["Result"] = false;
811 js["Error"] = "This recording is still recording.. ho ho";
815 if (recording->Delete())
817 Recordings->DelByName(recording->FileName());
822 js["Result"] = false;
823 js["Error"] = "Failed to delete recording";
829 bool VDRClient::recrename(PFMap& postFields, Json::Value& js) // RETHROWS
831 logger->debug("recrename");
833 std::string fileNameToAffect = getVarString(postFields, "filename");
834 std::string requestedNewName = getVarString(postFields, "newname");
838 LOCK_RECORDINGS_WRITE;
839 cRecording* recordingObj = Recordings->GetByName(fileNameToAffect.c_str());
840 if (!recordingObj) throw 2;
842 std::string newName(recordingObj->Folder());
843 logger->debug("newname after folder copy: #{}#");
844 if (newName.length()) newName += FOLDERDELIMCHAR;
845 newName += requestedNewName;
847 logger->debug("RM2 NEWLOC: #{}#", newName);
849 bool moveSuccess = recordingObj->ChangeName(newName.c_str());
851 js["Result"] = moveSuccess;
852 js["NewRecordingFileName"] = recordingObj->FileName();
854 catch (BadParamException& e)
856 js["Result"] = false;
857 logger->error("recmove: Bad parameters");
858 js["Error"] = "Bad request parameters";
862 js["Result"] = false;
865 logger->error("recmove: Could not find recording to move");
866 js["Error"] = "Bad filename";
873 bool VDRClient::recmove(PFMap& postFields, Json::Value& js) // RETHROWS
875 logger->debug("recmove");
877 std::string fileNameToAffect = getVarString(postFields, "filename");
878 std::string requestedNewDirName = getVarString(postFields, "newpath", true /* empty new path is ok */);
882 LOCK_RECORDINGS_WRITE;
883 cRecording* recordingObj = Recordings->GetByName(fileNameToAffect.c_str());
884 if (!recordingObj) throw 2;
887 if (requestedNewDirName.length()) newName = requestedNewDirName + FOLDERDELIMCHAR;
888 newName += recordingObj->BaseName();
890 logger->debug("RM2 NEWLOC: #{}#", newName);
892 bool moveSuccess = recordingObj->ChangeName(newName.c_str());
894 js["Result"] = moveSuccess;
895 js["NewRecordingFileName"] = recordingObj->FileName();
897 catch (BadParamException& e)
899 js["Result"] = false;
900 logger->error("recmove: Bad parameters");
901 js["Error"] = "Bad request parameters";
905 js["Result"] = false;
908 logger->error("recmove: Could not find recording to move");
909 js["Error"] = "Bad filename";
916 bool VDRClient::timersetactive(PFMap& postFields, Json::Value& js) // RETHROWS
918 logger->debug("timersetactive");
920 std::string rChannelID = getVarString(postFields, "ChannelID");
921 std::string rName = getVarString(postFields, "Name");
922 std::string rStartTime = getVarString(postFields, "StartTime");
923 std::string rStopTime = getVarString(postFields, "StopTime");
924 std::string rWeekDays = getVarString(postFields, "WeekDays");
925 std::string tNewActive = getVarString(postFields, "SetActive");
927 logger->debug("timersetactive: {} {}:{}:{}:{}:{}", tNewActive, rChannelID, rName, rStartTime, rStopTime, rWeekDays);
929 if ((tNewActive != "true") && (tNewActive != "false"))
931 js["Result"] = false;
932 js["Error"] = "Bad request parameters";
938 Timers->SetExplicitModify();
940 cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
943 if (tNewActive == "true") timer->SetFlags(tfActive);
944 else timer->ClrFlags(tfActive);
947 Timers->SetModified();
951 js["Result"] = false;
952 js["Error"] = "Timer not found";
956 bool VDRClient::timerdel(PFMap& postFields, Json::Value& js) // RETHROWS
958 logger->debug("timerdel");
960 std::string rChannelID = getVarString(postFields, "ChannelID");
961 std::string rName = getVarString(postFields, "Name");
962 std::string rStartTime = getVarString(postFields, "StartTime");
963 std::string rStopTime = getVarString(postFields, "StopTime");
964 std::string rWeekDays = getVarString(postFields, "WeekDays");
966 logger->debug("timerdel: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
969 Timers->SetExplicitModify();
971 cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
974 if (timer->Recording())
976 logger->debug("timerdel: Unable to delete timer - timer is running");
977 js["Result"] = false;
978 js["Error"] = "Timer is running";
983 Timers->SetModified();
988 js["Result"] = false;
989 js["Error"] = "Timer not found";
993 bool VDRClient::timerisrecording(PFMap& postFields, Json::Value& js) // RETHROWS
995 logger->debug("timerisrecording");
997 std::string rChannelID = getVarString(postFields, "ChannelID");
998 std::string rName = getVarString(postFields, "Name");
999 std::string rStartTime = getVarString(postFields, "StartTime");
1000 std::string rStopTime = getVarString(postFields, "StopTime");
1001 std::string rWeekDays = getVarString(postFields, "WeekDays");
1003 logger->debug("timerisrecording: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1007 const cTimer* timer = findTimer(Timers, rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1010 js["Recording"] = timer->Recording();
1011 js["Pending"] = timer->Pending();
1012 js["Result"] = true;
1016 js["Result"] = false;
1017 js["Error"] = "Timer not found";
1021 bool VDRClient::recresetresume(PFMap& postFields, Json::Value& js) // RETHROWS
1023 logger->debug("recresetresume");
1025 std::string reqfilename = getVarString(postFields, "filename");
1026 logger->debug("recresetresume: {}", reqfilename);
1030 const cRecordings* Recordings = cRecordings::GetRecordingsRead(StateKey);
1032 const cRecording* recording = Recordings->GetByName(reqfilename.c_str());
1038 js["Result"] = false;
1039 js["Error"] = "Could not find recording to reset resume";
1043 logger->debug("recresetresume: Reset resume for: {}", recording->Name());
1045 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
1049 if (ResumeFile.Read() >= 0)
1051 ResumeFile.Delete();
1052 js["Result"] = true;
1057 js["Result"] = false;
1058 js["Error"] = "Recording has no resume point";
1063 bool VDRClient::timeredit(PFMap& postFields, Json::Value& js) // RETHROWS
1065 logger->debug("timeredit");
1067 std::string oldName = getVarString(postFields, "OldName");
1068 std::string oldActive = getVarString(postFields, "OldActive");
1069 std::string oldChannelID = getVarString(postFields, "OldChannelID");
1070 std::string oldDay = getVarString(postFields, "OldDay");
1071 std::string oldWeekDays = getVarString(postFields, "OldWeekDays");
1072 std::string oldStartTime = getVarString(postFields, "OldStartTime");
1073 std::string oldStopTime = getVarString(postFields, "OldStopTime");
1074 std::string oldPriority = getVarString(postFields, "OldPriority");
1075 std::string oldLifetime = getVarString(postFields, "OldLifetime");
1077 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", oldName, oldActive, oldChannelID, oldDay, oldWeekDays, oldStartTime, oldStopTime, oldPriority, oldLifetime);
1079 std::string newName = getVarString(postFields, "NewName");
1080 std::string newActive = getVarString(postFields, "NewActive");
1081 std::string newChannelID = getVarString(postFields, "NewChannelID");
1082 std::string newDay = getVarString(postFields, "NewDay");
1083 std::string newWeekDays = getVarString(postFields, "NewWeekDays");
1084 std::string newStartTime = getVarString(postFields, "NewStartTime");
1085 std::string newStopTime = getVarString(postFields, "NewStopTime");
1086 std::string newPriority = getVarString(postFields, "NewPriority");
1087 std::string newLifetime = getVarString(postFields, "NewLifetime");
1089 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", newName, newActive, newChannelID, newDay, newWeekDays, newStartTime, newStopTime, newPriority, newLifetime);
1092 Timers->SetExplicitModify();
1094 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());
1097 js["Result"] = false;
1098 js["Error"] = "Timer not found";
1102 Timers->SetModified();
1104 // Old version commented below (now removed) used to set each thing individually based on whether it had changed. However, since
1105 // the only way to change the timer channel appears to be with the cTimer::Parse function, might as well use that
1106 // for everything it supports
1107 // Except flags. Get current flags, set using Parse, then add/remove active as needed the other way.
1109 time_t nstt = std::stoi(newStartTime);
1111 localtime_r(&nstt, &nstm);
1112 int nssf = (nstm.tm_hour * 100) + nstm.tm_min;
1114 time_t nztt = std::stoi(newStopTime);
1116 localtime_r(&nztt, &nztm);
1117 int nzsf = (nztm.tm_hour * 100) + nztm.tm_min;
1119 std::replace(newName.begin(), newName.end(), ':', '|');
1121 // ? Convert to std::string?
1122 cString parseBuffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s",
1123 timer->Flags(), newChannelID.c_str(), *(cTimer::PrintDay(std::stoi(newDay), std::stoi(newWeekDays), true)),
1124 nssf, nzsf, std::stoi(newPriority), std::stoi(newLifetime), newName.c_str(), timer->Aux() ? timer->Aux() : "");
1126 logger->debug("timeredit: new parse: {}", *parseBuffer);
1128 bool parseResult = timer->Parse(*parseBuffer);
1131 js["Result"] = false;
1132 js["Error"] = "Timer parsing failed";
1136 if (timer->HasFlags(tfActive) != !(strcasecmp(newActive.c_str(), "true")))
1138 logger->debug("timeredit: {} {} set new active: {}", timer->HasFlags(tfActive), !(strcasecmp(newActive.c_str(), "true")), newActive);
1140 if (strcasecmp(newActive.c_str(), "true") == 0)
1142 timer->SetFlags(tfActive);
1144 else if (strcasecmp(newActive.c_str(), "false") == 0)
1146 timer->ClrFlags(tfActive);
1150 js["Result"] = false;
1151 js["Error"] = "Bad request parameters";
1156 js["Result"] = true;
1160 bool VDRClient::epgfilteradd(PFMap& postFields, Json::Value& js) // RETHROWS
1162 std::string channel = getVarString(postFields, "channel");
1163 std::string programme = getVarString(postFields, "programme");
1165 logger->debug("epgFilterAdd: {} {}", channel, programme);
1167 libconfig::Config epgFilter;
1168 if (!loadEpgFilter(epgFilter))
1170 js["Result"] = false;
1171 js["Error"] = "Error initialising EPG filter";
1175 libconfig::Setting& setting = epgFilter.lookup("filters");
1176 libconfig::Setting& newPair = setting.add(libconfig::Setting::Type::TypeGroup);
1177 libconfig::Setting& newChannel = newPair.add("c", libconfig::Setting::Type::TypeString);
1178 newChannel = channel;
1179 libconfig::Setting& newProgramme = newPair.add("p", libconfig::Setting::Type::TypeString);
1180 newProgramme = programme;
1182 if (!saveEpgFilter(epgFilter))
1184 js["Result"] = false;
1185 js["Error"] = "Failed to save EPG filter";
1189 js["Result"] = true;
1193 bool VDRClient::epgfilterdel(PFMap& postFields, Json::Value& js) // RETHROWS
1195 std::string channel = getVarString(postFields, "channel");
1196 std::string programme = getVarString(postFields, "programme");
1198 logger->debug("epgFilterDel: {} {}", channel, programme);
1200 libconfig::Config epgFilter;
1201 if (!loadEpgFilter(epgFilter))
1203 js["Result"] = false;
1204 js["Error"] = "Error initialising EPG filter";
1210 libconfig::Setting& setting = epgFilter.lookup("filters");
1211 int numFilters = setting.getLength();
1213 for (x = 0; x < numFilters; x++)
1215 libconfig::Setting& pair = setting[x];
1218 if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1220 js["Result"] = false;
1221 js["Error"] = "Filter file format error";
1224 if ((c == channel) && (p == programme))
1228 if (!saveEpgFilter(epgFilter))
1230 js["Result"] = false;
1231 js["Error"] = "Failed to save EPG filter";
1235 logger->debug("Found and deleted: {} {}", c, p);
1236 js["Result"] = true;
1241 js["Result"] = false;
1242 js["Error"] = "Channel/Programme not found";
1245 catch (const std::exception& e)
1247 js["Result"] = false;
1248 js["Error"] = "Unknown error";
1253 bool VDRClient::epgfilterget(PFMap& postFields, Json::Value& js) // RETHROWS
1255 logger->debug("epgFilterget");
1257 libconfig::Config epgFilter;
1258 if (!loadEpgFilter(epgFilter))
1260 js["Result"] = false;
1261 js["Error"] = "Error initialising EPG filter";
1267 libconfig::Setting& setting = epgFilter.lookup("filters");
1268 int numFilters = setting.getLength();
1270 Json::Value jsfilters(Json::arrayValue);
1271 for (x = 0; x < numFilters; x++)
1273 libconfig::Setting& pair = setting[x];
1276 if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1278 js["Result"] = false;
1279 js["Error"] = "Filter file format error";
1282 Json::Value oneFilter;
1285 jsfilters.append(oneFilter);
1287 js["EPGFilters"] = jsfilters;
1289 catch (const std::exception& e)
1291 js["Result"] = false;
1292 js["Error"] = "Unknown error";
1296 js["Result"] = true;
1300 //////////////////////////////////////////////////////////////////////////////////////////////////
1302 const cEvent* VDRClient::getEvent(const cChannels* Channels, const cSchedules* Schedules,
1303 Json::Value& js, int channelNumber, int eventID, int aroundTime)
1305 const cChannel* channel = NULL;
1307 for (channel = Channels->First(); channel; channel = Channels->Next(channel))
1309 if (channel->GroupSep()) continue;
1310 if (channel->Number() == channelNumber) break;
1315 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1316 js["Error"] = "Could not find channel";
1320 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
1323 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1324 js["Error"] = "Internal schedules error (2)";
1328 const cEvent* event = NULL;
1330 event = Schedule->GetEvent(eventID);
1332 event = Schedule->GetEventAround(aroundTime);
1336 logger->error("getevent: Could not find requested event: {}", eventID);
1337 js["Error"] = "Internal schedules error (3)";
1344 cTimer* VDRClient::findTimer(cTimers* Timers,
1345 const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1347 int numTimers = Timers->Count();
1349 for (int i = 0; i < numTimers; i++)
1351 timer = Timers->Get(i);
1353 logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1354 logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1355 logger->debug("findtimer: {}", rChannelID);
1356 logger->debug("findtimer: {}", timer->File());
1357 logger->debug("findtimer: {}", rName);
1358 logger->debug("findtimer: {}", timer->StartTime());
1359 logger->debug("findtimer: {}", rStartTime);
1360 logger->debug("findtimer: {}", timer->StopTime());
1361 logger->debug("findtimer: {}", rStopTime);
1362 logger->debug("findtimer: {}", timer->WeekDays());
1363 logger->debug("findtimer: {}", rWeekDays);
1366 (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1367 && (strcmp(timer->File(), rName) == 0)
1368 && (timer->StartTime() == atoi(rStartTime))
1369 && (timer->StopTime() == atoi(rStopTime))
1370 && (timer->WeekDays() == atoi(rWeekDays))
1373 logger->debug("findtimer: found");
1377 logger->debug("findtimer: no timer found");
1381 // Differs only from above by taking const Timers and returning const cTimer
1382 const cTimer* VDRClient::findTimer(const cTimers* Timers,
1383 const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1385 int numTimers = Timers->Count();
1386 const cTimer* timer;
1387 for (int i = 0; i < numTimers; i++)
1389 timer = Timers->Get(i);
1391 logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1392 logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1393 logger->debug("findtimer: {}", rChannelID);
1394 logger->debug("findtimer: {}", timer->File());
1395 logger->debug("findtimer: {}", rName);
1396 logger->debug("findtimer: {}", timer->StartTime());
1397 logger->debug("findtimer: {}", rStartTime);
1398 logger->debug("findtimer: {}", timer->StopTime());
1399 logger->debug("findtimer: {}", rStopTime);
1400 logger->debug("findtimer: {}", timer->WeekDays());
1401 logger->debug("findtimer: {}", rWeekDays);
1404 (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1405 && (strcmp(timer->File(), rName) == 0)
1406 && (timer->StartTime() == atoi(rStartTime))
1407 && (timer->StopTime() == atoi(rStopTime))
1408 && (timer->WeekDays() == atoi(rWeekDays))
1411 logger->debug("findtimer: found");
1415 logger->debug("findtimer: no timer found");
1419 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)
1421 int numTimers = Timers->Count();
1423 logger->debug("findtimer2: {} {} {} {} {} {} {} {} {}", rName, rActive, rChannelID, rDay, rWeekDays, rStartTime, rStopTime, rPriority, rLifetime);
1424 for (int i = 0; i < numTimers; i++)
1426 timer = Timers->Get(i);
1428 logger->debug("findtimer2: search: {} {} {} {} {} {} {} {} {}", timer->File(), timer->HasFlags(tfActive), (const char*)timer->Channel()->GetChannelID().ToString(),
1429 (int)timer->Day(), timer->WeekDays(), (int)timer->StartTime(), (int)timer->StopTime(),
1430 timer->Priority(), timer->Lifetime());
1432 if ( (strcmp(timer->File(), rName) == 0)
1433 && (timer->HasFlags(tfActive) == !(strcasecmp(rActive, "true")))
1434 && (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1435 && (timer->Day() == atoi(rDay))
1436 && (timer->WeekDays() == atoi(rWeekDays))
1437 && (timer->StartTime() == atoi(rStartTime))
1438 && (timer->StopTime() == atoi(rStopTime))
1439 && (timer->Priority() == atoi(rPriority))
1440 && (timer->Lifetime() == atoi(rLifetime))
1443 logger->debug("findtimer2: found");
1447 logger->debug("findtimer2: no timer found");
1451 int VDRClient::getVarInt(PFMap& postFields, const char* paramName) // THROWS
1453 auto i = postFields.find(paramName);
1454 if (i == postFields.end()) throw BadParamException(paramName);
1456 try { r = std::stoi(i->second); }
1457 catch (const std::exception& e) { throw BadParamException(paramName); }
1461 std::string VDRClient::getVarString(PFMap& postFields, const char* paramName, bool emptyOk) // THROWS
1463 auto i = postFields.find(paramName);
1464 if (i == postFields.end()) throw BadParamException(paramName);
1465 if (!emptyOk && i->second.empty()) throw BadParamException(paramName);
1469 bool VDRClient::loadEpgFilter(libconfig::Config& epgFilter)
1471 std::string epgFilterFile(configDir + std::string("/epgfilter.conf"));
1472 FILE* fp = fopen(epgFilterFile.c_str(), "a");
1475 logger->error("loadEpgFilter: Error: Failed to fopen epgfilter.conf");
1482 epgFilter.readFile(epgFilterFile.c_str());
1484 catch (const libconfig::FileIOException &fioex)
1486 logger->error("loadEpgFilter: Error: Failed to read filter file");
1489 catch(const libconfig::ParseException &pex)
1491 logger->error("loadEpgFilter: Parse error at {}: {} - {}", pex.getFile(), pex.getLine(), pex.getError());
1497 libconfig::Setting& setting = epgFilter.lookup("filters");
1499 if (!setting.isList())
1501 logger->error("loadEpgFilter: Error: Failed to read filter file (2)");
1505 catch (const libconfig::SettingNotFoundException& e)
1507 libconfig::Setting& setting = epgFilter.getRoot();
1508 setting.add("filters", libconfig::Setting::Type::TypeList);
1514 bool VDRClient::saveEpgFilter(libconfig::Config& epgFilter)
1516 std::string epgFilterFile(configDir + std::string("/epgfilter.conf"));
1519 epgFilter.writeFile(epgFilterFile.c_str());
1520 logger->debug("saveEpgFilter: EPG filter saved");
1523 catch (const libconfig::FileIOException& e)
1525 logger->error("saveEpgFilter: Error: File write error");