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