17 #include <vdr/videodir.h>
18 #include <vdr/recording.h>
20 #include <vdr/timers.h>
22 VDRClient::VDRClient()
24 logger = spd::get("jsonserver_spdlog");
27 bool VDRClient::process(std::string& request, PFMap& postFields, std::string& returnString)
29 Json::Value returnJSON;
34 if (request == "gettime") success = gettime(postFields, returnJSON);
35 else if (request == "diskstats") success = diskstats(postFields, returnJSON);
36 else if (request == "channellist") success = channellist(postFields, returnJSON);
37 else if (request == "reclist") success = reclist(postFields, returnJSON);
38 else if (request == "timerlist") success = timerlist(postFields, returnJSON);
39 else if (request == "epgdownload") success = epgdownload(postFields, returnJSON);
40 else if (request == "tunersstatus") success = tunersstatus(postFields, returnJSON);
42 else if (request == "channelschedule") success = channelschedule(postFields, returnJSON);
43 else if (request == "getscheduleevent") success = getscheduleevent(postFields, returnJSON);
44 else if (request == "epgsearch") success = epgsearch(postFields, returnJSON);
45 else if (request == "epgsearchsame") success = epgsearchsame(postFields, returnJSON);
46 else if (request == "timerset") success = timerset(postFields, returnJSON);
47 else if (request == "recinfo") success = recinfo(postFields, returnJSON);
48 else if (request == "recstop") success = recstop(postFields, returnJSON);
49 else if (request == "recdel") success = recdel(postFields, returnJSON);
50 else if (request == "recrename") success = recrename(postFields, returnJSON);
51 else if (request == "recmove") success = recmove(postFields, returnJSON);
52 else if (request == "timersetactive") success = timersetactive(postFields, returnJSON);
53 else if (request == "timerdel") success = timerdel(postFields, returnJSON);
54 else if (request == "timerisrecording") success = timerisrecording(postFields, returnJSON);
55 else if (request == "recresetresume") success = recresetresume(postFields, returnJSON);
56 else if (request == "timeredit") success = timeredit(postFields, returnJSON);
58 catch (BadParamException e)
60 logger->error("Bad parameter in call, paramName: {}", e.param);
61 returnJSON["Result"] = false;
62 returnJSON["Error"] = "Bad request parameter";
63 returnJSON["Detail"] = e.param;
67 if (!success) return false;
69 Json::StyledWriter sw;
70 returnString = sw.write(returnJSON);
71 logger->debug("Done sw write");
75 bool VDRClient::gettime(PFMap& postFields, Json::Value& js)
77 logger->debug("get_time");
80 gettimeofday(&tv, NULL);
82 js["Time"] = (Json::UInt64)tv.tv_sec;
83 js["MTime"] = (Json::UInt)(tv.tv_usec/1000);
88 bool VDRClient::diskstats(PFMap& postFields, Json::Value& js)
90 logger->debug("diskstats");
94 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
96 js["FreeMiB"] = FreeMB;
97 js["UsedMiB"] = UsedMB;
98 js["Percent"] = Percent;
103 bool VDRClient::channellist(PFMap& postFields, Json::Value& js)
105 logger->debug("channellist");
107 Json::Value jschannels(Json::arrayValue);
109 for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
111 if (!channel->GroupSep())
113 Json::Value oneChannel;
114 oneChannel["ID"] = (const char *)channel->GetChannelID().ToString();
115 oneChannel["Number"] = channel->Number();
116 oneChannel["Name"] = channel->Name();
117 jschannels.append(oneChannel);
120 js["Channels"] = jschannels;
125 bool VDRClient::reclist(PFMap& postFields, Json::Value& js)
127 logger->debug("reclist");
129 Json::Value jsrecordings(Json::arrayValue);
130 cRecordings Recordings;
132 for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording))
135 oneRec["StartTime"] = (Json::UInt)recording->Start();
136 oneRec["Length"] = (Json::UInt)recording->LengthInSeconds();
137 oneRec["IsNew"] = recording->IsNew();
138 oneRec["Name"] = recording->Name();
139 oneRec["Filename"] = recording->FileName();
140 oneRec["FileSizeMB"] = recording->FileSizeMB();
142 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
143 if (rc) oneRec["CurrentlyRecording"] = true;
144 else oneRec["CurrentlyRecording"] = false;
146 jsrecordings.append(oneRec);
148 js["Recordings"] = jsrecordings;
153 bool VDRClient::timerlist(PFMap& postFields, Json::Value& js)
155 logger->debug("timerlist");
157 Json::Value jstimers(Json::arrayValue);
160 int numTimers = Timers.Count();
162 for (int i = 0; i < numTimers; i++)
164 timer = Timers.Get(i);
165 Json::Value oneTimer;
166 oneTimer["Active"] = timer->HasFlags(tfActive);
167 oneTimer["Recording"] = timer->Recording();
168 oneTimer["Pending"] = timer->Pending();
169 oneTimer["Priority"] = timer->Priority();
170 oneTimer["Lifetime"] = timer->Lifetime();
171 oneTimer["ChannelNumber"] = timer->Channel()->Number();
172 oneTimer["ChannelID"] = (const char *)timer->Channel()->GetChannelID().ToString();
173 oneTimer["StartTime"] = (int)timer->StartTime();
174 oneTimer["StopTime"] = (int)timer->StopTime();
175 oneTimer["Day"] = (int)timer->Day();
176 oneTimer["WeekDays"] = timer->WeekDays();
177 oneTimer["Name"] = timer->File();
179 const cEvent* event = timer->Event();
182 oneTimer["EventID"] = event->EventID();
186 int channelNumber = timer->Channel()->Number();
187 int aroundTime = timer->StartTime() + 1;
188 const cEvent* eventAround = getEvent(js, channelNumber, 0, aroundTime);
191 oneTimer["EventID"] = eventAround->EventID();
195 oneTimer["EventID"] = 0;
199 jstimers.append(oneTimer);
202 js["Timers"] = jstimers;
207 bool VDRClient::channelschedule(PFMap& postFields, Json::Value& js) // RETHROWS
209 logger->debug("channelschedule");
210 int channelNumber = getVarInt(postFields, "channelnumber");
211 int startTime = getVarInt(postFields, "starttime");
212 int duration = getVarInt(postFields, "duration");
214 cChannel* channel = NULL;
215 for (channel = Channels.First(); channel; channel = Channels.Next(channel))
217 if (channel->GroupSep()) continue;
218 if (channel->Number() == channelNumber) break;
223 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
224 js["Result"] = false;
225 js["Error"] = "Could not find channel";
229 cSchedulesLock MutexLock;
230 const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
233 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
234 js["Result"] = false;
235 js["Error"] = "Internal schedules error (1)";
238 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
241 logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
242 js["Result"] = false;
243 js["Error"] = "Internal schedules error (2)";
247 Json::Value jsevents(Json::arrayValue);
249 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
251 if ((event->StartTime() + event->Duration()) < time(NULL)) continue; //in the past filter
252 if ((event->StartTime() + event->Duration()) <= startTime) continue; //start time filter
253 if (event->StartTime() >= (startTime + duration)) continue; //duration filter
255 Json::Value oneEvent;
256 oneEvent["ID"] = event->EventID();
257 oneEvent["Time"] = (Json::UInt)event->StartTime();
258 oneEvent["Duration"] = event->Duration();
259 oneEvent["Title"] = event->Title() ? event->Title() : "";
260 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
261 oneEvent["HasTimer"] = event->HasTimer();
262 jsevents.append(oneEvent);
266 js["Events"] = jsevents;
270 bool VDRClient::getscheduleevent(PFMap& postFields, Json::Value& js) // RETHROWS
272 logger->debug("getscheduleevent");
274 int channelNumber = getVarInt(postFields, "channelnumber");
275 int eventID = getVarInt(postFields, "eventid");
277 const cEvent* event = getEvent(js, channelNumber, eventID, 0);
280 js["Result"] = false;
284 Json::Value oneEvent;
285 oneEvent["ID"] = event->EventID();
286 oneEvent["Time"] = (Json::UInt)event->StartTime();
287 oneEvent["Duration"] = event->Duration();
288 oneEvent["Title"] = event->Title() ? event->Title() : "";
289 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
290 oneEvent["Description"] = event->Description() ? event->Description() : "";
291 oneEvent["HasTimer"] = event->HasTimer();
292 oneEvent["RunningStatus"] = event->RunningStatus();
295 js["Event"] = oneEvent;
299 bool VDRClient::epgdownload(PFMap& postFields, Json::Value& js)
301 logger->debug("epgdownload");
303 cSchedulesLock MutexLock;
304 const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
308 logger->error("epgdownload: Could not get Schedules object");
309 js["Result"] = false;
310 js["Error"] = "Internal schedules error (1)";
314 Json::Value jsevents(Json::arrayValue);
316 for (cChannel* channel = Channels.First(); channel; channel = Channels.Next(channel))
318 if (channel->GroupSep()) continue;
320 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
321 if (!Schedule) continue;
324 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
326 Json::Value oneEvent;
327 oneEvent["ChannelNumber"] = channel->Number();
328 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
329 oneEvent["ID"] = event->EventID();
330 oneEvent["Time"] = (Json::UInt)event->StartTime();
331 oneEvent["Duration"] = event->Duration();
332 oneEvent["Title"] = event->Title() ? event->Title() : "";
333 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
334 oneEvent["HasTimer"] = event->HasTimer();
335 oneEvent["Description"] = event->Description() ? event->Description() : "";
336 jsevents.append(oneEvent);
341 js["Events"] = jsevents;
345 bool VDRClient::epgsearch(PFMap& postFields, Json::Value& js) // RETHROWS
347 logger->debug("epgsearch");
349 std::string searchfor = getVarString(postFields, "searchfor");
350 logger->debug("epgsearch: search for: {}", searchfor);
352 cSchedulesLock MutexLock;
353 const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
357 logger->error("epgsearch: Could not get Schedules object");
358 js["Result"] = false;
359 js["Error"] = "Internal schedules error (1)";
363 Json::Value jsevents(Json::arrayValue);
365 for (cChannel* channel = Channels.First(); channel; channel = Channels.Next(channel))
367 if (channel->GroupSep()) continue;
369 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
370 if (!Schedule) continue;
373 bool founddescription;
374 for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
377 founddescription = false;
379 if (event->Title() && strcasestr(event->Title(), searchfor.c_str())) foundtitle = true;
381 if (!foundtitle && event->Description())
382 if (strcasestr(event->Description(), searchfor.c_str())) founddescription = true;
384 if (foundtitle || founddescription)
386 Json::Value oneEvent;
387 oneEvent["ChannelNumber"] = channel->Number();
388 oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
389 oneEvent["ID"] = event->EventID();
390 oneEvent["Time"] = (Json::UInt)event->StartTime();
391 oneEvent["Duration"] = event->Duration();
392 oneEvent["Title"] = event->Title() ? event->Title() : "";
393 oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
394 oneEvent["HasTimer"] = event->HasTimer();
395 oneEvent["Description"] = event->Description() ? event->Description() : "";
396 if (founddescription)
397 oneEvent["FoundInDesc"] = true;
399 oneEvent["FoundInDesc"] = false;
400 jsevents.append(oneEvent);
406 js["Events"] = jsevents;
408 logger->debug("epgsearch: search for: {} done", searchfor);
413 bool VDRClient::epgsearchsame(PFMap& postFields, Json::Value& js) // RETHROWS
415 logger->debug("epgsearchsame");
417 int atTime = getVarInt(postFields, "time");
418 std::string sTitle = getVarString(postFields, "title");
420 logger->debug("epgsearchsame: request time: {}, title: {}", atTime, sTitle);
422 cSchedulesLock SchedulesLock;
423 const cSchedules *schedules = cSchedules::Schedules(SchedulesLock);
426 js["Result"] = false;
430 Json::Value jsevents(Json::arrayValue);
433 for (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::tunersstatus(PFMap& postFields, Json::Value& js)
460 logger->debug("tunerstatus");
462 js["NumDevices"] = cDevice::NumDevices();
464 Json::Value jsdevices(Json::arrayValue);
466 for (int i = 0; i < cDevice::NumDevices(); i++)
468 Json::Value oneDevice;
469 cDevice *d = cDevice::GetDevice(i);
470 oneDevice["Number"] = d->DeviceNumber();
471 oneDevice["Type"] = (const char*)d->DeviceType();
472 oneDevice["Name"] = (const char*)d->DeviceName();
473 oneDevice["IsPrimary"] = d->IsPrimaryDevice();
475 const cChannel* cchannel = d->GetCurrentlyTunedTransponder();
478 oneDevice["Frequency"] = cchannel->Frequency();
479 oneDevice["SignalStrength"] = d->SignalStrength();
480 oneDevice["SignalQuality"] = d->SignalQuality();
485 oneDevice["Frequency"] = 0;
486 oneDevice["SignalStrength"] = 0;
487 oneDevice["SignalQuality"] = 0;
490 jsdevices.append(oneDevice);
493 js["Devices"] = jsdevices;
496 Json::Value jstimers(Json::arrayValue);
497 int numTimers = Timers.Count();
499 for (int i = 0; i < numTimers; i++)
501 timer = Timers.Get(i);
503 if (timer->Recording())
505 Json::Value oneTimer;
506 oneTimer["Recording"] = timer->Recording();
507 oneTimer["StartTime"] = (int)timer->StartTime();
508 oneTimer["StopTime"] = (int)timer->StopTime();
509 oneTimer["File"] = timer->File();
511 cRecordControl* crc = cRecordControls::GetRecordControl(timer);
514 cDevice* crcd = crc->Device();
515 oneTimer["DeviceNumber"] = crcd->DeviceNumber();
519 oneTimer["DeviceNumber"] = Json::Value::null;
522 const cChannel* channel = timer->Channel();
525 oneTimer["ChannelName"] = channel->Name();
529 oneTimer["ChannelName"] = Json::Value::null;
532 jstimers.append(oneTimer);
536 js["CurrentRecordings"] = jstimers;
543 bool VDRClient::timerset(PFMap& postFields, Json::Value& js) // RETHROWS
545 logger->debug("timerset");
547 std::string sTimerString = getVarString(postFields, "timerstring");
549 logger->debug("timerset: '{}'", sTimerString);
550 cTimer *timer = new cTimer;
551 if (!timer->Parse(sTimerString.c_str()))
554 js["Result"] = false;
555 js["Error"] = "Failed to parse timer request details";
559 cTimer *t = Timers.GetTimer(timer);
563 js["Result"] = false;
564 js["Error"] = "Timer already exists";
569 Timers.SetModified();
574 bool VDRClient::recinfo(PFMap& postFields, Json::Value& js) // RETHROWS
576 logger->debug("recinfo");
578 std::string reqfilename = getVarString(postFields, "filename");
579 logger->debug("recinfo: {}", reqfilename);
581 cRecordings Recordings;
582 Recordings.Load(); // probably have to do this
583 cRecording *recording = Recordings.GetByName(reqfilename.c_str());
587 logger->error("recinfo: recinfo found no recording");
588 js["Result"] = false;
592 js["IsNew"] = recording->IsNew();
593 js["LengthInSeconds"] = recording->LengthInSeconds();
594 js["FileSizeMB"] = recording->FileSizeMB();
595 js["Name"] = recording->Name() ? recording->Name() : Json::Value::null;
596 js["Priority"] = recording->Priority();
597 js["LifeTime"] = recording->Lifetime();
598 js["Start"] = (Json::UInt)recording->Start();
600 js["CurrentlyRecordingStart"] = 0;
601 js["CurrentlyRecordingStop"] = 0;
602 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
605 js["CurrentlyRecordingStart"] = (Json::UInt)rc->Timer()->StartTime();
606 js["CurrentlyRecordingStop"] = (Json::UInt)rc->Timer()->StopTime();
609 js["ResumePoint"] = 0;
611 const cRecordingInfo *info = recording->Info();
614 js["ChannelName"] = info->ChannelName() ? info->ChannelName() : Json::Value::null;
615 js["Title"] = info->Title() ? info->Title() : Json::Value::null;
616 js["ShortText"] = info->ShortText() ? info->ShortText() : Json::Value::null;
617 js["Description"] = info->Description() ? info->Description() : Json::Value::null;
619 const cComponents* components = info->Components();
622 js["Components"] = Json::Value::null;
626 Json::Value jscomponents;
628 tComponent* component;
629 for (int i = 0; i < components->NumComponents(); i++)
631 component = components->Component(i);
633 Json::Value oneComponent;
634 oneComponent["Stream"] = component->stream;
635 oneComponent["Type"] = component->type;
636 oneComponent["Language"] = component->language ? component->language : Json::Value::null;
637 oneComponent["Description"] = component->description ? component->description : Json::Value::null;
638 jscomponents.append(oneComponent);
641 js["Components"] = jscomponents;
644 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
645 if (ResumeFile.Read() >= 0) js["ResumePoint"] = floor(ResumeFile.Read() / info->FramesPerSecond());
652 bool VDRClient::recstop(PFMap& postFields, Json::Value& js) // RETHROWS
654 logger->debug("recstop");
656 std::string reqfilename = getVarString(postFields, "filename");
657 logger->debug("recstop: {}", reqfilename);
659 cRecordings Recordings;
660 Recordings.Load(); // probably have to do this
661 cRecording *recording = Recordings.GetByName(reqfilename.c_str());
665 logger->error("recstop: recstop found no recording");
666 js["Result"] = false;
670 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
673 logger->error("recstop: not currently recording");
674 js["Result"] = false;
678 if (Timers.BeingEdited())
680 logger->debug("recstop: timers being edited elsewhere");
681 js["Result"] = false;
685 cTimer* timer = rc->Timer();
688 logger->error("recstop: timer not found");
689 js["Result"] = false;
693 timer->ClrFlags(tfActive);
694 Timers.SetModified();
701 bool VDRClient::recdel(PFMap& postFields, Json::Value& js) // RETHROWS
703 logger->debug("recdel");
705 std::string reqfilename = getVarString(postFields, "filename");
706 logger->debug("recdel: {}", reqfilename);
708 cRecordings Recordings;
709 Recordings.Load(); // probably have to do this
710 cRecording *recording = Recordings.GetByName(reqfilename.c_str());
714 js["Result"] = false;
715 js["Error"] = "Could not find recording to delete";
719 logger->debug("recdel: Deleting recording: {}", recording->Name());
720 cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
723 js["Result"] = false;
724 js["Error"] = "This recording is still recording.. ho ho";
728 if (recording->Delete())
730 ::Recordings.DelByName(recording->FileName());
735 js["Result"] = false;
736 js["Error"] = "Failed to delete recording";
742 void VDRClient::pathsForRecordingName(const std::string& recordingName,
743 std::string& dirNameSingleDate,
744 std::string& dirNameSingleTitle,
745 std::string& dirNameSingleFolder,
746 std::string& dirNameFullPathTitle,
747 std::string& dirNameFullPathDate) // throws int
749 cRecordings Recordings;
751 cRecording* recordingObj = Recordings.GetByName(recordingName.c_str());
752 if (!recordingObj) throw 2;
754 cRecordControl *rc = cRecordControls::GetRecordControl(recordingObj->FileName());
757 logger->debug("paths: recording: {}", recordingObj->Name());
758 logger->debug("paths: recording: {}", recordingObj->FileName());
760 const char* t = recordingObj->FileName();
761 dirNameFullPathDate = t;
765 // Find the datedirname
766 for(k = strlen(t) - 1; k >= 0; k--)
770 logger->debug("recmoverename: l1: {}", strlen(&t[k+1]) + 1);
771 dirNameSingleDate.assign(&t[k+1], strlen(t) - k - 1);
772 logger->debug("paths: dirNameSingleDate: '{}'", dirNameSingleDate);
777 // Find the titledirname
779 for(j = k-1; j >= 0; j--)
783 logger->debug("recmoverename: l2: {}", k - j);
784 dirNameSingleTitle.assign(&t[j+1], k - j - 1);
785 logger->debug("paths: dirNameSingleTitle: '{}'", dirNameSingleTitle);
790 // Find the foldername
792 const char* vidDirStr = cVideoDirectory::Name();
793 int vidDirStrLen = strlen(vidDirStr);
795 logger->debug("recmoverename: j = {}, strlenvd = {}", j, vidDirStrLen);
796 if (j > vidDirStrLen) // Rec is in a subfolder now
798 for(m = j-1; m >= 0; m--)
802 logger->debug("recmoverename: l3: {}", j - m);
803 dirNameSingleFolder.assign(&t[m+1], j - m - 1);
804 logger->debug("paths: dirNameSingleFolder: '{}'", dirNameSingleFolder);
810 dirNameFullPathTitle.assign(t, k);
811 logger->debug("paths: dirNameFullPathTitle: '{}'", dirNameFullPathTitle);
814 bool VDRClient::recrename(PFMap& postFields, Json::Value& js) // RETHROWS
816 logger->debug("recrename");
818 std::string fileNameToAffect = getVarString(postFields, "filename");
819 std::string requestedNewStr = getVarString(postFields, "newname");
821 std::string dirNameSingleDate;
822 std::string dirNameSingleTitle;
823 std::string dirNameSingleFolder;
824 std::string dirNameFullPathTitle;
825 std::string dirNameFullPathDate;
829 pathsForRecordingName(fileNameToAffect, dirNameSingleDate,
830 dirNameSingleTitle, dirNameSingleFolder,
831 dirNameFullPathTitle, dirNameFullPathDate);
833 char* requestedNewSingleTitle = (char*)malloc(requestedNewStr.size() + 1);
834 strcpy(requestedNewSingleTitle, requestedNewStr.c_str());
835 logger->debug("recmoverename: to: {}", requestedNewSingleTitle);
837 requestedNewSingleTitle = ExchangeChars(requestedNewSingleTitle, true);
838 if (!strlen(requestedNewSingleTitle)) throw 9;
839 logger->debug("recmoverename: EC: {}", requestedNewSingleTitle);
841 const char* vidDirStr = cVideoDirectory::Name();
842 logger->debug("recmoverename: viddir: {}", vidDirStr);
844 // Could be a new path - construct that first and test
846 std::string newDirNameFullPathTitle = vidDirStr;
847 newDirNameFullPathTitle.append("/");
849 if (!dirNameSingleFolder.empty())
851 newDirNameFullPathTitle.append(dirNameSingleFolder);
852 newDirNameFullPathTitle.append("/");
855 newDirNameFullPathTitle.append(requestedNewSingleTitle);
856 free(requestedNewSingleTitle);
858 logger->debug("recrename: NPT2: {}", newDirNameFullPathTitle);
861 int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
862 if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
864 logger->debug("recrename: new path does not exist (1)");
865 int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
866 if (mkdirret != 0) throw 4;
868 else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
870 // Something exists but it's not a dir
874 // New path now created or was there already
876 std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
877 newDirNameFullPathDate.append(dirNameSingleDate);
879 logger->debug("recrename: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
880 if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
882 // Success. Test for remove old dir containter
883 rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
885 ::Recordings.Update();
887 js["NewRecordingFileName"] = newDirNameFullPathDate;
891 js["Result"] = false;
894 logger->error("recrename: Bad parameters");
895 js["Error"] = "Bad request parameters";
899 logger->error("recrename: Could not find recording to move");
900 js["Error"] = "Bad filename";
904 logger->error("recrename: Could not move recording, it is still recording");
905 js["Error"] = "Cannot move recording in progress";
909 logger->error("recrename: Failed to make new dir (1)");
910 js["Error"] = "Failed to create new directory (1)";
914 logger->error("recrename: Something already exists? (1)");
915 js["Error"] = "Something already exists at the new path (1)";
919 logger->error("recrename: Rename failed");
920 js["Error"] = "Rename failed";
924 logger->error("recrename: ExchangeChars lost our string");
925 js["Error"] = "Rename failed";
932 bool VDRClient::recmove(PFMap& postFields, Json::Value& js) // RETHROWS
934 logger->debug("recmove");
936 std::string fileNameToAffect = getVarString(postFields, "filename");
937 std::string requestedNewStr = getVarString(postFields, "newpath");
939 std::string dirNameSingleDate;
940 std::string dirNameSingleTitle;
941 std::string dirNameSingleFolder;
942 std::string dirNameFullPathTitle;
943 std::string dirNameFullPathDate;
947 pathsForRecordingName(fileNameToAffect, dirNameSingleDate,
948 dirNameSingleTitle, dirNameSingleFolder,
949 dirNameFullPathTitle, dirNameFullPathDate);
951 char* requestedNewSinglePath = (char*)malloc(requestedNewStr.size() + 1);
952 strcpy(requestedNewSinglePath, requestedNewStr.c_str());
953 logger->debug("recmoverename: to: {}", requestedNewSinglePath);
955 requestedNewSinglePath = ExchangeChars(requestedNewSinglePath, true);
956 if (!strlen(requestedNewSinglePath)) throw 9;
957 logger->debug("recmoverename: EC: {}", requestedNewSinglePath);
959 const char* vidDirStr = cVideoDirectory::Name();
960 logger->debug("recmoverename: viddir: {}", vidDirStr);
962 // Could be a new path - construct that first and test
964 std::string newDirNameFullPathTitle = vidDirStr;
965 newDirNameFullPathTitle.append(requestedNewSinglePath);
966 free(requestedNewSinglePath);
968 logger->debug("recmove: NPT: {}", newDirNameFullPathTitle);
971 int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
972 if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
974 logger->debug("recmove: new path does not exist (1)");
975 int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
976 if (mkdirret != 0) throw 4;
978 else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
980 // Something exists but it's not a dir
984 // New path now created or was there already
986 newDirNameFullPathTitle.append(dirNameSingleTitle);
987 logger->debug("recmove: {}", newDirNameFullPathTitle);
989 statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
990 if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
992 logger->debug("recmove: new dir does not exist (2)");
993 int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
994 if (mkdirret != 0) throw 6;
996 else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
998 // Something exists but it's not a dir
1002 // Ok, the directory container has been made, or it pre-existed.
1004 std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
1005 newDirNameFullPathDate.append(dirNameSingleDate);
1007 logger->debug("recmove: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
1008 if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
1010 // Success. Test for remove old dir containter
1011 rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
1013 // Test for remove old foldername
1014 if (!dirNameSingleFolder.empty())
1016 std::string dirNameFullPathFolder = vidDirStr;
1017 dirNameFullPathFolder.append("/");
1018 dirNameFullPathFolder.append(dirNameSingleFolder);
1020 logger->debug("recmove: oldfoldername: {}", dirNameFullPathFolder);
1023 rmdir() deletes a directory, which must be empty.
1024 ENOTEMPTY - pathname contains entries other than . and ..
1025 So, should be safe to call rmdir on non-empty dir
1027 rmdir(dirNameFullPathFolder.c_str()); // can't do anything about a fail result at this point.
1030 ::Recordings.Update();
1031 js["Result"] = true;
1032 js["NewRecordingFileName"] = newDirNameFullPathDate;
1036 js["Result"] = false;
1039 logger->error("recmove: Bad parameters");
1040 js["Error"] = "Bad request parameters";
1044 logger->error("recmove: Could not find recording to move");
1045 js["Error"] = "Bad filename";
1049 logger->error("recmove: Could not move recording, it is still recording");
1050 js["Error"] = "Cannot move recording in progress";
1054 logger->error("recmove: Failed to make new dir (1)");
1055 js["Error"] = "Failed to create new directory (1)";
1059 logger->error("recmove: Something already exists? (1)");
1060 js["Error"] = "Something already exists at the new path (1)";
1064 logger->error("recmove: Failed to make new dir (2)");
1065 js["Error"] = "Failed to create new directory (2)";
1069 logger->error("recmove: Something already exists?");
1070 js["Error"] = "Something already exists at the new path";
1074 logger->error("recmove: Rename failed");
1075 js["Error"] = "Move failed";
1079 logger->error("recrename: ExchangeChars lost our string");
1080 js["Error"] = "Rename failed";
1087 bool VDRClient::timersetactive(PFMap& postFields, Json::Value& js) // RETHROWS
1089 logger->debug("timersetactive");
1091 std::string rChannelID = getVarString(postFields, "ChannelID");
1092 std::string rName = getVarString(postFields, "Name");
1093 std::string rStartTime = getVarString(postFields, "StartTime");
1094 std::string rStopTime = getVarString(postFields, "StopTime");
1095 std::string rWeekDays = getVarString(postFields, "WeekDays");
1096 std::string tNewActive = getVarString(postFields, "SetActive");
1098 logger->debug("timersetactive: {} {}:{}:{}:{}:{}", tNewActive, rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1100 cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1103 if (tNewActive == "true") timer->SetFlags(tfActive);
1104 else if (tNewActive == "false") timer->ClrFlags(tfActive);
1107 js["Result"] = false;
1108 js["Error"] = "Bad request parameters";
1112 js["Result"] = true;
1113 Timers.SetModified();
1117 js["Result"] = false;
1118 js["Error"] = "Timer not found";
1122 bool VDRClient::timerdel(PFMap& postFields, Json::Value& js) // RETHROWS
1124 logger->debug("timerdel");
1126 std::string rChannelID = getVarString(postFields, "ChannelID");
1127 std::string rName = getVarString(postFields, "Name");
1128 std::string rStartTime = getVarString(postFields, "StartTime");
1129 std::string rStopTime = getVarString(postFields, "StopTime");
1130 std::string rWeekDays = getVarString(postFields, "WeekDays");
1132 logger->debug("timerdel: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1134 if (Timers.BeingEdited())
1136 logger->debug("timerdel: Unable to delete timer - timers being edited at VDR");
1137 js["Result"] = false;
1138 js["Error"] = "Timers being edited at VDR";
1142 cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1145 if (timer->Recording())
1147 logger->debug("timerdel: Unable to delete timer - timer is running");
1148 js["Result"] = false;
1149 js["Error"] = "Timer is running";
1154 Timers.SetModified();
1155 js["Result"] = true;
1159 js["Result"] = false;
1160 js["Error"] = "Timer not found";
1164 bool VDRClient::timerisrecording(PFMap& postFields, Json::Value& js) // RETHROWS
1166 logger->debug("timerisrecording");
1168 std::string rChannelID = getVarString(postFields, "ChannelID");
1169 std::string rName = getVarString(postFields, "Name");
1170 std::string rStartTime = getVarString(postFields, "StartTime");
1171 std::string rStopTime = getVarString(postFields, "StopTime");
1172 std::string rWeekDays = getVarString(postFields, "WeekDays");
1174 logger->debug("timerisrecording: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1176 cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1179 js["Recording"] = timer->Recording();
1180 js["Pending"] = timer->Pending();
1181 js["Result"] = true;
1185 js["Result"] = false;
1186 js["Error"] = "Timer not found";
1190 bool VDRClient::recresetresume(PFMap& postFields, Json::Value& js) // RETHROWS
1192 logger->debug("recresetresume");
1194 std::string reqfilename = getVarString(postFields, "filename");
1195 logger->debug("recresetresume: {}", reqfilename);
1197 cRecordings Recordings;
1198 Recordings.Load(); // probably have to do this
1199 cRecording *recording = Recordings.GetByName(reqfilename.c_str());
1203 js["Result"] = false;
1204 js["Error"] = "Could not find recording to reset resume";
1208 logger->debug("recresetresume: Reset resume for: {}", recording->Name());
1210 cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
1211 if (ResumeFile.Read() >= 0)
1213 ResumeFile.Delete();
1214 js["Result"] = true;
1219 js["Result"] = false;
1220 js["Error"] = "Recording has no resume point";
1225 bool VDRClient::timeredit(PFMap& postFields, Json::Value& js) // RETHROWS
1227 logger->debug("timeredit");
1229 std::string oldName = getVarString(postFields, "OldName");
1230 std::string oldActive = getVarString(postFields, "OldActive");
1231 std::string oldChannelID = getVarString(postFields, "OldChannelID");
1232 std::string oldDay = getVarString(postFields, "OldDay");
1233 std::string oldWeekDays = getVarString(postFields, "OldWeekDays");
1234 std::string oldStartTime = getVarString(postFields, "OldStartTime");
1235 std::string oldStopTime = getVarString(postFields, "OldStopTime");
1236 std::string oldPriority = getVarString(postFields, "OldPriority");
1237 std::string oldLifetime = getVarString(postFields, "OldLifetime");
1239 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", oldName, oldActive, oldChannelID, oldDay, oldWeekDays, oldStartTime, oldStopTime, oldPriority, oldLifetime);
1241 std::string newName = getVarString(postFields, "NewName");
1242 std::string newActive = getVarString(postFields, "NewActive");
1243 std::string newChannelID = getVarString(postFields, "NewChannelID");
1244 std::string newDay = getVarString(postFields, "NewDay");
1245 std::string newWeekDays = getVarString(postFields, "NewWeekDays");
1246 std::string newStartTime = getVarString(postFields, "NewStartTime");
1247 std::string newStopTime = getVarString(postFields, "NewStopTime");
1248 std::string newPriority = getVarString(postFields, "NewPriority");
1249 std::string newLifetime = getVarString(postFields, "NewLifetime");
1251 logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", newName, newActive, newChannelID, newDay, newWeekDays, newStartTime, newStopTime, newPriority, newLifetime);
1253 cTimer* timer = findTimer2(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());
1256 js["Result"] = false;
1257 js["Error"] = "Timer not found";
1261 // Old version commented below (now removed) used to set each thing individually based on whether it had changed. However, since
1262 // the only way to change the timer channel appears to be with the cTimer::Parse function, might as well use that
1263 // for everything it supports
1264 // Except flags. Get current flags, set using Parse, then add/remove active as needed the other way.
1266 time_t nstt = std::stoi(newStartTime);
1268 localtime_r(&nstt, &nstm);
1269 int nssf = (nstm.tm_hour * 100) + nstm.tm_min;
1271 time_t nztt = std::stoi(newStopTime);
1273 localtime_r(&nztt, &nztm);
1274 int nzsf = (nztm.tm_hour * 100) + nztm.tm_min;
1276 std::replace(newName.begin(), newName.end(), ':', '|');
1278 // ? Convert to std::string?
1279 cString parseBuffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s",
1280 timer->Flags(), newChannelID.c_str(), *(cTimer::PrintDay(std::stoi(newDay), std::stoi(newWeekDays), true)),
1281 nssf, nzsf, std::stoi(newPriority), std::stoi(newLifetime), newName.c_str(), timer->Aux() ? timer->Aux() : "");
1283 logger->debug("timeredit: new parse: {}", *parseBuffer);
1285 bool parseResult = timer->Parse(*parseBuffer);
1288 js["Result"] = false;
1289 js["Error"] = "Timer parsing failed";
1293 if (timer->HasFlags(tfActive) != !(strcasecmp(newActive.c_str(), "true")))
1295 logger->debug("timeredit: {} {} set new active: {}", timer->HasFlags(tfActive), !(strcasecmp(newActive.c_str(), "true")), newActive);
1297 if (strcasecmp(newActive.c_str(), "true") == 0)
1299 timer->SetFlags(tfActive);
1301 else if (strcasecmp(newActive.c_str(), "false") == 0)
1303 timer->ClrFlags(tfActive);
1307 js["Result"] = false;
1308 js["Error"] = "Bad request parameters";
1313 js["Result"] = true;
1314 Timers.SetModified();
1318 //////////////////////////////////////////////////////////////////////////////////////////////////
1320 const cEvent* VDRClient::getEvent(Json::Value& js, int channelNumber, int eventID, int aroundTime)
1322 cChannel* channel = NULL;
1323 for (channel = Channels.First(); channel; channel = Channels.Next(channel))
1325 if (channel->GroupSep()) continue;
1326 if (channel->Number() == channelNumber) break;
1331 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1332 js["Error"] = "Could not find channel";
1336 cSchedulesLock MutexLock;
1337 const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
1340 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1341 js["Error"] = "Internal schedules error (1)";
1345 const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
1348 logger->error("getevent: Could not find requested channel: {}", channelNumber);
1349 js["Error"] = "Internal schedules error (2)";
1353 const cEvent* event = NULL;
1355 event = Schedule->GetEvent(eventID);
1357 event = Schedule->GetEventAround(aroundTime);
1361 logger->error("getevent: Could not find requested event: {}", eventID);
1362 js["Error"] = "Internal schedules error (3)";
1369 cTimer* VDRClient::findTimer(const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1371 int numTimers = Timers.Count();
1373 for (int i = 0; i < numTimers; i++)
1375 timer = Timers.Get(i);
1377 logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1378 logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1379 logger->debug("findtimer: {}", rChannelID);
1380 logger->debug("findtimer: {}", timer->File());
1381 logger->debug("findtimer: {}", rName);
1382 logger->debug("findtimer: {}", timer->StartTime());
1383 logger->debug("findtimer: {}", rStartTime);
1384 logger->debug("findtimer: {}", timer->StopTime());
1385 logger->debug("findtimer: {}", rStopTime);
1386 logger->debug("findtimer: {}", timer->WeekDays());
1387 logger->debug("findtimer: {}", rWeekDays);
1390 (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1391 && (strcmp(timer->File(), rName) == 0)
1392 && (timer->StartTime() == atoi(rStartTime))
1393 && (timer->StopTime() == atoi(rStopTime))
1394 && (timer->WeekDays() == atoi(rWeekDays))
1397 logger->debug("findtimer: found");
1401 logger->debug("findtimer: no timer found");
1405 cTimer* VDRClient::findTimer2(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)
1407 int numTimers = Timers.Count();
1409 logger->debug("findtimer2: {} {} {} {} {} {} {} {} {}", rName, rActive, rChannelID, rDay, rWeekDays, rStartTime, rStopTime, rPriority, rLifetime);
1410 for (int i = 0; i < numTimers; i++)
1412 timer = Timers.Get(i);
1414 logger->debug("findtimer2: search: {} {} {} {} {} {} {} {} {}", timer->File(), timer->HasFlags(tfActive), (const char*)timer->Channel()->GetChannelID().ToString(),
1415 (int)timer->Day(), timer->WeekDays(), (int)timer->StartTime(), (int)timer->StopTime(),
1416 timer->Priority(), timer->Lifetime());
1418 if ( (strcmp(timer->File(), rName) == 0)
1419 && (timer->HasFlags(tfActive) == !(strcasecmp(rActive, "true")))
1420 && (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1421 && (timer->Day() == atoi(rDay))
1422 && (timer->WeekDays() == atoi(rWeekDays))
1423 && (timer->StartTime() == atoi(rStartTime))
1424 && (timer->StopTime() == atoi(rStopTime))
1425 && (timer->Priority() == atoi(rPriority))
1426 && (timer->Lifetime() == atoi(rLifetime))
1429 logger->debug("findtimer2: found");
1433 logger->debug("findtimer2: no timer found");
1437 int VDRClient::getVarInt(PFMap& postFields, const char* paramName) // THROWS
1439 auto i = postFields.find(paramName);
1440 if (i == postFields.end()) throw BadParamException(paramName);
1442 try { r = std::stoi(i->second); }
1443 catch (std::exception e) { throw BadParamException(paramName); }
1447 std::string VDRClient::getVarString(PFMap& postFields, const char* paramName) // THROWS
1449 auto i = postFields.find(paramName);
1450 if (i == postFields.end()) throw BadParamException(paramName);
1451 if (i->second.empty()) throw BadParamException(paramName);