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