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