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