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