]> git.vomp.tv Git - jsonserver.git/blob - vdrclient.c
Switch to libmicrohttpd. Rewrite handler.
[jsonserver.git] / vdrclient.c
1 #include "vdrclient.h"
2
3 /*
4 trace
5 debug
6 info
7 warn
8 error
9 critical
10 */
11
12 #include <string.h>
13 #include <stdlib.h>
14 #include <sys/time.h>
15 #include <string>
16
17 #include <vdr/videodir.h>
18 #include <vdr/recording.h>
19 #include <vdr/menu.h>
20 #include <vdr/timers.h>
21
22 VDRClient::VDRClient()
23 {
24   logger = spd::get("jsonserver_spdlog");
25 }
26
27 bool VDRClient::process(std::string& request, PFMap& postFields, std::string& returnString)
28 {
29   Json::Value returnJSON;
30   bool success = false;
31
32   try
33   {
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);
41
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);
57   }
58   catch (BadParamException e)
59   {
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;
64     success = true;
65   }
66
67   if (!success) return false;
68
69   Json::StyledWriter sw;
70   returnString = sw.write(returnJSON);
71   logger->debug("Done sw write");
72   return true;
73 }
74
75 bool VDRClient::gettime(PFMap& postFields, Json::Value& js)
76 {
77   logger->debug("get_time");
78
79   struct timeval tv;
80   gettimeofday(&tv, NULL);
81
82   js["Time"] = (Json::UInt64)tv.tv_sec;
83   js["MTime"] = (Json::UInt)(tv.tv_usec/1000);
84   js["Result"] = true;
85   return true;
86 }
87
88 bool VDRClient::diskstats(PFMap& postFields, Json::Value& js)
89 {
90   logger->debug("diskstats");
91
92   int FreeMB;
93   int UsedMB;
94   int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
95
96   js["FreeMiB"] = FreeMB;
97   js["UsedMiB"] = UsedMB;
98   js["Percent"] = Percent;
99   js["Result"] = true;
100   return true;
101 }
102
103 bool VDRClient::channellist(PFMap& postFields, Json::Value& js)
104 {
105   logger->debug("channellist");
106
107   Json::Value jschannels(Json::arrayValue);
108
109   for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
110   {
111     if (!channel->GroupSep())
112     {
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);
118     }
119   }
120   js["Channels"] = jschannels;
121
122   return true;
123 }
124
125 bool VDRClient::reclist(PFMap& postFields, Json::Value& js)
126 {
127   logger->debug("reclist");
128
129   Json::Value jsrecordings(Json::arrayValue);
130   cRecordings Recordings;
131   Recordings.Load();
132   for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording))
133   {
134     Json::Value oneRec;
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();
141
142     cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
143     if (rc) oneRec["CurrentlyRecording"] = true;
144     else    oneRec["CurrentlyRecording"] = false;
145
146     jsrecordings.append(oneRec);
147   }
148   js["Recordings"] = jsrecordings;
149
150   return true;
151 }
152
153 bool VDRClient::timerlist(PFMap& postFields, Json::Value& js)
154 {
155   logger->debug("timerlist");
156
157   Json::Value jstimers(Json::arrayValue);
158
159   cTimer *timer;
160   int numTimers = Timers.Count();
161
162   for (int i = 0; i < numTimers; i++)
163   {
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();
178
179     const cEvent* event = timer->Event();
180     if (event)
181     {
182       oneTimer["EventID"] = event->EventID();
183     }
184     else
185     {
186       int channelNumber = timer->Channel()->Number();
187       int aroundTime = timer->StartTime() + 1;
188       const cEvent* eventAround = getEvent(js, channelNumber, 0, aroundTime);
189       if (eventAround)
190       {
191         oneTimer["EventID"] = eventAround->EventID();
192       }
193       else
194       {
195         oneTimer["EventID"] = 0;
196       }
197     }
198
199     jstimers.append(oneTimer);
200   }
201
202   js["Timers"] = jstimers;
203   js["Result"] = true;
204   return true;
205 }
206
207 bool VDRClient::channelschedule(PFMap& postFields, Json::Value& js) // RETHROWS
208 {
209   logger->debug("channelschedule");
210   int channelNumber = getVarInt(postFields, "channelnumber");
211   int startTime = getVarInt(postFields, "starttime");
212   int duration = getVarInt(postFields, "duration");
213
214   cChannel* channel = NULL;
215   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
216   {
217     if (channel->GroupSep()) continue;
218     if (channel->Number() == channelNumber) break;
219   }
220
221   if (!channel)
222   {
223     logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
224     js["Result"] = false;
225     js["Error"] = "Could not find channel";
226     return true;
227   }
228
229   cSchedulesLock MutexLock;
230   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
231   if (!Schedules)
232   {
233     logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
234     js["Result"] = false;
235     js["Error"] = "Internal schedules error (1)";
236     return true;
237   }
238   const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
239   if (!Schedule)
240   {
241     logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
242     js["Result"] = false;
243     js["Error"] = "Internal schedules error (2)";
244     return true;
245   }
246
247   Json::Value jsevents(Json::arrayValue);
248
249   for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
250   {
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
254
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);
263   }
264
265   js["Result"] = true;
266   js["Events"] = jsevents;
267   return true;
268 }
269
270 bool VDRClient::getscheduleevent(PFMap& postFields, Json::Value& js) // RETHROWS
271 {
272   logger->debug("getscheduleevent");
273
274   int channelNumber = getVarInt(postFields, "channelnumber");
275   int eventID = getVarInt(postFields, "eventid");
276
277   const cEvent* event = getEvent(js, channelNumber, eventID, 0);
278   if (!event)
279   {
280     js["Result"] = false;
281     return true;
282   }
283
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();
293
294   js["Result"] = true;
295   js["Event"] = oneEvent;
296   return true;
297 }
298
299 bool VDRClient::epgdownload(PFMap& postFields, Json::Value& js)
300 {
301   logger->debug("epgdownload");
302
303   cSchedulesLock MutexLock;
304   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
305
306   if (!Schedules)
307   {
308     logger->error("epgdownload: Could not get Schedules object");
309     js["Result"] = false;
310     js["Error"] = "Internal schedules error (1)";
311     return true;
312   }
313
314   Json::Value jsevents(Json::arrayValue);
315
316   for (cChannel* channel = Channels.First(); channel; channel = Channels.Next(channel))
317   {
318     if (channel->GroupSep()) continue;
319
320     const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
321     if (!Schedule) continue;
322
323
324     for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
325     {
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);
337     }
338   }
339
340   js["Result"] = true;
341   js["Events"] = jsevents;
342   return true;
343 }
344
345 bool VDRClient::epgsearch(PFMap& postFields, Json::Value& js) // RETHROWS
346 {
347   logger->debug("epgsearch");
348
349   std::string searchfor = getVarString(postFields, "searchfor");
350   logger->debug("epgsearch: search for: {}", searchfor);
351
352   cSchedulesLock MutexLock;
353   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
354
355   if (!Schedules)
356   {
357     logger->error("epgsearch: Could not get Schedules object");
358     js["Result"] = false;
359     js["Error"] = "Internal schedules error (1)";
360     return true;
361   }
362
363   Json::Value jsevents(Json::arrayValue);
364
365   for (cChannel* channel = Channels.First(); channel; channel = Channels.Next(channel))
366   {
367     if (channel->GroupSep()) continue;
368
369     const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
370     if (!Schedule) continue;
371
372     bool foundtitle;
373     bool founddescription;
374     for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
375     {
376       foundtitle = false;
377       founddescription = false;
378
379       if (event->Title() && strcasestr(event->Title(), searchfor.c_str())) foundtitle = true;
380
381       if (!foundtitle && event->Description())
382         if (strcasestr(event->Description(), searchfor.c_str())) founddescription = true;
383
384       if (foundtitle || founddescription)
385       {
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;
398         else
399           oneEvent["FoundInDesc"] = false;
400         jsevents.append(oneEvent);
401       }
402     }
403   }
404
405   js["Result"] = true;
406   js["Events"] = jsevents;
407
408   logger->debug("epgsearch: search for: {} done", searchfor);
409
410   return true;
411 }
412
413 bool VDRClient::epgsearchsame(PFMap& postFields, Json::Value& js) // RETHROWS
414 {
415   logger->debug("epgsearchsame");
416
417   int atTime = getVarInt(postFields, "time");
418   std::string sTitle = getVarString(postFields, "title");
419
420   logger->debug("epgsearchsame: request time: {}, title: {}", atTime, sTitle);
421
422   cSchedulesLock SchedulesLock;
423   const cSchedules *schedules = cSchedules::Schedules(SchedulesLock);
424   if (!schedules)
425   {
426     js["Result"] = false;
427     return true;
428   }
429
430   Json::Value jsevents(Json::arrayValue);
431
432   const cEvent *event;
433   for (cSchedule *schedule = schedules->First(); (schedule != NULL); schedule = schedules->Next(schedule))
434   {
435     event = schedule->GetEventAround(atTime);
436     if (!event) continue; // nothing found on this schedule(channel)
437
438     if (!strcmp(event->Title(), sTitle.c_str()))
439     {
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);
450     }
451   }
452
453   js["Events"] = jsevents;
454   js["Result"] = true;
455   return true;
456 }
457
458 bool VDRClient::tunersstatus(PFMap& postFields, Json::Value& js)
459 {
460   logger->debug("tunerstatus");
461
462   js["NumDevices"] = cDevice::NumDevices();
463
464   Json::Value jsdevices(Json::arrayValue);
465
466   for (int i = 0; i < cDevice::NumDevices(); i++)
467   {
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();
474
475     const cChannel* cchannel = d->GetCurrentlyTunedTransponder();
476     if (cchannel)
477     {
478       oneDevice["Frequency"] = cchannel->Frequency();
479       oneDevice["SignalStrength"] = d->SignalStrength();
480       oneDevice["SignalQuality"] = d->SignalQuality();
481
482     }
483     else
484     {
485       oneDevice["Frequency"] = 0;
486       oneDevice["SignalStrength"] = 0;
487       oneDevice["SignalQuality"] = 0;
488     }
489
490     jsdevices.append(oneDevice);
491   }
492
493   js["Devices"] = jsdevices;
494
495
496   Json::Value jstimers(Json::arrayValue);
497   int numTimers = Timers.Count();
498   cTimer* timer;
499   for (int i = 0; i < numTimers; i++)
500   {
501     timer = Timers.Get(i);
502
503     if (timer->Recording())
504     {
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();
510
511       cRecordControl* crc = cRecordControls::GetRecordControl(timer);
512       if (crc)
513       {
514         cDevice* crcd = crc->Device();
515         oneTimer["DeviceNumber"] = crcd->DeviceNumber();
516       }
517       else
518       {
519         oneTimer["DeviceNumber"] = Json::Value::null;
520       }
521
522       const cChannel* channel = timer->Channel();
523       if (channel)
524       {
525         oneTimer["ChannelName"] = channel->Name();
526       }
527       else
528       {
529         oneTimer["ChannelName"] = Json::Value::null;
530       }
531
532       jstimers.append(oneTimer);
533     }
534
535   }
536   js["CurrentRecordings"] = jstimers;
537
538
539   js["Result"] = true;
540   return true;
541 }
542
543 bool VDRClient::timerset(PFMap& postFields, Json::Value& js) // RETHROWS
544 {
545   logger->debug("timerset");
546
547   std::string sTimerString = getVarString(postFields, "timerstring");
548
549   logger->debug("timerset: '{}'", sTimerString);
550   cTimer *timer = new cTimer;
551   if (!timer->Parse(sTimerString.c_str()))
552   {
553     delete timer;
554     js["Result"] = false;
555     js["Error"] = "Failed to parse timer request details";
556     return true;
557   }
558
559   cTimer *t = Timers.GetTimer(timer);
560   if (t)
561   {
562     delete timer;
563     js["Result"] = false;
564     js["Error"] = "Timer already exists";
565     return true;
566   }
567
568   Timers.Add(timer);
569   Timers.SetModified();
570   js["Result"] = true;
571   return true;
572 }
573
574 bool VDRClient::recinfo(PFMap& postFields, Json::Value& js) // RETHROWS
575 {
576   logger->debug("recinfo");
577
578   std::string reqfilename = getVarString(postFields, "filename");
579   logger->debug("recinfo: {}", reqfilename);
580
581   cRecordings Recordings;
582   Recordings.Load(); // probably have to do this
583   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
584
585   if (!recording)
586   {
587     logger->error("recinfo: recinfo found no recording");
588     js["Result"] = false;
589     return true;
590   }
591
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();
599
600   js["CurrentlyRecordingStart"] = 0;
601   js["CurrentlyRecordingStop"] = 0;
602   cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
603   if (rc)
604   {
605     js["CurrentlyRecordingStart"] = (Json::UInt)rc->Timer()->StartTime();
606     js["CurrentlyRecordingStop"] = (Json::UInt)rc->Timer()->StopTime();
607   }
608
609   js["ResumePoint"] = 0;
610
611   const cRecordingInfo *info = recording->Info();
612   if (info)
613   {
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;
618
619     const cComponents* components = info->Components();
620     if (!components)
621     {
622       js["Components"] = Json::Value::null;
623     }
624     else
625     {
626       Json::Value jscomponents;
627
628       tComponent* component;
629       for (int i = 0; i < components->NumComponents(); i++)
630       {
631         component = components->Component(i);
632
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);
639       }
640
641       js["Components"] = jscomponents;
642     }
643
644     cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
645     if (ResumeFile.Read() >= 0) js["ResumePoint"] = floor(ResumeFile.Read() / info->FramesPerSecond());
646   }
647
648   js["Result"] = true;
649   return true;
650 }
651
652 bool VDRClient::recstop(PFMap& postFields, Json::Value& js) // RETHROWS
653 {
654   logger->debug("recstop");
655
656   std::string reqfilename = getVarString(postFields, "filename");
657   logger->debug("recstop: {}", reqfilename);
658
659   cRecordings Recordings;
660   Recordings.Load(); // probably have to do this
661   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
662
663   if (!recording)
664   {
665     logger->error("recstop: recstop found no recording");
666     js["Result"] = false;
667     return true;
668   }
669
670   cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
671   if (!rc)
672   {
673     logger->error("recstop: not currently recording");
674     js["Result"] = false;
675     return true;
676   }
677
678   if (Timers.BeingEdited())
679   {
680     logger->debug("recstop: timers being edited elsewhere");
681     js["Result"] = false;
682     return true;
683   }
684
685   cTimer* timer = rc->Timer();
686   if (!timer)
687   {
688     logger->error("recstop: timer not found");
689     js["Result"] = false;
690     return true;
691   }
692
693   timer->ClrFlags(tfActive);
694   Timers.SetModified();
695
696   js["Result"] = true;
697   return true;
698 }
699
700
701 bool VDRClient::recdel(PFMap& postFields, Json::Value& js) // RETHROWS
702 {
703   logger->debug("recdel");
704
705   std::string reqfilename = getVarString(postFields, "filename");
706   logger->debug("recdel: {}", reqfilename);
707
708   cRecordings Recordings;
709   Recordings.Load(); // probably have to do this
710   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
711
712   if (!recording)
713   {
714     js["Result"] = false;
715     js["Error"] = "Could not find recording to delete";
716     return true;
717   }
718
719   logger->debug("recdel: Deleting recording: {}", recording->Name());
720   cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
721   if (rc)
722   {
723     js["Result"] = false;
724     js["Error"] = "This recording is still recording.. ho ho";
725     return true;
726   }
727
728   if (recording->Delete())
729   {
730     ::Recordings.DelByName(recording->FileName());
731     js["Result"] = true;
732   }
733   else
734   {
735     js["Result"] = false;
736     js["Error"] = "Failed to delete recording";
737   }
738
739   return true;
740 }
741
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
748 {
749   cRecordings Recordings;
750   Recordings.Load();
751   cRecording* recordingObj = Recordings.GetByName(recordingName.c_str());
752   if (!recordingObj) throw 2;
753
754   cRecordControl *rc = cRecordControls::GetRecordControl(recordingObj->FileName());
755   if (rc) throw 3;
756
757   logger->debug("paths: recording: {}", recordingObj->Name());
758   logger->debug("paths: recording: {}", recordingObj->FileName());
759
760   const char* t = recordingObj->FileName();
761   dirNameFullPathDate = t;
762
763   int k, j, m;
764
765   // Find the datedirname
766   for(k = strlen(t) - 1; k >= 0; k--)
767   {
768     if (t[k] == '/')
769     {
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);
773       break;
774     }
775   }
776
777   // Find the titledirname
778
779   for(j = k-1; j >= 0; j--)
780   {
781     if (t[j] == '/')
782     {
783       logger->debug("recmoverename: l2: {}", k - j);
784       dirNameSingleTitle.assign(&t[j+1], k - j - 1);
785       logger->debug("paths: dirNameSingleTitle: '{}'", dirNameSingleTitle);
786       break;
787     }
788   }
789
790   // Find the foldername
791
792   const char* vidDirStr = cVideoDirectory::Name();
793   int vidDirStrLen = strlen(vidDirStr);
794
795   logger->debug("recmoverename: j = {}, strlenvd = {}", j, vidDirStrLen);
796   if (j > vidDirStrLen) // Rec is in a subfolder now
797   {
798     for(m = j-1; m >= 0; m--)
799     {
800       if (t[m] == '/')
801       {
802         logger->debug("recmoverename: l3: {}", j - m);
803         dirNameSingleFolder.assign(&t[m+1], j - m - 1);
804         logger->debug("paths: dirNameSingleFolder: '{}'", dirNameSingleFolder);
805         break;
806       }
807     }
808   }
809
810   dirNameFullPathTitle.assign(t, k);
811   logger->debug("paths: dirNameFullPathTitle: '{}'", dirNameFullPathTitle);
812 }
813
814 bool VDRClient::recrename(PFMap& postFields, Json::Value& js) // RETHROWS
815 {
816   logger->debug("recrename");
817
818   std::string fileNameToAffect = getVarString(postFields, "filename");
819   std::string requestedNewStr = getVarString(postFields, "newname");
820
821   std::string dirNameSingleDate;
822   std::string dirNameSingleTitle;
823   std::string dirNameSingleFolder;
824   std::string dirNameFullPathTitle;
825   std::string dirNameFullPathDate;
826
827   try
828   {
829     pathsForRecordingName(fileNameToAffect, dirNameSingleDate,
830                           dirNameSingleTitle, dirNameSingleFolder,
831                           dirNameFullPathTitle, dirNameFullPathDate);
832
833     char* requestedNewSingleTitle = (char*)malloc(requestedNewStr.size() + 1);
834     strcpy(requestedNewSingleTitle, requestedNewStr.c_str());
835     logger->debug("recmoverename: to: {}", requestedNewSingleTitle);
836
837     requestedNewSingleTitle = ExchangeChars(requestedNewSingleTitle, true);
838     if (!strlen(requestedNewSingleTitle)) throw 9;
839     logger->debug("recmoverename: EC: {}", requestedNewSingleTitle);
840
841     const char* vidDirStr = cVideoDirectory::Name();
842     logger->debug("recmoverename: viddir: {}", vidDirStr);
843
844     // Could be a new path - construct that first and test
845
846     std::string newDirNameFullPathTitle = vidDirStr;
847     newDirNameFullPathTitle.append("/");
848
849     if (!dirNameSingleFolder.empty())
850     {
851       newDirNameFullPathTitle.append(dirNameSingleFolder);
852       newDirNameFullPathTitle.append("/");
853     }
854
855     newDirNameFullPathTitle.append(requestedNewSingleTitle);
856     free(requestedNewSingleTitle);
857
858     logger->debug("recrename: NPT2: {}", newDirNameFullPathTitle);
859
860     struct stat dstat;
861     int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
862     if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
863     {
864       logger->debug("recrename: new path does not exist (1)");
865       int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
866       if (mkdirret != 0) throw 4;
867     }
868     else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
869     {
870       // Something exists but it's not a dir
871       throw 5;
872     }
873
874     // New path now created or was there already
875
876     std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
877     newDirNameFullPathDate.append(dirNameSingleDate);
878
879     logger->debug("recrename: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
880     if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
881
882     // Success. Test for remove old dir containter
883     rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
884
885     ::Recordings.Update();
886     js["Result"] = true;
887     js["NewRecordingFileName"] = newDirNameFullPathDate;
888   }
889   catch (int e)
890   {
891     js["Result"] = false;
892     if (e == 1)
893     {
894       logger->error("recrename: Bad parameters");
895       js["Error"] = "Bad request parameters";
896     }
897     else if (e == 2)
898     {
899       logger->error("recrename: Could not find recording to move");
900       js["Error"] = "Bad filename";
901     }
902     else if (e == 3)
903     {
904       logger->error("recrename: Could not move recording, it is still recording");
905       js["Error"] = "Cannot move recording in progress";
906     }
907     else if (e == 4)
908     {
909       logger->error("recrename: Failed to make new dir (1)");
910       js["Error"] = "Failed to create new directory (1)";
911     }
912     else if (e == 5)
913     {
914       logger->error("recrename: Something already exists? (1)");
915       js["Error"] = "Something already exists at the new path (1)";
916     }
917     else if (e == 8)
918     {
919       logger->error("recrename: Rename failed");
920       js["Error"] = "Rename failed";
921     }
922     else if (e == 9)
923     {
924       logger->error("recrename: ExchangeChars lost our string");
925       js["Error"] = "Rename failed";
926     }
927   }
928
929   return true;
930 }
931
932 bool VDRClient::recmove(PFMap& postFields, Json::Value& js) // RETHROWS
933 {
934   logger->debug("recmove");
935
936   std::string fileNameToAffect = getVarString(postFields, "filename");
937   std::string requestedNewStr = getVarString(postFields, "newpath");
938
939   std::string dirNameSingleDate;
940   std::string dirNameSingleTitle;
941   std::string dirNameSingleFolder;
942   std::string dirNameFullPathTitle;
943   std::string dirNameFullPathDate;
944
945   try
946   {
947     pathsForRecordingName(fileNameToAffect, dirNameSingleDate,
948                           dirNameSingleTitle, dirNameSingleFolder,
949                           dirNameFullPathTitle, dirNameFullPathDate);
950
951     char* requestedNewSinglePath = (char*)malloc(requestedNewStr.size() + 1);
952     strcpy(requestedNewSinglePath, requestedNewStr.c_str());
953     logger->debug("recmoverename: to: {}", requestedNewSinglePath);
954
955     requestedNewSinglePath = ExchangeChars(requestedNewSinglePath, true);
956     if (!strlen(requestedNewSinglePath)) throw 9;
957     logger->debug("recmoverename: EC: {}", requestedNewSinglePath);
958
959     const char* vidDirStr = cVideoDirectory::Name();
960     logger->debug("recmoverename: viddir: {}", vidDirStr);
961
962     // Could be a new path - construct that first and test
963
964     std::string newDirNameFullPathTitle = vidDirStr;
965     newDirNameFullPathTitle.append(requestedNewSinglePath);
966     free(requestedNewSinglePath);
967
968     logger->debug("recmove: NPT: {}", newDirNameFullPathTitle);
969
970     struct stat dstat;
971     int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
972     if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
973     {
974       logger->debug("recmove: new path does not exist (1)");
975       int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
976       if (mkdirret != 0) throw 4;
977     }
978     else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
979     {
980       // Something exists but it's not a dir
981       throw 5;
982     }
983
984     // New path now created or was there already
985
986     newDirNameFullPathTitle.append(dirNameSingleTitle);
987     logger->debug("recmove: {}", newDirNameFullPathTitle);
988
989     statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
990     if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
991     {
992       logger->debug("recmove: new dir does not exist (2)");
993       int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
994       if (mkdirret != 0) throw 6;
995     }
996     else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
997     {
998       // Something exists but it's not a dir
999       throw 7;
1000     }
1001
1002     // Ok, the directory container has been made, or it pre-existed.
1003
1004     std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
1005     newDirNameFullPathDate.append(dirNameSingleDate);
1006
1007     logger->debug("recmove: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
1008     if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
1009
1010     // Success. Test for remove old dir containter
1011     rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
1012
1013     // Test for remove old foldername
1014     if (!dirNameSingleFolder.empty())
1015     {
1016       std::string dirNameFullPathFolder = vidDirStr;
1017       dirNameFullPathFolder.append("/");
1018       dirNameFullPathFolder.append(dirNameSingleFolder);
1019
1020       logger->debug("recmove: oldfoldername: {}", dirNameFullPathFolder);
1021       /*
1022       DESCRIPTION
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
1026       */
1027       rmdir(dirNameFullPathFolder.c_str()); // can't do anything about a fail result at this point.
1028     }
1029
1030     ::Recordings.Update();
1031     js["Result"] = true;
1032     js["NewRecordingFileName"] = newDirNameFullPathDate;
1033   }
1034   catch (int e)
1035   {
1036     js["Result"] = false;
1037     if (e == 1)
1038     {
1039       logger->error("recmove: Bad parameters");
1040       js["Error"] = "Bad request parameters";
1041     }
1042     else if (e == 2)
1043     {
1044       logger->error("recmove: Could not find recording to move");
1045       js["Error"] = "Bad filename";
1046     }
1047     else if (e == 3)
1048     {
1049       logger->error("recmove: Could not move recording, it is still recording");
1050       js["Error"] = "Cannot move recording in progress";
1051     }
1052     else if (e == 4)
1053     {
1054       logger->error("recmove: Failed to make new dir (1)");
1055       js["Error"] = "Failed to create new directory (1)";
1056     }
1057     else if (e == 5)
1058     {
1059       logger->error("recmove: Something already exists? (1)");
1060       js["Error"] = "Something already exists at the new path (1)";
1061     }
1062     else if (e == 6)
1063     {
1064       logger->error("recmove: Failed to make new dir (2)");
1065       js["Error"] = "Failed to create new directory (2)";
1066     }
1067     else if (e == 7)
1068     {
1069       logger->error("recmove: Something already exists?");
1070       js["Error"] = "Something already exists at the new path";
1071     }
1072     else if (e == 8)
1073     {
1074       logger->error("recmove: Rename failed");
1075       js["Error"] = "Move failed";
1076     }
1077     else if (e == 9)
1078     {
1079       logger->error("recrename: ExchangeChars lost our string");
1080       js["Error"] = "Rename failed";
1081     }
1082   }
1083
1084   return true;
1085 }
1086
1087 bool VDRClient::timersetactive(PFMap& postFields, Json::Value& js) // RETHROWS
1088 {
1089   logger->debug("timersetactive");
1090
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");
1097
1098   logger->debug("timersetactive: {} {}:{}:{}:{}:{}", tNewActive, rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1099
1100   cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1101   if (timer)
1102   {
1103     if      (tNewActive == "true")  timer->SetFlags(tfActive);
1104     else if (tNewActive == "false") timer->ClrFlags(tfActive);
1105     else
1106     {
1107       js["Result"] = false;
1108       js["Error"] = "Bad request parameters";
1109       return true;
1110     }
1111
1112     js["Result"] = true;
1113     Timers.SetModified();
1114     return true;
1115   }
1116
1117   js["Result"] = false;
1118   js["Error"] = "Timer not found";
1119   return true;
1120 }
1121
1122 bool VDRClient::timerdel(PFMap& postFields, Json::Value& js) // RETHROWS
1123 {
1124   logger->debug("timerdel");
1125
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");
1131
1132   logger->debug("timerdel: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1133
1134   if (Timers.BeingEdited())
1135   {
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";
1139     return true;
1140   }
1141
1142   cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1143   if (timer)
1144   {
1145     if (timer->Recording())
1146     {
1147       logger->debug("timerdel: Unable to delete timer - timer is running");
1148       js["Result"] = false;
1149       js["Error"] = "Timer is running";
1150       return true;
1151     }
1152
1153     Timers.Del(timer);
1154     Timers.SetModified();
1155     js["Result"] = true;
1156     return true;
1157   }
1158
1159   js["Result"] = false;
1160   js["Error"] = "Timer not found";
1161   return true;
1162 }
1163
1164 bool VDRClient::timerisrecording(PFMap& postFields, Json::Value& js) // RETHROWS
1165 {
1166   logger->debug("timerisrecording");
1167
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");
1173
1174   logger->debug("timerisrecording: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1175
1176   cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1177   if (timer)
1178   {
1179     js["Recording"] = timer->Recording();
1180     js["Pending"] = timer->Pending();
1181     js["Result"] = true;
1182     return true;
1183   }
1184
1185   js["Result"] = false;
1186   js["Error"] = "Timer not found";
1187   return true;
1188 }
1189
1190 bool VDRClient::recresetresume(PFMap& postFields, Json::Value& js) // RETHROWS
1191 {
1192   logger->debug("recresetresume");
1193
1194   std::string reqfilename = getVarString(postFields, "filename");
1195   logger->debug("recresetresume: {}", reqfilename);
1196
1197   cRecordings Recordings;
1198   Recordings.Load(); // probably have to do this
1199   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
1200
1201   if (!recording)
1202   {
1203     js["Result"] = false;
1204     js["Error"] = "Could not find recording to reset resume";
1205     return true;
1206   }
1207
1208   logger->debug("recresetresume: Reset resume for: {}", recording->Name());
1209
1210   cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
1211   if (ResumeFile.Read() >= 0)
1212   {
1213     ResumeFile.Delete();
1214     js["Result"] = true;
1215     return true;
1216   }
1217   else
1218   {
1219     js["Result"] = false;
1220     js["Error"] = "Recording has no resume point";
1221     return true;
1222   }
1223 }
1224
1225 bool VDRClient::timeredit(PFMap& postFields, Json::Value& js) // RETHROWS
1226 {
1227   logger->debug("timeredit");
1228
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");
1238
1239   logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", oldName, oldActive, oldChannelID, oldDay, oldWeekDays, oldStartTime, oldStopTime, oldPriority, oldLifetime);
1240
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");
1250
1251   logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", newName, newActive, newChannelID, newDay, newWeekDays, newStartTime, newStopTime, newPriority, newLifetime);
1252
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());
1254   if (!timer)
1255   {
1256     js["Result"] = false;
1257     js["Error"] = "Timer not found";
1258     return true;
1259   }
1260
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.
1265
1266   time_t nstt = std::stoi(newStartTime);
1267   struct tm nstm;
1268   localtime_r(&nstt, &nstm);
1269   int nssf = (nstm.tm_hour * 100) + nstm.tm_min;
1270
1271   time_t nztt = std::stoi(newStopTime);
1272   struct tm nztm;
1273   localtime_r(&nztt, &nztm);
1274   int nzsf = (nztm.tm_hour * 100) + nztm.tm_min;
1275
1276   std::replace(newName.begin(), newName.end(), ':', '|');
1277
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() : "");
1282
1283   logger->debug("timeredit: new parse: {}", *parseBuffer);
1284
1285   bool parseResult = timer->Parse(*parseBuffer);
1286   if (!parseResult)
1287   {
1288     js["Result"] = false;
1289     js["Error"] = "Timer parsing failed";
1290     return true;
1291   }
1292
1293   if (timer->HasFlags(tfActive) != !(strcasecmp(newActive.c_str(), "true")))
1294   {
1295     logger->debug("timeredit: {} {} set new active: {}", timer->HasFlags(tfActive), !(strcasecmp(newActive.c_str(), "true")), newActive);
1296
1297     if (strcasecmp(newActive.c_str(), "true") == 0)
1298     {
1299       timer->SetFlags(tfActive);
1300     }
1301     else if (strcasecmp(newActive.c_str(), "false") == 0)
1302     {
1303       timer->ClrFlags(tfActive);
1304     }
1305     else
1306     {
1307       js["Result"] = false;
1308       js["Error"] = "Bad request parameters";
1309       return true;
1310     }
1311   }
1312
1313   js["Result"] = true;
1314   Timers.SetModified();
1315   return true;
1316 }
1317
1318 //////////////////////////////////////////////////////////////////////////////////////////////////
1319
1320 const cEvent* VDRClient::getEvent(Json::Value& js, int channelNumber, int eventID, int aroundTime)
1321 {
1322   cChannel* channel = NULL;
1323   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
1324   {
1325     if (channel->GroupSep()) continue;
1326     if (channel->Number() == channelNumber) break;
1327   }
1328
1329   if (!channel)
1330   {
1331     logger->error("getevent: Could not find requested channel: {}", channelNumber);
1332     js["Error"] = "Could not find channel";
1333     return NULL;
1334   }
1335
1336   cSchedulesLock MutexLock;
1337   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
1338   if (!Schedules)
1339   {
1340     logger->error("getevent: Could not find requested channel: {}", channelNumber);
1341     js["Error"] = "Internal schedules error (1)";
1342     return NULL;
1343   }
1344
1345   const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
1346   if (!Schedule)
1347   {
1348     logger->error("getevent: Could not find requested channel: {}", channelNumber);
1349     js["Error"] = "Internal schedules error (2)";
1350     return NULL;
1351   }
1352
1353   const cEvent* event = NULL;
1354   if (eventID)
1355     event = Schedule->GetEvent(eventID);
1356   else
1357     event = Schedule->GetEventAround(aroundTime);
1358
1359   if (!event)
1360   {
1361     logger->error("getevent: Could not find requested event: {}", eventID);
1362     js["Error"] = "Internal schedules error (3)";
1363     return NULL;
1364   }
1365
1366   return event;
1367 }
1368
1369 cTimer* VDRClient::findTimer(const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1370 {
1371   int numTimers = Timers.Count();
1372   cTimer* timer;
1373   for (int i = 0; i < numTimers; i++)
1374   {
1375     timer = Timers.Get(i);
1376
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);
1388
1389     if (
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))
1395        )
1396     {
1397       logger->debug("findtimer: found");
1398       return timer;
1399     }
1400   }
1401   logger->debug("findtimer: no timer found");
1402   return NULL;
1403 }
1404
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)
1406 {
1407   int numTimers = Timers.Count();
1408   cTimer* timer;
1409   logger->debug("findtimer2: {} {} {} {} {} {} {} {} {}", rName, rActive, rChannelID, rDay, rWeekDays, rStartTime, rStopTime, rPriority, rLifetime);
1410   for (int i = 0; i < numTimers; i++)
1411   {
1412     timer = Timers.Get(i);
1413
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());
1417
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))
1427        )
1428     {
1429       logger->debug("findtimer2: found");
1430       return timer;
1431     }
1432   }
1433   logger->debug("findtimer2: no timer found");
1434   return NULL;
1435 }
1436
1437 int VDRClient::getVarInt(PFMap& postFields, const char* paramName) // THROWS
1438 {
1439   auto i = postFields.find(paramName);
1440   if (i == postFields.end()) throw BadParamException(paramName);
1441   int r;
1442   try { r = std::stoi(i->second); }
1443   catch (std::exception e) { throw BadParamException(paramName); }
1444   return r;
1445 }
1446
1447 std::string VDRClient::getVarString(PFMap& postFields, const char* paramName) // THROWS
1448 {
1449   auto i = postFields.find(paramName);
1450   if (i == postFields.end()) throw BadParamException(paramName);
1451   if (i->second.empty()) throw BadParamException(paramName);
1452   return i->second;
1453 }