]> git.vomp.tv Git - jsonserver.git/blob - vdrclient.c
New include for spdlog 1.3.1
[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/plugin.h>
18 #include <vdr/videodir.h>
19 #include <vdr/recording.h>
20 #include <vdr/menu.h>
21 #include <vdr/timers.h>
22
23 VDRClient::VDRClient()
24 {
25   logger = spd::get("jsonserver_spdlog");
26 }
27
28 VDRClient::~VDRClient()
29 {
30   logger->debug("VDRClient destructor");
31 }
32
33 bool VDRClient::process(std::string& request, PFMap& postFields, std::string& returnString)
34 {
35   Json::Value returnJSON;
36   bool success = false;
37
38   try
39   {
40     if      (request == "gettime")           success = gettime(postFields, returnJSON);
41     else if (request == "diskstats")         success = diskstats(postFields, returnJSON);
42     else if (request == "channellist")       success = channellist(postFields, returnJSON);
43     else if (request == "reclist")           success = reclist(postFields, returnJSON);
44     else if (request == "timerlist")         success = timerlist(postFields, returnJSON);
45     else if (request == "epgdownload")       success = epgdownload(postFields, returnJSON);
46     else if (request == "tunersstatus")      success = tunersstatus(postFields, returnJSON);
47     else if (request == "epgfilterget")      success = epgfilterget(postFields, returnJSON);
48
49     else if (request == "channelschedule")   success = channelschedule(postFields, returnJSON);
50     else if (request == "getscheduleevent")  success = getscheduleevent(postFields, returnJSON);
51     else if (request == "epgsearch")         success = epgsearch(postFields, returnJSON);
52     else if (request == "epgsearchsame")     success = epgsearchsame(postFields, returnJSON);
53     else if (request == "epgsearchotherhalf")success = epgsearchotherhalf(postFields, returnJSON);
54     else if (request == "timerset")          success = timerset(postFields, returnJSON);
55     else if (request == "recinfo")           success = recinfo(postFields, returnJSON);
56     else if (request == "recstop")           success = recstop(postFields, returnJSON);
57     else if (request == "recdel")            success = recdel(postFields, returnJSON);
58     else if (request == "recrename")         success = recrename(postFields, returnJSON);
59     else if (request == "recmove")           success = recmove(postFields, returnJSON);
60     else if (request == "timersetactive")    success = timersetactive(postFields, returnJSON);
61     else if (request == "timerdel")          success = timerdel(postFields, returnJSON);
62     else if (request == "timerisrecording")  success = timerisrecording(postFields, returnJSON);
63     else if (request == "recresetresume")    success = recresetresume(postFields, returnJSON);
64     else if (request == "timeredit")         success = timeredit(postFields, returnJSON);
65     else if (request == "epgfilteradd")      success = epgfilteradd(postFields, returnJSON);
66     else if (request == "epgfilterdel")      success = epgfilterdel(postFields, returnJSON);
67   }
68   catch (BadParamException e)
69   {
70     logger->error("Bad parameter in call, paramName: {}", e.param);
71     returnJSON["Result"] = false;
72     returnJSON["Error"] = "Bad request parameter";
73     returnJSON["Detail"] = e.param;
74     success = true;
75   }
76
77   if (!success) return false;
78
79   Json::StyledWriter sw;
80   returnString = sw.write(returnJSON);
81   logger->debug("Done sw write");
82   return true;
83 }
84
85 bool VDRClient::gettime(PFMap& postFields, Json::Value& js)
86 {
87   logger->debug("get_time");
88
89   struct timeval tv;
90   gettimeofday(&tv, NULL);
91
92   js["Time"] = (Json::UInt64)tv.tv_sec;
93   js["MTime"] = (Json::UInt)(tv.tv_usec/1000);
94   js["Result"] = true;
95   return true;
96 }
97
98 bool VDRClient::diskstats(PFMap& postFields, Json::Value& js)
99 {
100   logger->debug("diskstats");
101
102   int FreeMB;
103   int UsedMB;
104   int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
105
106   js["FreeMiB"] = FreeMB;
107   js["UsedMiB"] = UsedMB;
108   js["Percent"] = Percent;
109   js["Result"] = true;
110   return true;
111 }
112
113 bool VDRClient::channellist(PFMap& postFields, Json::Value& js)
114 {
115   logger->debug("channellist");
116
117   Json::Value jschannels(Json::arrayValue);
118
119   for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
120   {
121     if (!channel->GroupSep())
122     {
123       Json::Value oneChannel;
124       oneChannel["ID"] = (const char *)channel->GetChannelID().ToString();
125       oneChannel["Number"] = channel->Number();
126       oneChannel["Name"] = channel->Name();
127       jschannels.append(oneChannel);
128     }
129   }
130   js["Channels"] = jschannels;
131
132   return true;
133 }
134
135 bool VDRClient::reclist(PFMap& postFields, Json::Value& js)
136 {
137   logger->debug("reclist");
138
139   Json::Value jsrecordings(Json::arrayValue);
140   cRecordings Recordings;
141   Recordings.Load();
142   for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording))
143   {
144     Json::Value oneRec;
145     oneRec["StartTime"] = (Json::UInt)recording->Start();
146     oneRec["Length"] = (Json::UInt)recording->LengthInSeconds();
147     oneRec["IsNew"] = recording->IsNew();
148     oneRec["Name"] = recording->Name();
149     oneRec["Filename"] = recording->FileName();
150     oneRec["FileSizeMB"] = recording->FileSizeMB();
151
152     cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
153     if (rc) oneRec["CurrentlyRecording"] = true;
154     else    oneRec["CurrentlyRecording"] = false;
155
156     jsrecordings.append(oneRec);
157   }
158   js["Recordings"] = jsrecordings;
159
160   return true;
161 }
162
163 bool VDRClient::timerlist(PFMap& postFields, Json::Value& js)
164 {
165   logger->debug("timerlist");
166
167   Json::Value jstimers(Json::arrayValue);
168
169   cTimer *timer;
170   int numTimers = Timers.Count();
171
172   for (int i = 0; i < numTimers; i++)
173   {
174     timer = Timers.Get(i);
175     Json::Value oneTimer;
176     oneTimer["Active"] = timer->HasFlags(tfActive);
177     oneTimer["Recording"] = timer->Recording();
178     oneTimer["Pending"] = timer->Pending();
179     oneTimer["Priority"] = timer->Priority();
180     oneTimer["Lifetime"] = timer->Lifetime();
181     oneTimer["ChannelNumber"] = timer->Channel()->Number();
182     oneTimer["ChannelID"] = (const char *)timer->Channel()->GetChannelID().ToString();
183     oneTimer["StartTime"] = (int)timer->StartTime();
184     oneTimer["StopTime"] = (int)timer->StopTime();
185     oneTimer["Day"] = (int)timer->Day();
186     oneTimer["WeekDays"] = timer->WeekDays();
187     oneTimer["Name"] = timer->File();
188
189     const cEvent* event = timer->Event();
190     if (event)
191     {
192       oneTimer["EventID"] = event->EventID();
193     }
194     else
195     {
196       int channelNumber = timer->Channel()->Number();
197       int aroundTime = timer->StartTime() + 1;
198       const cEvent* eventAround = getEvent(js, channelNumber, 0, aroundTime);
199       if (eventAround)
200       {
201         oneTimer["EventID"] = eventAround->EventID();
202       }
203       else
204       {
205         oneTimer["EventID"] = 0;
206       }
207     }
208
209     jstimers.append(oneTimer);
210   }
211
212   js["Timers"] = jstimers;
213   js["NumTuners"] = cDevice::NumDevices();
214   js["Result"] = true;
215   return true;
216 }
217
218 bool VDRClient::channelschedule(PFMap& postFields, Json::Value& js) // RETHROWS
219 {
220   logger->debug("channelschedule");
221   int channelNumber = getVarInt(postFields, "channelnumber");
222   int startTime = getVarInt(postFields, "starttime");
223   int duration = getVarInt(postFields, "duration");
224
225   cChannel* channel = NULL;
226   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
227   {
228     if (channel->GroupSep()) continue;
229     if (channel->Number() == channelNumber) break;
230   }
231
232   if (!channel)
233   {
234     logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
235     js["Result"] = false;
236     js["Error"] = "Could not find channel";
237     return true;
238   }
239
240   cSchedulesLock MutexLock;
241   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
242   if (!Schedules)
243   {
244     logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
245     js["Result"] = false;
246     js["Error"] = "Internal schedules error (1)";
247     return true;
248   }
249   const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
250   if (!Schedule)
251   {
252     logger->error("channelschedule: Could not find requested channel: {}", channelNumber);
253     js["Result"] = false;
254     js["Error"] = "Internal schedules error (2)";
255     return true;
256   }
257
258   Json::Value jsevents(Json::arrayValue);
259
260   for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
261   {
262     if ((event->StartTime() + event->Duration()) < time(NULL)) continue; //in the past filter
263     if ((event->StartTime() + event->Duration()) <= startTime) continue; //start time filter
264     if (event->StartTime() >= (startTime + duration)) continue;          //duration filter
265
266     Json::Value oneEvent;
267     oneEvent["ID"] = event->EventID();
268     oneEvent["Time"] = (Json::UInt)event->StartTime();
269     oneEvent["Duration"] = event->Duration();
270     oneEvent["Title"] = event->Title() ? event->Title() : "";
271     oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
272     oneEvent["HasTimer"] = event->HasTimer();
273     jsevents.append(oneEvent);
274   }
275
276   js["Result"] = true;
277   js["Events"] = jsevents;
278   return true;
279 }
280
281 bool VDRClient::getscheduleevent(PFMap& postFields, Json::Value& js) // RETHROWS
282 {
283   logger->debug("getscheduleevent");
284
285   int channelNumber = getVarInt(postFields, "channelnumber");
286   int eventID = getVarInt(postFields, "eventid");
287
288   const cEvent* event = getEvent(js, channelNumber, eventID, 0);
289   if (!event)
290   {
291     js["Result"] = false;
292     return true;
293   }
294
295   Json::Value oneEvent;
296   oneEvent["ID"] = event->EventID();
297   oneEvent["Time"] = (Json::UInt)event->StartTime();
298   oneEvent["Duration"] = event->Duration();
299   oneEvent["Title"] = event->Title() ? event->Title() : "";
300   oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
301   oneEvent["Description"] = event->Description() ? event->Description() : "";
302   oneEvent["HasTimer"] = event->HasTimer();
303   oneEvent["RunningStatus"] = event->RunningStatus();
304
305   js["Result"] = true;
306   js["Event"] = oneEvent;
307   return true;
308 }
309
310 bool VDRClient::epgdownload(PFMap& postFields, Json::Value& js)
311 {
312   logger->debug("epgdownload");
313
314   cSchedulesLock MutexLock;
315   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
316
317   if (!Schedules)
318   {
319     logger->error("epgdownload: Could not get Schedules object");
320     js["Result"] = false;
321     js["Error"] = "Internal schedules error (1)";
322     return true;
323   }
324
325   Json::Value jsevents(Json::arrayValue);
326
327   for (cChannel* channel = Channels.First(); channel; channel = Channels.Next(channel))
328   {
329     if (channel->GroupSep()) continue;
330
331     const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
332     if (!Schedule) continue;
333
334
335     for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
336     {
337       Json::Value oneEvent;
338       oneEvent["ChannelNumber"] = channel->Number();
339       oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
340       oneEvent["ID"] = event->EventID();
341       oneEvent["Time"] = (Json::UInt)event->StartTime();
342       oneEvent["Duration"] = event->Duration();
343       oneEvent["Title"] = event->Title() ? event->Title() : "";
344       oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
345       oneEvent["HasTimer"] = event->HasTimer();
346       oneEvent["Description"] = event->Description() ? event->Description() : "";
347       jsevents.append(oneEvent);
348     }
349   }
350
351   js["Result"] = true;
352   js["Events"] = jsevents;
353   return true;
354 }
355
356 bool VDRClient::epgsearch(PFMap& postFields, Json::Value& js) // RETHROWS
357 {
358   logger->debug("epgsearch");
359
360   std::string searchfor = getVarString(postFields, "searchfor");
361   logger->debug("epgsearch: search for: {}", searchfor);
362
363   cSchedulesLock MutexLock;
364   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
365
366   if (!Schedules)
367   {
368     logger->error("epgsearch: Could not get Schedules object");
369     js["Result"] = false;
370     js["Error"] = "Internal schedules error (1)";
371     return true;
372   }
373
374   Json::Value jsevents(Json::arrayValue);
375
376   for (cChannel* channel = Channels.First(); channel; channel = Channels.Next(channel))
377   {
378     if (channel->GroupSep()) continue;
379
380     const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
381     if (!Schedule) continue;
382
383     bool foundtitle;
384     bool founddescription;
385     for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
386     {
387       foundtitle = false;
388       founddescription = false;
389
390       if (event->Title() && strcasestr(event->Title(), searchfor.c_str())) foundtitle = true;
391
392       if (!foundtitle && event->Description())
393         if (strcasestr(event->Description(), searchfor.c_str())) founddescription = true;
394
395       if (foundtitle || founddescription)
396       {
397         Json::Value oneEvent;
398         oneEvent["ChannelNumber"] = channel->Number();
399         oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
400         oneEvent["ID"] = event->EventID();
401         oneEvent["Time"] = (Json::UInt)event->StartTime();
402         oneEvent["Duration"] = event->Duration();
403         oneEvent["Title"] = event->Title() ? event->Title() : "";
404         oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
405         oneEvent["HasTimer"] = event->HasTimer();
406         oneEvent["Description"] = event->Description() ? event->Description() : "";
407         if (founddescription)
408           oneEvent["FoundInDesc"] = true;
409         else
410           oneEvent["FoundInDesc"] = false;
411         jsevents.append(oneEvent);
412       }
413     }
414   }
415
416   js["Result"] = true;
417   js["Events"] = jsevents;
418
419   logger->debug("epgsearch: search for: {} done", searchfor);
420
421   return true;
422 }
423
424 bool VDRClient::epgsearchsame(PFMap& postFields, Json::Value& js) // RETHROWS
425 {
426   logger->debug("epgsearchsame");
427
428   int atTime = getVarInt(postFields, "time");
429   std::string sTitle = getVarString(postFields, "title");
430
431   logger->debug("epgsearchsame: request time: {}, title: {}", atTime, sTitle);
432
433   cSchedulesLock SchedulesLock;
434   const cSchedules *schedules = cSchedules::Schedules(SchedulesLock);
435   if (!schedules)
436   {
437     js["Result"] = false;
438     return true;
439   }
440
441   Json::Value jsevents(Json::arrayValue);
442
443   const cEvent *event;
444   for (cSchedule *schedule = schedules->First(); (schedule != NULL); schedule = schedules->Next(schedule))
445   {
446     event = schedule->GetEventAround(atTime);
447     if (!event) continue; // nothing found on this schedule(channel)
448
449     if (!strcmp(event->Title(), sTitle.c_str()))
450     {
451       Json::Value oneEvent;
452       oneEvent["ChannelID"] = (const char*)event->ChannelID().ToString();
453       oneEvent["ID"] = event->EventID();
454       oneEvent["Time"] = (Json::UInt)event->StartTime();
455       oneEvent["Duration"] = event->Duration();
456       oneEvent["Title"] = event->Title() ? event->Title() : "";
457       oneEvent["ShortText"] = event->ShortText() ? event->ShortText() : "";
458       oneEvent["HasTimer"] = event->HasTimer();
459       //oneEvent["Description"] = event->Description() ? event->Description() : "";
460       jsevents.append(oneEvent);
461     }
462   }
463
464   js["Events"] = jsevents;
465   js["Result"] = true;
466   return true;
467 }
468
469 bool VDRClient::epgsearchotherhalf(PFMap& postFields, Json::Value& js) // RETHROWS
470 {
471   logger->debug("epgsearchotherhalf");
472   int channelNumber = getVarInt(postFields, "channelnumber");
473   int eventID = getVarInt(postFields, "eventid");
474
475   cChannel* channel = NULL;
476   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
477   {
478     if (channel->GroupSep()) continue;
479     if (channel->Number() == channelNumber) break;
480   }
481
482   if (!channel)
483   {
484     logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
485     js["Result"] = false;
486     js["Error"] = "Could not find channel";
487     return true;
488   }
489
490   cSchedulesLock MutexLock;
491   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
492   if (!Schedules)
493   {
494     logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
495     js["Result"] = false;
496     js["Error"] = "Internal schedules error (1)";
497     return true;
498   }
499   const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
500   if (!Schedule)
501   {
502     logger->error("epgsearchotherhalf: Could not find requested channel: {}", channelNumber);
503     js["Result"] = false;
504     js["Error"] = "Internal schedules error (2)";
505     return true;
506   }
507
508   js["OtherEventFound"] = false;
509
510   const cEvent* eventM1 = NULL;
511   const cEvent* eventM2 = NULL;
512   const cEvent* otherEvent = NULL;
513
514   for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
515   {
516     if (event->EventID() == (unsigned long)eventID)
517     {
518       if ((eventM2 != NULL) && (!strcmp(eventM2->Title(), event->Title())))
519       {
520         otherEvent = eventM2;
521       }
522       else
523       {
524         const cEvent* eventP1 = Schedule->Events()->Next(event);
525         if (eventP1)
526         {
527           const cEvent* eventP2 = Schedule->Events()->Next(eventP1);
528
529           if (eventP2 && (!strcmp(eventP2->Title(), event->Title())))
530           {
531             otherEvent = eventP2;
532           }
533         }
534       }
535
536       if (otherEvent)
537       {
538         Json::Value oneEvent;
539         oneEvent["ID"] = otherEvent->EventID();
540         oneEvent["ChannelNumber"] = channel->Number();
541         oneEvent["Time"] = (Json::UInt)otherEvent->StartTime();
542         oneEvent["Duration"] = otherEvent->Duration();
543         oneEvent["Title"] = otherEvent->Title() ? otherEvent->Title() : "";
544         oneEvent["HasTimer"] = otherEvent->HasTimer();
545         js["Event"] = oneEvent;
546         js["OtherEventFound"] = true;
547       }
548
549       break;
550     }
551
552     eventM2 = eventM1;
553     eventM1 = event;
554   }
555
556   js["Result"] = true;
557   return true;
558 }
559
560 bool VDRClient::tunersstatus(PFMap& postFields, Json::Value& js)
561 {
562   logger->debug("tunerstatus");
563
564   js["NumDevices"] = cDevice::NumDevices();
565
566   Json::Value jsdevices(Json::arrayValue);
567
568   for (int i = 0; i < cDevice::NumDevices(); i++)
569   {
570     Json::Value oneDevice;
571     cDevice *d = cDevice::GetDevice(i);
572     oneDevice["Number"] = d->DeviceNumber();
573     oneDevice["Type"] = (const char*)d->DeviceType();
574     oneDevice["Name"] = (const char*)d->DeviceName();
575     oneDevice["IsPrimary"] = d->IsPrimaryDevice();
576
577     const cChannel* cchannel = d->GetCurrentlyTunedTransponder();
578     if (cchannel)
579     {
580       oneDevice["Frequency"] = cchannel->Frequency();
581       oneDevice["SignalStrength"] = d->SignalStrength();
582       oneDevice["SignalQuality"] = d->SignalQuality();
583
584     }
585     else
586     {
587       oneDevice["Frequency"] = 0;
588       oneDevice["SignalStrength"] = 0;
589       oneDevice["SignalQuality"] = 0;
590     }
591
592     jsdevices.append(oneDevice);
593   }
594
595   js["Devices"] = jsdevices;
596
597
598   Json::Value jstimers(Json::arrayValue);
599   int numTimers = Timers.Count();
600   cTimer* timer;
601   for (int i = 0; i < numTimers; i++)
602   {
603     timer = Timers.Get(i);
604
605     if (timer->Recording())
606     {
607       Json::Value oneTimer;
608       oneTimer["Recording"] = timer->Recording();
609       oneTimer["StartTime"] = (int)timer->StartTime();
610       oneTimer["StopTime"] = (int)timer->StopTime();
611       oneTimer["File"] = timer->File();
612
613       cRecordControl* crc = cRecordControls::GetRecordControl(timer);
614       if (crc)
615       {
616         cDevice* crcd = crc->Device();
617         oneTimer["DeviceNumber"] = crcd->DeviceNumber();
618       }
619       else
620       {
621         oneTimer["DeviceNumber"] = Json::Value::null;
622       }
623
624       const cChannel* channel = timer->Channel();
625       if (channel)
626       {
627         oneTimer["ChannelName"] = channel->Name();
628       }
629       else
630       {
631         oneTimer["ChannelName"] = Json::Value::null;
632       }
633
634       jstimers.append(oneTimer);
635     }
636
637   }
638   js["CurrentRecordings"] = jstimers;
639
640
641   js["Result"] = true;
642   return true;
643 }
644
645 bool VDRClient::timerset(PFMap& postFields, Json::Value& js) // RETHROWS
646 {
647   logger->debug("timerset");
648
649   std::string sTimerString = getVarString(postFields, "timerstring");
650
651   logger->debug("timerset: '{}'", sTimerString);
652   cTimer *timer = new cTimer;
653   if (!timer->Parse(sTimerString.c_str()))
654   {
655     delete timer;
656     js["Result"] = false;
657     js["Error"] = "Failed to parse timer request details";
658     return true;
659   }
660
661   cTimer *t = Timers.GetTimer(timer);
662   if (t)
663   {
664     delete timer;
665     js["Result"] = false;
666     js["Error"] = "Timer already exists";
667     return true;
668   }
669
670   Timers.Add(timer);
671   Timers.SetModified();
672   js["Result"] = true;
673   return true;
674 }
675
676 bool VDRClient::recinfo(PFMap& postFields, Json::Value& js) // RETHROWS
677 {
678   logger->debug("recinfo");
679
680   std::string reqfilename = getVarString(postFields, "filename");
681   logger->debug("recinfo: {}", reqfilename);
682
683   cRecordings Recordings;
684   Recordings.Load(); // probably have to do this
685   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
686
687   if (!recording)
688   {
689     logger->error("recinfo: recinfo found no recording");
690     js["Result"] = false;
691     return true;
692   }
693
694   js["IsNew"] = recording->IsNew();
695   js["LengthInSeconds"] = recording->LengthInSeconds();
696   js["FileSizeMB"] = recording->FileSizeMB();
697   js["Name"] = recording->Name() ? recording->Name() : Json::Value::null;
698   js["Priority"] = recording->Priority();
699   js["LifeTime"] = recording->Lifetime();
700   js["Start"] = (Json::UInt)recording->Start();
701
702   js["CurrentlyRecordingStart"] = 0;
703   js["CurrentlyRecordingStop"] = 0;
704   cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
705   if (rc)
706   {
707     js["CurrentlyRecordingStart"] = (Json::UInt)rc->Timer()->StartTime();
708     js["CurrentlyRecordingStop"] = (Json::UInt)rc->Timer()->StopTime();
709   }
710
711   js["ResumePoint"] = 0;
712
713   const cRecordingInfo *info = recording->Info();
714   if (info)
715   {
716     js["ChannelName"] = info->ChannelName() ? info->ChannelName() : Json::Value::null;
717     js["Title"] = info->Title() ? info->Title() : Json::Value::null;
718     js["ShortText"] = info->ShortText() ? info->ShortText() : Json::Value::null;
719     js["Description"] = info->Description() ? info->Description() : Json::Value::null;
720
721     const cComponents* components = info->Components();
722     if (!components)
723     {
724       js["Components"] = Json::Value::null;
725     }
726     else
727     {
728       Json::Value jscomponents;
729
730       tComponent* component;
731       for (int i = 0; i < components->NumComponents(); i++)
732       {
733         component = components->Component(i);
734
735         Json::Value oneComponent;
736         oneComponent["Stream"] = component->stream;
737         oneComponent["Type"] = component->type;
738         oneComponent["Language"] = component->language ? component->language : Json::Value::null;
739         oneComponent["Description"] = component->description ? component->description : Json::Value::null;
740         jscomponents.append(oneComponent);
741       }
742
743       js["Components"] = jscomponents;
744     }
745
746     cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
747     if (ResumeFile.Read() >= 0) js["ResumePoint"] = floor(ResumeFile.Read() / info->FramesPerSecond());
748   }
749
750   js["Result"] = true;
751   return true;
752 }
753
754 bool VDRClient::recstop(PFMap& postFields, Json::Value& js) // RETHROWS
755 {
756   logger->debug("recstop");
757
758   std::string reqfilename = getVarString(postFields, "filename");
759   logger->debug("recstop: {}", reqfilename);
760
761   cRecordings Recordings;
762   Recordings.Load(); // probably have to do this
763   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
764
765   if (!recording)
766   {
767     logger->error("recstop: recstop found no recording");
768     js["Result"] = false;
769     return true;
770   }
771
772   cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
773   if (!rc)
774   {
775     logger->error("recstop: not currently recording");
776     js["Result"] = false;
777     return true;
778   }
779
780   if (Timers.BeingEdited())
781   {
782     logger->debug("recstop: timers being edited elsewhere");
783     js["Result"] = false;
784     return true;
785   }
786
787   cTimer* timer = rc->Timer();
788   if (!timer)
789   {
790     logger->error("recstop: timer not found");
791     js["Result"] = false;
792     return true;
793   }
794
795   timer->ClrFlags(tfActive);
796   Timers.SetModified();
797
798   js["Result"] = true;
799   return true;
800 }
801
802
803 bool VDRClient::recdel(PFMap& postFields, Json::Value& js) // RETHROWS
804 {
805   logger->debug("recdel");
806
807   std::string reqfilename = getVarString(postFields, "filename");
808   logger->debug("recdel: {}", reqfilename);
809
810   cRecordings Recordings;
811   Recordings.Load(); // probably have to do this
812   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
813
814   if (!recording)
815   {
816     js["Result"] = false;
817     js["Error"] = "Could not find recording to delete";
818     return true;
819   }
820
821   logger->debug("recdel: Deleting recording: {}", recording->Name());
822   cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
823   if (rc)
824   {
825     js["Result"] = false;
826     js["Error"] = "This recording is still recording.. ho ho";
827     return true;
828   }
829
830   if (recording->Delete())
831   {
832     ::Recordings.DelByName(recording->FileName());
833     js["Result"] = true;
834   }
835   else
836   {
837     js["Result"] = false;
838     js["Error"] = "Failed to delete recording";
839   }
840
841   return true;
842 }
843
844 void VDRClient::pathsForRecordingName(const std::string& recordingName,
845                            std::string& dirNameSingleDate,
846                            std::string& dirNameSingleTitle,
847                            std::string& dirNameSingleFolder,
848                            std::string& dirNameFullPathTitle,
849                            std::string& dirNameFullPathDate)  // throws int
850 {
851   cRecordings Recordings;
852   Recordings.Load();
853   cRecording* recordingObj = Recordings.GetByName(recordingName.c_str());
854   if (!recordingObj) throw 2;
855
856   cRecordControl *rc = cRecordControls::GetRecordControl(recordingObj->FileName());
857   if (rc) throw 3;
858
859   logger->debug("paths: recording: {}", recordingObj->Name());
860   logger->debug("paths: recording: {}", recordingObj->FileName());
861
862   const char* t = recordingObj->FileName();
863   dirNameFullPathDate = t;
864
865   int k, j, m;
866
867   // Find the datedirname
868   for(k = strlen(t) - 1; k >= 0; k--)
869   {
870     if (t[k] == '/')
871     {
872       logger->debug("recmoverename: l1: {}", strlen(&t[k+1]) + 1);
873       dirNameSingleDate.assign(&t[k+1], strlen(t) - k - 1);
874       logger->debug("paths: dirNameSingleDate: '{}'", dirNameSingleDate);
875       break;
876     }
877   }
878
879   // Find the titledirname
880
881   for(j = k-1; j >= 0; j--)
882   {
883     if (t[j] == '/')
884     {
885       logger->debug("recmoverename: l2: {}", k - j);
886       dirNameSingleTitle.assign(&t[j+1], k - j - 1);
887       logger->debug("paths: dirNameSingleTitle: '{}'", dirNameSingleTitle);
888       break;
889     }
890   }
891
892   // Find the foldername
893
894   const char* vidDirStr = cVideoDirectory::Name();
895   int vidDirStrLen = strlen(vidDirStr);
896
897   logger->debug("recmoverename: j = {}, strlenvd = {}", j, vidDirStrLen);
898   if (j > vidDirStrLen) // Rec is in a subfolder now
899   {
900     for(m = j-1; m >= 0; m--)
901     {
902       if (t[m] == '/')
903       {
904         logger->debug("recmoverename: l3: {}", j - m);
905         dirNameSingleFolder.assign(&t[m+1], j - m - 1);
906         logger->debug("paths: dirNameSingleFolder: '{}'", dirNameSingleFolder);
907         break;
908       }
909     }
910   }
911
912   dirNameFullPathTitle.assign(t, k);
913   logger->debug("paths: dirNameFullPathTitle: '{}'", dirNameFullPathTitle);
914 }
915
916 bool VDRClient::recrename(PFMap& postFields, Json::Value& js) // RETHROWS
917 {
918   logger->debug("recrename");
919
920   std::string fileNameToAffect = getVarString(postFields, "filename");
921   std::string requestedNewStr = getVarString(postFields, "newname");
922
923   std::string dirNameSingleDate;
924   std::string dirNameSingleTitle;
925   std::string dirNameSingleFolder;
926   std::string dirNameFullPathTitle;
927   std::string dirNameFullPathDate;
928
929   try
930   {
931     pathsForRecordingName(fileNameToAffect, dirNameSingleDate,
932                           dirNameSingleTitle, dirNameSingleFolder,
933                           dirNameFullPathTitle, dirNameFullPathDate);
934
935     char* requestedNewSingleTitle = (char*)malloc(requestedNewStr.size() + 1);
936     strcpy(requestedNewSingleTitle, requestedNewStr.c_str());
937     logger->debug("recmoverename: to: {}", requestedNewSingleTitle);
938
939     requestedNewSingleTitle = ExchangeChars(requestedNewSingleTitle, true);
940     if (!strlen(requestedNewSingleTitle)) throw 9;
941     logger->debug("recmoverename: EC: {}", requestedNewSingleTitle);
942
943     const char* vidDirStr = cVideoDirectory::Name();
944     logger->debug("recmoverename: viddir: {}", vidDirStr);
945
946     // Could be a new path - construct that first and test
947
948     std::string newDirNameFullPathTitle = vidDirStr;
949     newDirNameFullPathTitle.append("/");
950
951     if (!dirNameSingleFolder.empty())
952     {
953       newDirNameFullPathTitle.append(dirNameSingleFolder);
954       newDirNameFullPathTitle.append("/");
955     }
956
957     newDirNameFullPathTitle.append(requestedNewSingleTitle);
958     free(requestedNewSingleTitle);
959
960     logger->debug("recrename: NPT2: {}", newDirNameFullPathTitle);
961
962     struct stat dstat;
963     int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
964     if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
965     {
966       logger->debug("recrename: new path does not exist (1)");
967       int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
968       if (mkdirret != 0) throw 4;
969     }
970     else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
971     {
972       // Something exists but it's not a dir
973       throw 5;
974     }
975
976     // New path now created or was there already
977
978     std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
979     newDirNameFullPathDate.append(dirNameSingleDate);
980
981     logger->debug("recrename: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
982     if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
983
984     // Success. Test for remove old dir containter
985     rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
986
987     ::Recordings.Update();
988     js["Result"] = true;
989     js["NewRecordingFileName"] = newDirNameFullPathDate;
990   }
991   catch (int e)
992   {
993     js["Result"] = false;
994     if (e == 1)
995     {
996       logger->error("recrename: Bad parameters");
997       js["Error"] = "Bad request parameters";
998     }
999     else if (e == 2)
1000     {
1001       logger->error("recrename: Could not find recording to move");
1002       js["Error"] = "Bad filename";
1003     }
1004     else if (e == 3)
1005     {
1006       logger->error("recrename: Could not move recording, it is still recording");
1007       js["Error"] = "Cannot move recording in progress";
1008     }
1009     else if (e == 4)
1010     {
1011       logger->error("recrename: Failed to make new dir (1)");
1012       js["Error"] = "Failed to create new directory (1)";
1013     }
1014     else if (e == 5)
1015     {
1016       logger->error("recrename: Something already exists? (1)");
1017       js["Error"] = "Something already exists at the new path (1)";
1018     }
1019     else if (e == 8)
1020     {
1021       logger->error("recrename: Rename failed");
1022       js["Error"] = "Rename failed";
1023     }
1024     else if (e == 9)
1025     {
1026       logger->error("recrename: ExchangeChars lost our string");
1027       js["Error"] = "Rename failed";
1028     }
1029   }
1030
1031   return true;
1032 }
1033
1034 bool VDRClient::recmove(PFMap& postFields, Json::Value& js) // RETHROWS
1035 {
1036   logger->debug("recmove");
1037
1038   std::string fileNameToAffect = getVarString(postFields, "filename");
1039   std::string requestedNewStr = getVarString(postFields, "newpath");
1040
1041   std::string dirNameSingleDate;
1042   std::string dirNameSingleTitle;
1043   std::string dirNameSingleFolder;
1044   std::string dirNameFullPathTitle;
1045   std::string dirNameFullPathDate;
1046
1047   try
1048   {
1049     pathsForRecordingName(fileNameToAffect, dirNameSingleDate,
1050                           dirNameSingleTitle, dirNameSingleFolder,
1051                           dirNameFullPathTitle, dirNameFullPathDate);
1052
1053     char* requestedNewSinglePath = (char*)malloc(requestedNewStr.size() + 1);
1054     strcpy(requestedNewSinglePath, requestedNewStr.c_str());
1055     logger->debug("recmoverename: to: {}", requestedNewSinglePath);
1056
1057     requestedNewSinglePath = ExchangeChars(requestedNewSinglePath, true);
1058     if (!strlen(requestedNewSinglePath)) throw 9;
1059     logger->debug("recmoverename: EC: {}", requestedNewSinglePath);
1060
1061     const char* vidDirStr = cVideoDirectory::Name();
1062     logger->debug("recmoverename: viddir: {}", vidDirStr);
1063
1064     // Could be a new path - construct that first and test
1065
1066     std::string newDirNameFullPathTitle = vidDirStr;
1067     newDirNameFullPathTitle.append(requestedNewSinglePath);
1068     free(requestedNewSinglePath);
1069
1070     logger->debug("recmove: NPT: {}", newDirNameFullPathTitle);
1071
1072     struct stat dstat;
1073     int statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
1074     if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
1075     {
1076       logger->debug("recmove: new path does not exist (1)");
1077       int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
1078       if (mkdirret != 0) throw 4;
1079     }
1080     else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
1081     {
1082       // Something exists but it's not a dir
1083       throw 5;
1084     }
1085
1086     // New path now created or was there already
1087
1088     newDirNameFullPathTitle.append(dirNameSingleTitle);
1089     logger->debug("recmove: {}", newDirNameFullPathTitle);
1090
1091     statret = stat(newDirNameFullPathTitle.c_str(), &dstat);
1092     if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
1093     {
1094       logger->debug("recmove: new dir does not exist (2)");
1095       int mkdirret = mkdir(newDirNameFullPathTitle.c_str(), 0755);
1096       if (mkdirret != 0) throw 6;
1097     }
1098     else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR)))
1099     {
1100       // Something exists but it's not a dir
1101       throw 7;
1102     }
1103
1104     // Ok, the directory container has been made, or it pre-existed.
1105
1106     std::string newDirNameFullPathDate = newDirNameFullPathTitle + "/";
1107     newDirNameFullPathDate.append(dirNameSingleDate);
1108
1109     logger->debug("recmove: doing rename '{}' '{}'", dirNameFullPathDate, newDirNameFullPathDate);
1110     if (rename(dirNameFullPathDate.c_str(), newDirNameFullPathDate.c_str()) != 0) throw 8;
1111
1112     // Success. Test for remove old dir containter
1113     rmdir(dirNameFullPathTitle.c_str()); // can't do anything about a fail result at this point.
1114
1115     // Test for remove old foldername
1116     if (!dirNameSingleFolder.empty())
1117     {
1118       std::string dirNameFullPathFolder = vidDirStr;
1119       dirNameFullPathFolder.append("/");
1120       dirNameFullPathFolder.append(dirNameSingleFolder);
1121
1122       logger->debug("recmove: oldfoldername: {}", dirNameFullPathFolder);
1123       /*
1124       DESCRIPTION
1125       rmdir() deletes a directory, which must be empty.
1126       ENOTEMPTY - pathname  contains  entries  other than . and ..
1127       So, should be safe to call rmdir on non-empty dir
1128       */
1129       rmdir(dirNameFullPathFolder.c_str()); // can't do anything about a fail result at this point.
1130     }
1131
1132     ::Recordings.Update();
1133     js["Result"] = true;
1134     js["NewRecordingFileName"] = newDirNameFullPathDate;
1135   }
1136   catch (int e)
1137   {
1138     js["Result"] = false;
1139     if (e == 1)
1140     {
1141       logger->error("recmove: Bad parameters");
1142       js["Error"] = "Bad request parameters";
1143     }
1144     else if (e == 2)
1145     {
1146       logger->error("recmove: Could not find recording to move");
1147       js["Error"] = "Bad filename";
1148     }
1149     else if (e == 3)
1150     {
1151       logger->error("recmove: Could not move recording, it is still recording");
1152       js["Error"] = "Cannot move recording in progress";
1153     }
1154     else if (e == 4)
1155     {
1156       logger->error("recmove: Failed to make new dir (1)");
1157       js["Error"] = "Failed to create new directory (1)";
1158     }
1159     else if (e == 5)
1160     {
1161       logger->error("recmove: Something already exists? (1)");
1162       js["Error"] = "Something already exists at the new path (1)";
1163     }
1164     else if (e == 6)
1165     {
1166       logger->error("recmove: Failed to make new dir (2)");
1167       js["Error"] = "Failed to create new directory (2)";
1168     }
1169     else if (e == 7)
1170     {
1171       logger->error("recmove: Something already exists?");
1172       js["Error"] = "Something already exists at the new path";
1173     }
1174     else if (e == 8)
1175     {
1176       logger->error("recmove: Rename failed");
1177       js["Error"] = "Move failed";
1178     }
1179     else if (e == 9)
1180     {
1181       logger->error("recrename: ExchangeChars lost our string");
1182       js["Error"] = "Rename failed";
1183     }
1184   }
1185
1186   return true;
1187 }
1188
1189 bool VDRClient::timersetactive(PFMap& postFields, Json::Value& js) // RETHROWS
1190 {
1191   logger->debug("timersetactive");
1192
1193   std::string rChannelID = getVarString(postFields, "ChannelID");
1194   std::string rName = getVarString(postFields, "Name");
1195   std::string rStartTime = getVarString(postFields, "StartTime");
1196   std::string rStopTime = getVarString(postFields, "StopTime");
1197   std::string rWeekDays = getVarString(postFields, "WeekDays");
1198   std::string tNewActive = getVarString(postFields, "SetActive");
1199
1200   logger->debug("timersetactive: {} {}:{}:{}:{}:{}", tNewActive, rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1201
1202   cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1203   if (timer)
1204   {
1205     if      (tNewActive == "true")  timer->SetFlags(tfActive);
1206     else if (tNewActive == "false") timer->ClrFlags(tfActive);
1207     else
1208     {
1209       js["Result"] = false;
1210       js["Error"] = "Bad request parameters";
1211       return true;
1212     }
1213
1214     js["Result"] = true;
1215     Timers.SetModified();
1216     return true;
1217   }
1218
1219   js["Result"] = false;
1220   js["Error"] = "Timer not found";
1221   return true;
1222 }
1223
1224 bool VDRClient::timerdel(PFMap& postFields, Json::Value& js) // RETHROWS
1225 {
1226   logger->debug("timerdel");
1227
1228   std::string rChannelID = getVarString(postFields, "ChannelID");
1229   std::string rName = getVarString(postFields, "Name");
1230   std::string rStartTime = getVarString(postFields, "StartTime");
1231   std::string rStopTime = getVarString(postFields, "StopTime");
1232   std::string rWeekDays = getVarString(postFields, "WeekDays");
1233
1234   logger->debug("timerdel: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1235
1236   if (Timers.BeingEdited())
1237   {
1238     logger->debug("timerdel: Unable to delete timer - timers being edited at VDR");
1239     js["Result"] = false;
1240     js["Error"] = "Timers being edited at VDR";
1241     return true;
1242   }
1243
1244   cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1245   if (timer)
1246   {
1247     if (timer->Recording())
1248     {
1249       logger->debug("timerdel: Unable to delete timer - timer is running");
1250       js["Result"] = false;
1251       js["Error"] = "Timer is running";
1252       return true;
1253     }
1254
1255     Timers.Del(timer);
1256     Timers.SetModified();
1257     js["Result"] = true;
1258     return true;
1259   }
1260
1261   js["Result"] = false;
1262   js["Error"] = "Timer not found";
1263   return true;
1264 }
1265
1266 bool VDRClient::timerisrecording(PFMap& postFields, Json::Value& js) // RETHROWS
1267 {
1268   logger->debug("timerisrecording");
1269
1270   std::string rChannelID = getVarString(postFields, "ChannelID");
1271   std::string rName = getVarString(postFields, "Name");
1272   std::string rStartTime = getVarString(postFields, "StartTime");
1273   std::string rStopTime = getVarString(postFields, "StopTime");
1274   std::string rWeekDays = getVarString(postFields, "WeekDays");
1275
1276   logger->debug("timerisrecording: {}:{}:{}:{}:{}", rChannelID, rName, rStartTime, rStopTime, rWeekDays);
1277
1278   cTimer* timer = findTimer(rChannelID.c_str(), rName.c_str(), rStartTime.c_str(), rStopTime.c_str(), rWeekDays.c_str());
1279   if (timer)
1280   {
1281     js["Recording"] = timer->Recording();
1282     js["Pending"] = timer->Pending();
1283     js["Result"] = true;
1284     return true;
1285   }
1286
1287   js["Result"] = false;
1288   js["Error"] = "Timer not found";
1289   return true;
1290 }
1291
1292 bool VDRClient::recresetresume(PFMap& postFields, Json::Value& js) // RETHROWS
1293 {
1294   logger->debug("recresetresume");
1295
1296   std::string reqfilename = getVarString(postFields, "filename");
1297   logger->debug("recresetresume: {}", reqfilename);
1298
1299   cRecordings Recordings;
1300   Recordings.Load(); // probably have to do this
1301   cRecording *recording = Recordings.GetByName(reqfilename.c_str());
1302
1303   if (!recording)
1304   {
1305     js["Result"] = false;
1306     js["Error"] = "Could not find recording to reset resume";
1307     return true;
1308   }
1309
1310   logger->debug("recresetresume: Reset resume for: {}", recording->Name());
1311
1312   cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
1313   if (ResumeFile.Read() >= 0)
1314   {
1315     ResumeFile.Delete();
1316     js["Result"] = true;
1317     return true;
1318   }
1319   else
1320   {
1321     js["Result"] = false;
1322     js["Error"] = "Recording has no resume point";
1323     return true;
1324   }
1325 }
1326
1327 bool VDRClient::timeredit(PFMap& postFields, Json::Value& js) // RETHROWS
1328 {
1329   logger->debug("timeredit");
1330
1331   std::string oldName      = getVarString(postFields, "OldName");
1332   std::string oldActive    = getVarString(postFields, "OldActive");
1333   std::string oldChannelID = getVarString(postFields, "OldChannelID");
1334   std::string oldDay       = getVarString(postFields, "OldDay");
1335   std::string oldWeekDays  = getVarString(postFields, "OldWeekDays");
1336   std::string oldStartTime = getVarString(postFields, "OldStartTime");
1337   std::string oldStopTime  = getVarString(postFields, "OldStopTime");
1338   std::string oldPriority  = getVarString(postFields, "OldPriority");
1339   std::string oldLifetime  = getVarString(postFields, "OldLifetime");
1340
1341   logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", oldName, oldActive, oldChannelID, oldDay, oldWeekDays, oldStartTime, oldStopTime, oldPriority, oldLifetime);
1342
1343   std::string newName      = getVarString(postFields, "NewName");
1344   std::string newActive    = getVarString(postFields, "NewActive");
1345   std::string newChannelID = getVarString(postFields, "NewChannelID");
1346   std::string newDay       = getVarString(postFields, "NewDay");
1347   std::string newWeekDays  = getVarString(postFields, "NewWeekDays");
1348   std::string newStartTime = getVarString(postFields, "NewStartTime");
1349   std::string newStopTime  = getVarString(postFields, "NewStopTime");
1350   std::string newPriority  = getVarString(postFields, "NewPriority");
1351   std::string newLifetime  = getVarString(postFields, "NewLifetime");
1352
1353   logger->debug("timeredit: {} {} {} {} {} {} {} {} {}", newName, newActive, newChannelID, newDay, newWeekDays, newStartTime, newStopTime, newPriority, newLifetime);
1354
1355   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());
1356   if (!timer)
1357   {
1358     js["Result"] = false;
1359     js["Error"] = "Timer not found";
1360     return true;
1361   }
1362
1363   // Old version commented below (now removed) used to set each thing individually based on whether it had changed. However, since
1364   // the only way to change the timer channel appears to be with the cTimer::Parse function, might as well use that
1365   // for everything it supports
1366   // Except flags. Get current flags, set using Parse, then add/remove active as needed the other way.
1367
1368   time_t nstt = std::stoi(newStartTime);
1369   struct tm nstm;
1370   localtime_r(&nstt, &nstm);
1371   int nssf = (nstm.tm_hour * 100) + nstm.tm_min;
1372
1373   time_t nztt = std::stoi(newStopTime);
1374   struct tm nztm;
1375   localtime_r(&nztt, &nztm);
1376   int nzsf = (nztm.tm_hour * 100) + nztm.tm_min;
1377
1378   std::replace(newName.begin(), newName.end(), ':', '|');
1379
1380   // ? Convert to std::string?
1381   cString parseBuffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s",
1382                           timer->Flags(), newChannelID.c_str(), *(cTimer::PrintDay(std::stoi(newDay), std::stoi(newWeekDays), true)),
1383                           nssf, nzsf, std::stoi(newPriority), std::stoi(newLifetime), newName.c_str(), timer->Aux() ? timer->Aux() : "");
1384
1385   logger->debug("timeredit: new parse: {}", *parseBuffer);
1386
1387   bool parseResult = timer->Parse(*parseBuffer);
1388   if (!parseResult)
1389   {
1390     js["Result"] = false;
1391     js["Error"] = "Timer parsing failed";
1392     return true;
1393   }
1394
1395   if (timer->HasFlags(tfActive) != !(strcasecmp(newActive.c_str(), "true")))
1396   {
1397     logger->debug("timeredit: {} {} set new active: {}", timer->HasFlags(tfActive), !(strcasecmp(newActive.c_str(), "true")), newActive);
1398
1399     if (strcasecmp(newActive.c_str(), "true") == 0)
1400     {
1401       timer->SetFlags(tfActive);
1402     }
1403     else if (strcasecmp(newActive.c_str(), "false") == 0)
1404     {
1405       timer->ClrFlags(tfActive);
1406     }
1407     else
1408     {
1409       js["Result"] = false;
1410       js["Error"] = "Bad request parameters";
1411       return true;
1412     }
1413   }
1414
1415   js["Result"] = true;
1416   Timers.SetModified();
1417   return true;
1418 }
1419
1420 bool VDRClient::epgfilteradd(PFMap& postFields, Json::Value& js) // RETHROWS
1421 {
1422   std::string channel = getVarString(postFields, "channel");
1423   std::string programme = getVarString(postFields, "programme");
1424
1425   logger->debug("epgFilterAdd: {} {}", channel, programme);
1426
1427   libconfig::Config epgFilter;
1428   if (!loadEpgFilter(epgFilter))
1429   {
1430     js["Result"] = false;
1431     js["Error"] = "Error initialising EPG filter";
1432     return true;
1433   }
1434
1435   libconfig::Setting& setting = epgFilter.lookup("filters");
1436   libconfig::Setting& newPair = setting.add(libconfig::Setting::Type::TypeGroup);
1437   libconfig::Setting& newChannel = newPair.add("c", libconfig::Setting::Type::TypeString);
1438   newChannel = channel;
1439   libconfig::Setting& newProgramme = newPair.add("p", libconfig::Setting::Type::TypeString);
1440   newProgramme = programme;
1441
1442   if (!saveEpgFilter(epgFilter))
1443   {
1444     js["Result"] = false;
1445     js["Error"] = "Failed to save EPG filter";
1446     return true;
1447   }
1448
1449   js["Result"] = true;
1450   return true;
1451 }
1452
1453 bool VDRClient::epgfilterdel(PFMap& postFields, Json::Value& js) // RETHROWS
1454 {
1455   std::string channel = getVarString(postFields, "channel");
1456   std::string programme = getVarString(postFields, "programme");
1457
1458   logger->debug("epgFilterDel: {} {}", channel, programme);
1459
1460   libconfig::Config epgFilter;
1461   if (!loadEpgFilter(epgFilter))
1462   {
1463     js["Result"] = false;
1464     js["Error"] = "Error initialising EPG filter";
1465     return true;
1466   }
1467
1468   try
1469   {
1470     libconfig::Setting& setting = epgFilter.lookup("filters");
1471     int numFilters = setting.getLength();
1472     int x;
1473     for (x = 0; x < numFilters; x++)
1474     {
1475       libconfig::Setting& pair = setting[x];
1476       std::string c;
1477       std::string p;
1478       if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1479       {
1480         js["Result"] = false;
1481         js["Error"] = "Filter file format error";
1482         return true;
1483       }
1484       logger->debug("loop: {} {}", c, p);
1485       if ((c == channel) && (p == programme))
1486       {
1487         setting.remove(x);
1488
1489         if (!saveEpgFilter(epgFilter))
1490         {
1491           js["Result"] = false;
1492           js["Error"] = "Failed to save EPG filter";
1493           return true;
1494         }
1495
1496         logger->debug("Found and deleted: {} {}", c, p);
1497         js["Result"] = true;
1498         return true;
1499       }
1500     }
1501
1502     js["Result"] = false;
1503     js["Error"] = "Channel/Programme not found";
1504     return true;
1505   }
1506   catch (std::exception e)
1507   {
1508     js["Result"] = false;
1509     js["Error"] = "Unknown error";
1510     return true;
1511   }
1512 }
1513
1514 bool VDRClient::epgfilterget(PFMap& postFields, Json::Value& js) // RETHROWS
1515 {
1516   logger->debug("epgFilterget");
1517
1518   libconfig::Config epgFilter;
1519   if (!loadEpgFilter(epgFilter))
1520   {
1521     js["Result"] = false;
1522     js["Error"] = "Error initialising EPG filter";
1523     return true;
1524   }
1525
1526   try
1527   {
1528     libconfig::Setting& setting = epgFilter.lookup("filters");
1529     int numFilters = setting.getLength();
1530     int x;
1531     Json::Value jsfilters(Json::arrayValue);
1532     for (x = 0; x < numFilters; x++)
1533     {
1534       libconfig::Setting& pair = setting[x];
1535       std::string c;
1536       std::string p;
1537       if (!pair.lookupValue("c", c) || !pair.lookupValue("p", p))
1538       {
1539         js["Result"] = false;
1540         js["Error"] = "Filter file format error";
1541         return true;
1542       }
1543       logger->debug("loop: {} {}", c, p);
1544       Json::Value oneFilter;
1545       oneFilter["c"] = c;
1546       oneFilter["p"] = p;
1547       jsfilters.append(oneFilter);
1548     }
1549     js["EPGFilters"] = jsfilters;
1550   }
1551   catch (std::exception e)
1552   {
1553     js["Result"] = false;
1554     js["Error"] = "Unknown error";
1555     return true;
1556   }
1557
1558   js["Result"] = true;
1559   return true;
1560 }
1561
1562 //////////////////////////////////////////////////////////////////////////////////////////////////
1563
1564 const cEvent* VDRClient::getEvent(Json::Value& js, int channelNumber, int eventID, int aroundTime)
1565 {
1566   cChannel* channel = NULL;
1567   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
1568   {
1569     if (channel->GroupSep()) continue;
1570     if (channel->Number() == channelNumber) break;
1571   }
1572
1573   if (!channel)
1574   {
1575     logger->error("getevent: Could not find requested channel: {}", channelNumber);
1576     js["Error"] = "Could not find channel";
1577     return NULL;
1578   }
1579
1580   cSchedulesLock MutexLock;
1581   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
1582   if (!Schedules)
1583   {
1584     logger->error("getevent: Could not find requested channel: {}", channelNumber);
1585     js["Error"] = "Internal schedules error (1)";
1586     return NULL;
1587   }
1588
1589   const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
1590   if (!Schedule)
1591   {
1592     logger->error("getevent: Could not find requested channel: {}", channelNumber);
1593     js["Error"] = "Internal schedules error (2)";
1594     return NULL;
1595   }
1596
1597   const cEvent* event = NULL;
1598   if (eventID)
1599     event = Schedule->GetEvent(eventID);
1600   else
1601     event = Schedule->GetEventAround(aroundTime);
1602
1603   if (!event)
1604   {
1605     logger->error("getevent: Could not find requested event: {}", eventID);
1606     js["Error"] = "Internal schedules error (3)";
1607     return NULL;
1608   }
1609
1610   return event;
1611 }
1612
1613 cTimer* VDRClient::findTimer(const char* rChannelID, const char* rName, const char* rStartTime, const char* rStopTime, const char* rWeekDays)
1614 {
1615   int numTimers = Timers.Count();
1616   cTimer* timer;
1617   for (int i = 0; i < numTimers; i++)
1618   {
1619     timer = Timers.Get(i);
1620
1621     logger->debug("findtimer: current: {}", (const char*)timer->ToText(true));
1622     logger->debug("findtimer: {}", (const char*)timer->Channel()->GetChannelID().ToString());
1623     logger->debug("findtimer: {}", rChannelID);
1624     logger->debug("findtimer: {}", timer->File());
1625     logger->debug("findtimer: {}", rName);
1626     logger->debug("findtimer: {}", timer->StartTime());
1627     logger->debug("findtimer: {}", rStartTime);
1628     logger->debug("findtimer: {}", timer->StopTime());
1629     logger->debug("findtimer: {}", rStopTime);
1630     logger->debug("findtimer: {}", timer->WeekDays());
1631     logger->debug("findtimer: {}", rWeekDays);
1632
1633     if (
1634             (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1635          && (strcmp(timer->File(), rName) == 0)
1636          && (timer->StartTime() == atoi(rStartTime))
1637          && (timer->StopTime() == atoi(rStopTime))
1638          && (timer->WeekDays() == atoi(rWeekDays))
1639        )
1640     {
1641       logger->debug("findtimer: found");
1642       return timer;
1643     }
1644   }
1645   logger->debug("findtimer: no timer found");
1646   return NULL;
1647 }
1648
1649 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)
1650 {
1651   int numTimers = Timers.Count();
1652   cTimer* timer;
1653   logger->debug("findtimer2: {} {} {} {} {} {} {} {} {}", rName, rActive, rChannelID, rDay, rWeekDays, rStartTime, rStopTime, rPriority, rLifetime);
1654   for (int i = 0; i < numTimers; i++)
1655   {
1656     timer = Timers.Get(i);
1657
1658     logger->debug("findtimer2: search: {} {} {} {} {} {} {} {} {}", timer->File(), timer->HasFlags(tfActive), (const char*)timer->Channel()->GetChannelID().ToString(),
1659                                                                                         (int)timer->Day(), timer->WeekDays(), (int)timer->StartTime(), (int)timer->StopTime(),
1660                                                                                         timer->Priority(), timer->Lifetime());
1661
1662     if (    (strcmp(timer->File(), rName) == 0)
1663          && (timer->HasFlags(tfActive) == !(strcasecmp(rActive, "true")))
1664          && (strcmp(timer->Channel()->GetChannelID().ToString(), rChannelID) == 0)
1665          && (timer->Day() == atoi(rDay))
1666          && (timer->WeekDays() == atoi(rWeekDays))
1667          && (timer->StartTime() == atoi(rStartTime))
1668          && (timer->StopTime() == atoi(rStopTime))
1669          && (timer->Priority() == atoi(rPriority))
1670          && (timer->Lifetime() == atoi(rLifetime))
1671        )
1672     {
1673       logger->debug("findtimer2: found");
1674       return timer;
1675     }
1676   }
1677   logger->debug("findtimer2: no timer found");
1678   return NULL;
1679 }
1680
1681 int VDRClient::getVarInt(PFMap& postFields, const char* paramName) // THROWS
1682 {
1683   auto i = postFields.find(paramName);
1684   if (i == postFields.end()) throw BadParamException(paramName);
1685   int r;
1686   try { r = std::stoi(i->second); }
1687   catch (std::exception e) { throw BadParamException(paramName); }
1688   return r;
1689 }
1690
1691 std::string VDRClient::getVarString(PFMap& postFields, const char* paramName) // THROWS
1692 {
1693   auto i = postFields.find(paramName);
1694   if (i == postFields.end()) throw BadParamException(paramName);
1695   if (i->second.empty()) throw BadParamException(paramName);
1696   return i->second;
1697 }
1698
1699 bool VDRClient::loadEpgFilter(libconfig::Config& epgFilter)
1700 {
1701   const char* configDir = cPlugin::ConfigDirectory("jsonserver");
1702   if (!configDir)
1703   {
1704     logger->error("loadEpgFilter: Error: Could not get config dir from VDR");
1705     return false;
1706   }
1707
1708   std::string epgFilterFile(std::string(configDir) + std::string("/epgfilter.conf"));
1709   FILE* fp = fopen(epgFilterFile.c_str(), "a");
1710   if (!fp)
1711   {
1712     logger->error("loadEpgFilter: Error: Failed to fopen epgfilter.conf");
1713     return false;
1714   }
1715   fclose(fp);
1716
1717   try
1718   {
1719     epgFilter.readFile(epgFilterFile.c_str());
1720   }
1721   catch (const libconfig::FileIOException &fioex)
1722   {
1723     logger->error("loadEpgFilter: Error: Failed to read filter file");
1724     return false;
1725   }
1726   catch(const libconfig::ParseException &pex)
1727   {
1728     logger->error("loadEpgFilter: Parse error at {}: {} - {}", pex.getFile(), pex.getLine(), pex.getError());
1729     return false;
1730   }
1731
1732   try
1733   {
1734     libconfig::Setting& setting = epgFilter.lookup("filters");
1735
1736     if (!setting.isList())
1737     {
1738       logger->error("loadEpgFilter: Error: Failed to read filter file (2)");
1739       return false;
1740     }
1741   }
1742   catch (libconfig::SettingNotFoundException e)
1743   {
1744     libconfig::Setting& setting = epgFilter.getRoot();
1745     setting.add("filters", libconfig::Setting::Type::TypeList);
1746   }
1747
1748   return true;
1749 }
1750
1751 bool VDRClient::saveEpgFilter(libconfig::Config& epgFilter)
1752 {
1753   const char* configDir = cPlugin::ConfigDirectory("jsonserver");
1754   if (!configDir)
1755   {
1756     logger->error("saveEpgFilter: Error: Could not get config dir from VDR");
1757     return false;
1758   }
1759
1760   std::string epgFilterFile(std::string(configDir) + std::string("/epgfilter.conf"));
1761   try
1762   {
1763     epgFilter.writeFile(epgFilterFile.c_str());
1764     logger->debug("saveEpgFilter: EPG filter saved");
1765     return true;
1766   }
1767   catch (const libconfig::FileIOException& e)
1768   {
1769     logger->error("saveEpgFilter: Error: File write error");
1770     return false;
1771   }
1772 }