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