]> git.vomp.tv Git - vompserver.git/blob - mvpclient.c
Timer deleting: fix signedness issue
[vompserver.git] / mvpclient.c
1 /*
2     Copyright 2004-2005 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "mvpclient.h"
22
23 // This is here else it causes compile errors with something in libdvbmpeg
24 //#include <vdr/menu.h>
25
26 pthread_mutex_t threadClientMutex;
27 int MVPClient::nr_clients = 0;
28
29
30 MVPClient::MVPClient(Config* cfgBase, char* tconfigDirExtra, int tsocket)
31  : tcp(tsocket)
32 {
33   lp = NULL;
34   rp = NULL;
35   imageFile = 0;
36   recordingManager = NULL;
37   log = Log::getInstance();
38   loggedIn = false;
39   configDirExtra = tconfigDirExtra;
40   baseConfig = cfgBase;
41   incClients();
42 }
43
44 MVPClient::~MVPClient()
45 {
46   log->log("Client", Log::DEBUG, "MVP client destructor");
47   if (lp)
48   {
49     delete lp;
50     lp = NULL;
51   }
52   else if (rp)
53   {
54     writeResumeData();
55
56     delete rp;
57     delete recordingManager;
58     rp = NULL;
59     recordingManager = NULL;
60   }
61
62   if (loggedIn) cleanConfig();
63   decClients();
64 }
65
66 ULLONG MVPClient::ntohll(ULLONG a)
67 {
68   return htonll(a);
69 }
70
71 ULLONG MVPClient::htonll(ULLONG a)
72 {
73   #if BYTE_ORDER == BIG_ENDIAN
74     return a;
75   #else
76     ULLONG b = 0;
77
78     b = ((a << 56) & 0xFF00000000000000ULL)
79       | ((a << 40) & 0x00FF000000000000ULL)
80       | ((a << 24) & 0x0000FF0000000000ULL)
81       | ((a <<  8) & 0x000000FF00000000ULL)
82       | ((a >>  8) & 0x00000000FF000000ULL)
83       | ((a >> 24) & 0x0000000000FF0000ULL)
84       | ((a >> 40) & 0x000000000000FF00ULL)
85       | ((a >> 56) & 0x00000000000000FFULL) ;
86
87     return b;
88   #endif
89 }
90
91 cChannel* MVPClient::channelFromNumber(ULONG channelNumber)
92 {
93   cChannel* channel = NULL;
94
95   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
96   {
97     if (!channel->GroupSep())
98     {
99       log->log("Client", Log::DEBUG, "Looking for channel %lu::: number: %i name: '%s'", channelNumber, channel->Number(), channel->Name());
100
101       if (channel->Number() == (int)channelNumber)
102       {
103         int vpid = channel->Vpid();
104 #if VDRVERSNUM < 10300
105         int apid1 = channel->Apid1();
106 #else
107         int apid1 = channel->Apid(0);
108 #endif
109         log->log("Client", Log::DEBUG, "Found channel number %lu, vpid = %i, apid1 = %i", channelNumber, vpid, apid1);
110         return channel;
111       }
112     }
113   }
114
115   if (!channel)
116   {
117     log->log("Client", Log::DEBUG, "Channel not found");
118   }
119
120   return channel;
121 }
122
123 void MVPClient::writeResumeData()
124 {
125   config.setValueLong("ResumeData",
126                           (char*)rp->getCurrentRecording()->FileName(),
127                           rp->frameNumberFromPosition(rp->getLastPosition()) );
128 }
129
130 void MVPClient::sendULONG(ULONG ul)
131 {
132   UCHAR sendBuffer[8];
133   *(ULONG*)&sendBuffer[0] = htonl(4);
134   *(ULONG*)&sendBuffer[4] = htonl(ul);
135
136   tcp.sendPacket(sendBuffer, 8);
137   log->log("Client", Log::DEBUG, "written ULONG %lu", ul);
138 }
139
140 void MVPClientStartThread(void* arg)
141 {
142   MVPClient* m = (MVPClient*)arg;
143   m->run2();
144   // Nothing external to this class has a reference to it
145   // This is the end of the thread.. so delete m
146   delete m;
147   pthread_exit(NULL);
148 }
149
150 int MVPClient::run()
151 {
152   if (pthread_create(&runThread, NULL, (void*(*)(void*))MVPClientStartThread, (void *)this) == -1) return 0;
153   log->log("Client", Log::DEBUG, "MVPClient run success");
154   return 1;
155 }
156
157 void MVPClient::run2()
158 {
159   // Thread stuff
160   sigset_t sigset;
161   sigfillset(&sigset);
162   pthread_sigmask(SIG_BLOCK, &sigset, NULL);
163   pthread_detach(runThread);  // Detach
164
165   tcp.disableReadTimeout();
166
167   tcp.setSoKeepTime(3);
168   tcp.setNonBlocking();
169
170   UCHAR* buffer;
171   UCHAR* data;
172   int packetLength;
173   ULONG opcode;
174   int result = 0;
175
176   while(1)
177   {
178     log->log("Client", Log::DEBUG, "Waiting");
179     buffer = (UCHAR*)tcp.receivePacket();
180     log->log("Client", Log::DEBUG, "Received packet, length = %u", tcp.getDataLength());
181     if (buffer == NULL)
182     {
183       log->log("Client", Log::DEBUG, "Detected connection closed");
184       break;
185     }
186
187     packetLength = tcp.getDataLength() - 4;
188     opcode = ntohl(*(ULONG*)buffer);
189     data = buffer + 4;
190
191     if (!loggedIn && (opcode != 1))
192     {
193       free(buffer);
194       break;
195     }
196
197     log->log("Client", Log::DEBUG, "SwitchOp");
198     switch(opcode)
199     {
200       case 1:
201         result = processLogin(data, packetLength);
202         break;
203       case 2:
204         result = processGetRecordingsList(data, packetLength);
205         break;
206       case 3:
207         result = processDeleteRecording(data, packetLength);
208         break;
209       case 5:
210         result = processGetChannelsList(data, packetLength);
211         break;
212       case 6:
213         result = processStartStreamingChannel(data, packetLength);
214         break;
215       case 7:
216         result = processGetBlock(data, packetLength);
217         break;
218       case 8:
219         result = processStopStreaming(data, packetLength);
220         break;
221       case 9:
222         result = processStartStreamingRecording(data, packetLength);
223         break;
224       case 10:
225         result = processGetChannelSchedule(data, packetLength);
226         break;
227       case 11:
228         result = processConfigSave(data, packetLength);
229         break;
230       case 12:
231         result = processConfigLoad(data, packetLength);
232         break;
233       case 13:
234         result = processReScanRecording(data, packetLength);         // FIXME obselete
235         break;
236       case 14:
237         result = processGetTimers(data, packetLength);
238         break;
239       case 15:
240         result = processSetTimer(data, packetLength);
241         break;
242       case 16:
243         result = processPositionFromFrameNumber(data, packetLength);
244         break;
245       case 17:
246         result = processFrameNumberFromPosition(data, packetLength);
247         break;
248       case 18:
249         result = processMoveRecording(data, packetLength);
250         break;
251       case 19:
252         result = processGetIFrame(data, packetLength);
253         break;
254       case 20:
255         result = processGetRecInfo(data, packetLength);
256         break;
257       case 21:
258         result = processGetMarks(data, packetLength);
259         break;
260       case 22:
261         result = processGetChannelPids(data, packetLength);
262         break;
263       case 23:
264         result = processDeleteTimer(data, packetLength);
265         break;
266       case 30:
267         result = processGetMediaList(data, packetLength);
268         break;
269       case 31:
270         result = processGetPicture(data, packetLength);
271         break;
272       case 32:
273         result = processGetImageBlock(data, packetLength);
274         break;
275     }
276
277     free(buffer);
278     if (!result) break;
279   }
280 }
281
282 int MVPClient::processLogin(UCHAR* buffer, int length)
283 {
284   if (length != 6) return 0;
285
286   // Open the config
287
288   const char* configDir = cPlugin::ConfigDirectory(configDirExtra);
289   if (!configDir)
290   {
291     log->log("Client", Log::DEBUG, "No config dir!");
292     return 0;
293   }
294
295   char configFileName[PATH_MAX];
296   snprintf(configFileName, PATH_MAX, "%s/vomp-%02X-%02X-%02X-%02X-%02X-%02X.conf", configDir, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
297   config.init(configFileName);
298
299   // Send the login reply
300
301   time_t timeNow = time(NULL);
302   struct tm* timeStruct = localtime(&timeNow);
303   int timeOffset = timeStruct->tm_gmtoff;
304
305   UCHAR sendBuffer[12];
306   *(ULONG*)&sendBuffer[0] = htonl(8);
307   *(ULONG*)&sendBuffer[4] = htonl(timeNow);
308   *(signed int*)&sendBuffer[8] = htonl(timeOffset);
309
310   tcp.sendPacket(sendBuffer, 12);
311   log->log("Client", Log::DEBUG, "written login reply");
312
313   loggedIn = true;
314   return 1;
315 }
316
317 int MVPClient::processGetRecordingsList(UCHAR* data, int length)
318 {
319   UCHAR* sendBuffer = new UCHAR[50000]; // hope this is enough
320   int count = 4; // leave space for the packet length
321   char* point;
322
323
324   int FreeMB;
325   int Percent = VideoDiskSpace(&FreeMB);
326   int Total = (FreeMB / (100 - Percent)) * 100;
327
328   *(ULONG*)&sendBuffer[count] = htonl(Total);
329   count += sizeof(ULONG);
330   *(ULONG*)&sendBuffer[count] = htonl(FreeMB);
331   count += sizeof(ULONG);
332   *(ULONG*)&sendBuffer[count] = htonl(Percent);
333   count += sizeof(ULONG);
334
335
336   cRecordings Recordings;
337   Recordings.Load();
338
339   for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording))
340   {
341     if (count > 49000) break; // just how big is that hard disk?!
342     *(ULONG*)&sendBuffer[count] = htonl(recording->start);// + timeOffset);
343     count += 4;
344
345     point = (char*)recording->Name();
346     strcpy((char*)&sendBuffer[count], point);
347     count += strlen(point) + 1;
348
349     point = (char*)recording->FileName();
350     strcpy((char*)&sendBuffer[count], point);
351     count += strlen(point) + 1;
352   }
353
354   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
355
356   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
357
358   tcp.sendPacket(sendBuffer, count);
359   delete[] sendBuffer;
360   log->log("Client", Log::DEBUG, "Written list");
361
362   return 1;
363 }
364
365 int MVPClient::processDeleteRecording(UCHAR* data, int length)
366 {
367   // data is a pointer to the fileName string
368
369   cRecordings Recordings;
370   Recordings.Load(); // probably have to do this
371
372   cRecording* recording = Recordings.GetByName((char*)data);
373
374   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
375
376   if (recording)
377   {
378     log->log("Client", Log::DEBUG, "deleting recording: %s", recording->Name());
379
380     cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
381     if (!rc)
382     {
383       if (recording->Delete())
384       {
385         // Copy svdrp's way of doing this, see if it works
386 #if VDRVERSNUM > 10300
387         ::Recordings.DelByName(recording->FileName());
388 #endif
389         sendULONG(1);
390       }
391       else
392       {
393         sendULONG(2);
394       }
395     }
396     else
397     {
398       sendULONG(3);
399     }
400   }
401   else
402   {
403     sendULONG(4);
404   }
405
406   return 1;
407 }
408
409 int MVPClient::processMoveRecording(UCHAR* data, int length)
410 {
411   log->log("Client", Log::DEBUG, "Process move recording");
412   char* fileName = (char*)data;
413   char* newPath = NULL;
414
415   for (int k = 0; k < length; k++)
416   {
417     if (data[k] == '\0')
418     {
419       newPath = (char*)&data[k+1];
420       break;
421     }
422   }
423   if (!newPath) return 0;
424
425   cRecordings Recordings;
426   Recordings.Load(); // probably have to do this
427
428   cRecording* recording = Recordings.GetByName((char*)fileName);
429
430   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
431
432   if (recording)
433   {
434     cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
435     if (!rc)
436     {
437       log->log("Client", Log::DEBUG, "moving recording: %s", recording->Name());
438       log->log("Client", Log::DEBUG, "moving recording: %s", recording->FileName());
439       log->log("Client", Log::DEBUG, "to: %s", newPath);
440
441       const char* t = recording->FileName();
442
443       char* dateDirName = NULL;   int k;
444       char* titleDirName = NULL;  int j;
445
446       // Find the datedirname
447       for(k = strlen(t) - 1; k >= 0; k--)
448       {
449         if (t[k] == '/')
450         {
451           log->log("Client", Log::DEBUG, "l1: %i", strlen(&t[k+1]) + 1);
452           dateDirName = new char[strlen(&t[k+1]) + 1];
453           strcpy(dateDirName, &t[k+1]);
454           break;
455         }
456       }
457
458       // Find the titledirname
459
460       for(j = k-1; j >= 0; j--)
461       {
462         if (t[j] == '/')
463         {
464           log->log("Client", Log::DEBUG, "l2: %i", (k - j - 1) + 1);
465           titleDirName = new char[(k - j - 1) + 1];
466           memcpy(titleDirName, &t[j+1], k - j - 1);
467           titleDirName[k - j - 1] = '\0';
468           break;
469         }
470       }
471
472       log->log("Client", Log::DEBUG, "datedirname: %s", dateDirName);
473       log->log("Client", Log::DEBUG, "titledirname: %s", titleDirName);
474
475       log->log("Client", Log::DEBUG, "viddir: %s", VideoDirectory);
476
477       char* newContainer = new char[strlen(VideoDirectory) + strlen(newPath) + strlen(titleDirName) + 1];
478       log->log("Client", Log::DEBUG, "l10: %i", strlen(VideoDirectory) + strlen(newPath) + strlen(titleDirName) + 1);
479       sprintf(newContainer, "%s%s%s", VideoDirectory, newPath, titleDirName);
480
481       // FIXME Check whether this already exists before mkdiring it
482
483       log->log("Client", Log::DEBUG, "%s", newContainer);
484
485
486       struct stat dstat;
487       int statret = stat(newContainer, &dstat);
488       if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
489       {
490         log->log("Client", Log::DEBUG, "new dir does not exist");
491         int mkdirret = mkdir(newContainer, 0755);
492         if (mkdirret != 0)
493         {
494           delete[] dateDirName;
495           delete[] titleDirName;
496           delete[] newContainer;
497           sendULONG(5);
498           return 1;
499         }
500       }
501       else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR))) // Something exists but it's not a dir
502       {
503         delete[] dateDirName;
504         delete[] titleDirName;
505         delete[] newContainer;
506         sendULONG(5);
507         return 1;
508       }
509
510       // Ok, the directory container has been made, or it pre-existed.
511
512       char* newDir = new char[strlen(newContainer) + 1 + strlen(dateDirName) + 1];
513       sprintf(newDir, "%s/%s", newContainer, dateDirName);
514
515       log->log("Client", Log::DEBUG, "doing rename '%s' '%s'", t, newDir);
516       int renameret = rename(t, newDir);
517       if (renameret == 0)
518       {
519         // Success. Test for remove old dir containter
520         char* oldTitleDir = new char[k+1];
521         memcpy(oldTitleDir, t, k);
522         oldTitleDir[k] = '\0';
523         log->log("Client", Log::DEBUG, "len: %i, cp: %i, strlen: %i, oldtitledir: %s", k+1, k, strlen(oldTitleDir), oldTitleDir);
524         rmdir(oldTitleDir); // can't do anything about a fail result at this point.
525         delete[] oldTitleDir;
526       }
527
528       if (renameret == 0)
529       {
530 #if VDRVERSNUM > 10311
531         // Tell VDR
532         ::Recordings.Update();
533 #endif
534         // Success. Send a different packet from just a ulong
535         int totalLength = 4 + 4 + strlen(newDir) + 1;
536         UCHAR* sendBuffer = new UCHAR[totalLength];
537         *(ULONG*)&sendBuffer[0] = htonl(totalLength - 4);
538         *(ULONG*)&sendBuffer[4] = htonl(1); // success
539         strcpy((char*)&sendBuffer[8], newDir);
540         tcp.sendPacket(sendBuffer, totalLength);
541         delete[] sendBuffer;
542       }
543       else
544       {
545         sendULONG(5);
546       }
547
548       delete[] dateDirName;
549       delete[] titleDirName;
550       delete[] newContainer;
551       delete[] newDir;
552     }
553     else
554     {
555       sendULONG(3);
556     }
557   }
558   else
559   {
560     sendULONG(4);
561   }
562
563   return 1;
564 }
565
566 int MVPClient::processGetChannelsList(UCHAR* data, int length)
567 {
568   UCHAR* sendBuffer = new UCHAR[50000]; // FIXME hope this is enough
569   int count = 4; // leave space for the packet length
570   char* point;
571   ULONG type;
572
573   char* chanConfig = config.getValueString("General", "Channels");
574   int allChans = 1;
575   if (chanConfig) allChans = strcasecmp(chanConfig, "FTA only");
576
577   for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
578   {
579 #if VDRVERSNUM < 10300
580     if (!channel->GroupSep() && (!channel->Ca() || allChans))
581 #else
582     if (!channel->GroupSep() && (!channel->Ca(0) || allChans))
583 #endif
584     {
585       log->log("Client", Log::DEBUG, "name: '%s'", channel->Name());
586
587       if (channel->Vpid()) type = 1;
588 #if VDRVERSNUM < 10300
589       else type = 2;
590 #else
591       else if (channel->Apid(0)) type = 2;
592       else continue;
593 #endif
594
595       if (count > 49000) break;
596       *(ULONG*)&sendBuffer[count] = htonl(channel->Number());
597       count += 4;
598
599       *(ULONG*)&sendBuffer[count] = htonl(type);
600       count += 4;
601
602       point = (char*)channel->Name();
603       strcpy((char*)&sendBuffer[count], point);
604       count += strlen(point) + 1;
605     }
606   }
607
608   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
609
610   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
611
612   tcp.sendPacket(sendBuffer, count);
613   delete[] sendBuffer;
614   log->log("Client", Log::DEBUG, "Written channels list");
615
616   return 1;
617 }
618
619 int MVPClient::processGetChannelPids(UCHAR* data, int length)
620 {
621   ULONG channelNumber = ntohl(*(ULONG*)data);
622
623   cChannel* channel = channelFromNumber(channelNumber);
624   if (!channel)
625   {
626     sendULONG(0);
627     return 1;
628   }
629
630   ULONG numApids = 0;
631   ULONG spaceRequired = 12; // 4 for length field, 4 for vpid, 4 for number of apids
632   // Work out space required and number of Apids
633
634 #if VDRVERSNUM < 10300
635
636   log->log("Client", Log::DEBUG, "Apid1: %i", channel->Apid1());
637   log->log("Client", Log::DEBUG, "Apid2: %i", channel->Apid2());
638
639   if (channel->Apid2())
640   {
641     numApids = 2;
642     spaceRequired += 10; // 8 + 2 nulls
643   }
644   else if (channel->Apid1())
645   {
646     numApids = 1;
647     spaceRequired += 5; // 4 + 1 null
648   }
649   else
650   {
651     numApids = 0;
652   }
653
654 #else
655
656   for (const int *Apid = channel->Apids(); *Apid; Apid++)
657   {
658     spaceRequired += 4 + strlen(channel->Alang(numApids)) + 1; // 4 for pid, length of string + \0
659     numApids++;
660   }
661 #endif
662
663
664   // Format of response
665   // vpid
666   // number of apids
667   // {
668   //    apid
669   //    lang string
670   // }
671
672   UCHAR* sendBuffer = new UCHAR[spaceRequired];
673   ULONG point = 0;
674   *(ULONG*)&sendBuffer[point] = htonl(spaceRequired - 4);   point += 4;   // take off first 4 bytes
675   *(ULONG*)&sendBuffer[point] = htonl(channel->Vpid());     point += 4;
676   *(ULONG*)&sendBuffer[point] = htonl(numApids);            point += 4;
677
678 #if VDRVERSNUM < 10300
679   if (numApids >= 1)
680   {
681     *(ULONG*)&sendBuffer[point] = htonl(channel->Apid1());  point += 4;
682     sendBuffer[point] = '\0';                               point += 1;
683   }
684   if (numApids == 2)
685   {
686     *(ULONG*)&sendBuffer[point] = htonl(channel->Apid2());  point += 4;
687     sendBuffer[point] = '\0';                               point += 1;
688   }
689 #else
690   for (ULONG i = 0; i < numApids; i++)
691   {
692     *(ULONG*)&sendBuffer[point] = htonl(channel->Apid(i));  point += 4;
693     strcpy((char*)&sendBuffer[point], channel->Alang(i));   point += strlen(channel->Alang(i)) + 1;
694   }
695 #endif
696
697 //  printf("About to send getchannelpids response. length = %u\n", spaceRequired);
698   tcp.dump(sendBuffer, spaceRequired);
699
700   tcp.sendPacket(sendBuffer, spaceRequired);
701   delete[] sendBuffer;
702   log->log("Client", Log::DEBUG, "Written channels pids");
703
704   return 1;
705 }
706
707 int MVPClient::processStartStreamingChannel(UCHAR* data, int length)
708 {
709   log->log("Client", Log::DEBUG, "length = %i", length);
710   ULONG channelNumber = ntohl(*(ULONG*)data);
711
712   cChannel* channel = channelFromNumber(channelNumber);
713   if (!channel)
714   {
715     sendULONG(0);
716     return 1;
717   }
718
719   // get the priority we should use
720   int fail = 1;
721   int priority = config.getValueLong("General", "Live priority", &fail);
722   if (!fail)
723   {
724     log->log("Client", Log::DEBUG, "Config: Live TV priority: %i", priority);
725   }
726   else
727   {
728     log->log("Client", Log::DEBUG, "Config: Live TV priority config fail");
729     priority = 0;
730   }
731
732   // a bit of sanity..
733   if (priority < 0) priority = 0;
734   if (priority > 99) priority = 99;
735
736   log->log("Client", Log::DEBUG, "Using live TV priority %i", priority);
737   lp = MVPReceiver::create(channel, priority);
738
739   if (!lp)
740   {
741     sendULONG(0);
742     return 1;
743   }
744
745   if (!lp->init())
746   {
747     delete lp;
748     lp = NULL;
749     sendULONG(0);
750     return 1;
751   }
752
753   sendULONG(1);
754   return 1;
755 }
756
757 int MVPClient::processStopStreaming(UCHAR* data, int length)
758 {
759   log->log("Client", Log::DEBUG, "STOP STREAMING RECEIVED");
760   if (lp)
761   {
762     delete lp;
763     lp = NULL;
764   }
765   else if (rp)
766   {
767     writeResumeData();
768
769     delete rp;
770     delete recordingManager;
771     rp = NULL;
772     recordingManager = NULL;
773   }
774
775   sendULONG(1);
776   return 1;
777 }
778
779 int MVPClient::processGetBlock(UCHAR* data, int length)
780 {
781   if (!lp && !rp)
782   {
783     log->log("Client", Log::DEBUG, "Get block called when no streaming happening!");
784     return 0;
785   }
786
787   ULLONG position = ntohll(*(ULLONG*)data);
788   data += sizeof(ULLONG);
789   ULONG amount = ntohl(*(ULONG*)data);
790
791   log->log("Client", Log::DEBUG, "getblock pos = %llu length = %lu", position, amount);
792
793   UCHAR sendBuffer[amount + 4];
794   ULONG amountReceived = 0; // compiler moan.
795   if (lp)
796   {
797     log->log("Client", Log::DEBUG, "getting from live");
798     amountReceived = lp->getBlock(&sendBuffer[4], amount);
799
800     if (!amountReceived)
801     {
802       // vdr has possibly disconnected the receiver
803       log->log("Client", Log::DEBUG, "VDR has disconnected the live receiver");
804       delete lp;
805       lp = NULL;
806     }
807   }
808   else if (rp)
809   {
810     log->log("Client", Log::DEBUG, "getting from recording");
811     amountReceived = rp->getBlock(&sendBuffer[4], position, amount);
812   }
813
814   if (!amountReceived)
815   {
816     sendULONG(0);
817     log->log("Client", Log::DEBUG, "written 4(0) as getblock got 0");
818   }
819   else
820   {
821     *(ULONG*)&sendBuffer[0] = htonl(amountReceived);
822     tcp.sendPacket(sendBuffer, amountReceived + 4);
823     log->log("Client", Log::DEBUG, "written ok %lu", amountReceived);
824   }
825
826   return 1;
827 }
828
829 int MVPClient::processStartStreamingRecording(UCHAR* data, int length)
830 {
831   // data is a pointer to the fileName string
832
833   recordingManager = new cRecordings;
834   recordingManager->Load();
835
836   cRecording* recording = recordingManager->GetByName((char*)data);
837
838   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
839
840   if (recording)
841   {
842     rp = new RecPlayer(recording);
843
844     UCHAR sendBuffer[16];
845     *(ULONG*)&sendBuffer[0] = htonl(12);
846     *(ULLONG*)&sendBuffer[4] = htonll(rp->getLengthBytes());
847     *(ULONG*)&sendBuffer[12] = htonl(rp->getLengthFrames());
848
849     tcp.sendPacket(sendBuffer, 16);
850     log->log("Client", Log::DEBUG, "written totalLength");
851   }
852   else
853   {
854     delete recordingManager;
855     recordingManager = NULL;
856   }
857   return 1;
858 }
859
860 int MVPClient::processPositionFromFrameNumber(UCHAR* data, int length)
861 {
862   ULLONG retval = 0;
863
864   ULONG frameNumber = ntohl(*(ULONG*)data);
865   data += 4;
866
867   if (!rp)
868   {
869     log->log("Client", Log::DEBUG, "Rescan recording called when no recording being played!");
870   }
871   else
872   {
873     retval = rp->positionFromFrameNumber(frameNumber);
874   }
875
876   UCHAR sendBuffer[12];
877   *(ULONG*)&sendBuffer[0] = htonl(8);
878   *(ULLONG*)&sendBuffer[4] = htonll(retval);
879
880   tcp.sendPacket(sendBuffer, 12);
881   log->log("Client", Log::DEBUG, "Wrote posFromFrameNum reply to client");
882   return 1;
883 }
884
885 int MVPClient::processFrameNumberFromPosition(UCHAR* data, int length)
886 {
887   ULONG retval = 0;
888
889   ULLONG position = ntohll(*(ULLONG*)data);
890   data += 8;
891
892   if (!rp)
893   {
894     log->log("Client", Log::DEBUG, "Rescan recording called when no recording being played!");
895   }
896   else
897   {
898     retval = rp->frameNumberFromPosition(position);
899   }
900
901   UCHAR sendBuffer[8];
902   *(ULONG*)&sendBuffer[0] = htonl(4);
903   *(ULONG*)&sendBuffer[4] = htonl(retval);
904
905   tcp.sendPacket(sendBuffer, 8);
906   log->log("Client", Log::DEBUG, "Wrote frameNumFromPos reply to client");
907   return 1;
908 }
909
910 int MVPClient::processGetIFrame(UCHAR* data, int length)
911 {
912   bool success = false;
913
914   ULONG frameNumber = ntohl(*(ULONG*)data);
915   data += 4;
916   ULONG direction = ntohl(*(ULONG*)data);
917   data += 4;
918
919   ULLONG rfilePosition = 0;
920   ULONG rframeNumber = 0;
921   ULONG rframeLength = 0;
922
923   if (!rp)
924   {
925     log->log("Client", Log::DEBUG, "GetIFrame recording called when no recording being played!");
926   }
927   else
928   {
929     success = rp->getNextIFrame(frameNumber, direction, &rfilePosition, &rframeNumber, &rframeLength);
930   }
931
932   // returns file position, frame number, length
933
934   UCHAR sendBuffer[20];
935   int packetLength;
936
937   if (success)
938   {
939     packetLength = 20;
940     *(ULONG*)&sendBuffer[0] = htonl(16);
941     *(ULLONG*)&sendBuffer[4] = htonll(rfilePosition);
942     *(ULONG*)&sendBuffer[12] = htonl(rframeNumber);
943     *(ULONG*)&sendBuffer[16] = htonl(rframeLength);
944   }
945   else
946   {
947     packetLength = 8;
948     *(ULONG*)&sendBuffer[0] = htonl(4);
949     *(ULONG*)&sendBuffer[4] = 0;
950   }
951
952   log->log("Client", Log::DEBUG, "%llu %lu %lu", rfilePosition, rframeNumber, rframeLength);
953
954   tcp.sendPacket(sendBuffer, packetLength);
955   log->log("Client", Log::DEBUG, "Wrote GNIF reply to client");
956   return 1;
957 }
958
959 int MVPClient::processGetChannelSchedule(UCHAR* data, int length)
960 {
961   ULONG channelNumber = ntohl(*(ULONG*)data);
962   data += 4;
963   ULONG startTime = ntohl(*(ULONG*)data);
964   data += 4;
965   ULONG duration = ntohl(*(ULONG*)data);
966
967   log->log("Client", Log::DEBUG, "get schedule called for channel %lu", channelNumber);
968
969   cChannel* channel = channelFromNumber(channelNumber);
970   if (!channel)
971   {
972     sendULONG(0);
973     log->log("Client", Log::DEBUG, "written 0 because channel = NULL");
974     return 1;
975   }
976
977   log->log("Client", Log::DEBUG, "Got channel");
978
979 #if VDRVERSNUM < 10300
980   cMutexLock MutexLock;
981   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
982 #else
983   cSchedulesLock MutexLock;
984   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
985 #endif
986   if (!Schedules)
987   {
988     sendULONG(0);
989     log->log("Client", Log::DEBUG, "written 0 because Schedule!s! = NULL");
990     return 1;
991   }
992
993   log->log("Client", Log::DEBUG, "Got schedule!s! object");
994
995   const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
996   if (!Schedule)
997   {
998     sendULONG(0);
999     log->log("Client", Log::DEBUG, "written 0 because Schedule = NULL");
1000     return 1;
1001   }
1002
1003   log->log("Client", Log::DEBUG, "Got schedule object");
1004
1005   UCHAR* sendBuffer = (UCHAR*)malloc(100000);
1006   ULONG sendBufferLength = 100000;
1007   ULONG sendBufferUsed = sizeof(ULONG); // leave a hole for the entire packet length
1008
1009   char* empty = "";
1010
1011   // assign all the event info to temp vars then we know exactly what size they are
1012   ULONG thisEventID;
1013   ULONG thisEventTime;
1014   ULONG thisEventDuration;
1015   const char* thisEventTitle;
1016   const char* thisEventSubTitle;
1017   const char* thisEventDescription;
1018
1019   ULONG constEventLength = sizeof(thisEventID) + sizeof(thisEventTime) + sizeof(thisEventDuration);
1020   ULONG thisEventLength;
1021
1022 #if VDRVERSNUM < 10300
1023
1024   const cEventInfo *event;
1025   for (int eventNumber = 0; eventNumber < Schedule->NumEvents(); eventNumber++)
1026   {
1027     event = Schedule->GetEventNumber(eventNumber);
1028
1029     thisEventID = event->GetEventID();
1030     thisEventTime = event->GetTime();
1031     thisEventDuration = event->GetDuration();
1032     thisEventTitle = event->GetTitle();
1033     thisEventSubTitle = event->GetSubtitle();
1034     thisEventDescription = event->GetExtendedDescription();
1035
1036 #else
1037
1038   for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
1039   {
1040     thisEventID = event->EventID();
1041     thisEventTime = event->StartTime();
1042     thisEventDuration = event->Duration();
1043     thisEventTitle = event->Title();
1044     thisEventSubTitle = NULL;
1045     thisEventDescription = event->Description();
1046
1047 #endif
1048
1049     log->log("Client", Log::DEBUG, "Got an event object %p", event);
1050
1051     //in the past filter
1052     if ((thisEventTime + thisEventDuration) < (ULONG)time(NULL)) continue;
1053
1054     //start time filter
1055     if ((thisEventTime + thisEventDuration) <= startTime) continue;
1056
1057     //duration filter
1058     if (thisEventTime >= (startTime + duration)) continue;
1059
1060     if (!thisEventTitle) thisEventTitle = empty;
1061     if (!thisEventSubTitle) thisEventSubTitle = empty;
1062     if (!thisEventDescription) thisEventDescription = empty;
1063
1064     thisEventLength = constEventLength + strlen(thisEventTitle) + 1 + strlen(thisEventSubTitle) + 1 + strlen(thisEventDescription) + 1;
1065
1066     log->log("Client", Log::DEBUG, "Done s1");
1067
1068     // now extend the buffer if necessary
1069     if ((sendBufferUsed + thisEventLength) > sendBufferLength)
1070     {
1071       log->log("Client", Log::DEBUG, "Extending buffer");
1072       sendBufferLength += 100000;
1073       UCHAR* temp = (UCHAR*)realloc(sendBuffer, sendBufferLength);
1074       if (temp == NULL)
1075       {
1076         free(sendBuffer);
1077         UCHAR sendBuffer2[8];
1078         *(ULONG*)&sendBuffer2[0] = htonl(4);
1079         *(ULONG*)&sendBuffer2[4] = htonl(0);
1080         tcp.sendPacket(sendBuffer2, 8);
1081         log->log("Client", Log::DEBUG, "written 0 because failed to realloc packet");
1082         return 1;
1083       }
1084       sendBuffer = temp;
1085     }
1086
1087     log->log("Client", Log::DEBUG, "Done s2");
1088
1089     *(ULONG*)&sendBuffer[sendBufferUsed] = htonl(thisEventID);       sendBufferUsed += sizeof(ULONG);
1090     *(ULONG*)&sendBuffer[sendBufferUsed] = htonl(thisEventTime);     sendBufferUsed += sizeof(ULONG);
1091     *(ULONG*)&sendBuffer[sendBufferUsed] = htonl(thisEventDuration); sendBufferUsed += sizeof(ULONG);
1092
1093     strcpy((char*)&sendBuffer[sendBufferUsed], thisEventTitle);       sendBufferUsed += strlen(thisEventTitle) + 1;
1094     strcpy((char*)&sendBuffer[sendBufferUsed], thisEventSubTitle);    sendBufferUsed += strlen(thisEventSubTitle) + 1;
1095     strcpy((char*)&sendBuffer[sendBufferUsed], thisEventDescription); sendBufferUsed += strlen(thisEventDescription) + 1;
1096
1097     log->log("Client", Log::DEBUG, "Done s3 %lu", sendBufferUsed);
1098   }
1099
1100   log->log("Client", Log::DEBUG, "Got all event data");
1101
1102   if (sendBufferUsed == sizeof(ULONG))
1103   {
1104     // No data
1105     sendULONG(0);
1106     log->log("Client", Log::DEBUG, "Written 0 because no data");
1107   }
1108   else
1109   {
1110     // Write the length into the first 4 bytes. It's sendBufferUsed - 4 because of the hole!
1111     *(ULONG*)&sendBuffer[0] = htonl(sendBufferUsed - sizeof(ULONG));
1112     tcp.sendPacket(sendBuffer, sendBufferUsed);
1113     log->log("Client", Log::DEBUG, "written %lu schedules packet", sendBufferUsed);
1114   }
1115
1116   free(sendBuffer);
1117
1118   return 1;
1119 }
1120
1121 int MVPClient::processConfigSave(UCHAR* buffer, int length)
1122 {
1123   char* section = (char*)buffer;
1124   char* key = NULL;
1125   char* value = NULL;
1126
1127   for (int k = 0; k < length; k++)
1128   {
1129     if (buffer[k] == '\0')
1130     {
1131       if (!key)
1132       {
1133         key = (char*)&buffer[k+1];
1134       }
1135       else
1136       {
1137         value = (char*)&buffer[k+1];
1138         break;
1139       }
1140     }
1141   }
1142
1143   // if the last string (value) doesnt have null terminator, give up
1144   if (buffer[length - 1] != '\0') return 0;
1145
1146   log->log("Client", Log::DEBUG, "Config save: %s %s %s", section, key, value);
1147   if (config.setValueString(section, key, value))
1148   {
1149     sendULONG(1);
1150   }
1151   else
1152   {
1153     sendULONG(0);
1154   }
1155
1156   return 1;
1157 }
1158
1159 int MVPClient::processConfigLoad(UCHAR* buffer, int length)
1160 {
1161   char* section = (char*)buffer;
1162   char* key = NULL;
1163
1164   for (int k = 0; k < length; k++)
1165   {
1166     if (buffer[k] == '\0')
1167     {
1168       key = (char*)&buffer[k+1];
1169       break;
1170     }
1171   }
1172
1173   char* value = config.getValueString(section, key);
1174
1175   if (value)
1176   {
1177     UCHAR sendBuffer[4 + strlen(value) + 1];
1178     *(ULONG*)&sendBuffer[0] = htonl(strlen(value) + 1);
1179     strcpy((char*)&sendBuffer[4], value);
1180     tcp.sendPacket(sendBuffer, 4 + strlen(value) + 1);
1181
1182     log->log("Client", Log::DEBUG, "Written config load packet");
1183     delete[] value;
1184   }
1185   else
1186   {
1187     UCHAR sendBuffer[8];
1188     *(ULONG*)&sendBuffer[0] = htonl(4);
1189     *(ULONG*)&sendBuffer[4] = htonl(0);
1190     tcp.sendPacket(sendBuffer, 8);
1191
1192     log->log("Client", Log::DEBUG, "Written config load failed packet");
1193   }
1194
1195   return 1;
1196 }
1197
1198 void MVPClient::cleanConfig()
1199 {
1200   log->log("Client", Log::DEBUG, "Clean config");
1201
1202   cRecordings Recordings;
1203   Recordings.Load();
1204
1205   int numReturns;
1206   int length;
1207   char* resumes = config.getSectionKeyNames("ResumeData", numReturns, length);
1208   char* position = resumes;
1209   for(int k = 0; k < numReturns; k++)
1210   {
1211     log->log("Client", Log::DEBUG, "EXAMINING: %i %i %p %s", k, numReturns, position, position);
1212
1213     cRecording* recording = Recordings.GetByName(position);
1214     if (!recording)
1215     {
1216       // doesn't exist anymore
1217       log->log("Client", Log::DEBUG, "Found a recording that doesn't exist anymore");
1218       config.deleteValue("ResumeData", position);
1219     }
1220     else
1221     {
1222       log->log("Client", Log::DEBUG, "This recording still exists");
1223     }
1224
1225     position += strlen(position) + 1;
1226   }
1227
1228   delete[] resumes;
1229 }
1230
1231
1232
1233
1234
1235
1236 /*
1237     event = Schedule->GetPresentEvent();
1238
1239     fprintf(f, "\n\nCurrent event\n\n");
1240
1241     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", 0, event->GetEventID(), event->GetTime(), event->GetDuration());
1242     fprintf(f, "Event %i title = %s subtitle = %s\n", 0, event->GetTitle(), event->GetSubtitle());
1243     fprintf(f, "Event %i extendeddescription = %s\n", 0, event->GetExtendedDescription());
1244     fprintf(f, "Event %i isFollowing = %i, isPresent = %i\n", 0, event->IsFollowing(), event->IsPresent());
1245
1246     event = Schedule->GetFollowingEvent();
1247
1248     fprintf(f, "\n\nFollowing event\n\n");
1249
1250     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", 0, event->GetEventID(), event->GetTime(), event->GetDuration());
1251     fprintf(f, "Event %i title = %s subtitle = %s\n", 0, event->GetTitle(), event->GetSubtitle());
1252     fprintf(f, "Event %i extendeddescription = %s\n", 0, event->GetExtendedDescription());
1253     fprintf(f, "Event %i isFollowing = %i, isPresent = %i\n", 0, event->IsFollowing(), event->IsPresent());
1254
1255     fprintf(f, "\n\n");
1256 */
1257
1258 /*
1259     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", eventNumber, event->GetEventID(), event->GetTime(), event->GetDuration());
1260     fprintf(f, "Event %i title = %s subtitle = %s\n", eventNumber, event->GetTitle(), event->GetSubtitle());
1261     fprintf(f, "Event %i extendeddescription = %s\n", eventNumber, event->GetExtendedDescription());
1262     fprintf(f, "Event %i isFollowing = %i, isPresent = %i\n", eventNumber, event->IsFollowing(), event->IsPresent());
1263
1264     fprintf(f, "\n\n");
1265 */
1266
1267 /*
1268
1269
1270 void MVPClient::test2()
1271 {
1272   FILE* f = fopen("/tmp/s.txt", "w");
1273
1274 #if VDRVERSNUM < 10300
1275   cMutexLock MutexLock;
1276   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
1277 #else
1278   cSchedulesLock MutexLock;
1279   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
1280 #endif
1281
1282   if (!Schedules)
1283   {
1284     fprintf(f, "Schedules = NULL\n");
1285     fclose(f);
1286     return;
1287   }
1288
1289   fprintf(f, "Schedules dump:\n");
1290   Schedules->Dump(f);
1291
1292
1293   const cSchedule *Schedule;
1294   int scheduleNumber = 0;
1295
1296   tChannelID tchid;
1297   cChannel *thisChannel;
1298
1299 #if VDRVERSNUM < 10300
1300   const cEventInfo *event;
1301   int eventNumber = 0;
1302 #else
1303   const cEvent *event;
1304 #endif
1305
1306 //    Schedule = Schedules->GetSchedule(channel->GetChannelID());
1307 //    Schedule = Schedules->GetSchedule();
1308   Schedule = Schedules->First();
1309   if (!Schedule)
1310   {
1311     fprintf(f, "First Schedule = NULL\n");
1312     fclose(f);
1313     return;
1314   }
1315
1316   while (Schedule)
1317   {
1318     fprintf(f, "Schedule #%i\n", scheduleNumber);
1319     fprintf(f, "-------------\n\n");
1320
1321 #if VDRVERSNUM < 10300
1322     tchid = Schedule->GetChannelID();
1323 #else
1324     tchid = Schedule->ChannelID();
1325 #endif
1326
1327 #if VDRVERSNUM < 10300
1328     fprintf(f, "ChannelID.ToString() = %s\n", tchid.ToString());
1329     fprintf(f, "NumEvents() = %i\n", Schedule->NumEvents());
1330 #else
1331 //  put the count at the end.
1332 #endif
1333
1334     thisChannel = Channels.GetByChannelID(tchid, true);
1335     if (thisChannel)
1336     {
1337       fprintf(f, "Channel Number: %p %i\n", thisChannel, thisChannel->Number());
1338     }
1339     else
1340     {
1341       fprintf(f, "thisChannel = NULL for tchid\n");
1342     }
1343
1344 #if VDRVERSNUM < 10300
1345     for (eventNumber = 0; eventNumber < Schedule->NumEvents(); eventNumber++)
1346     {
1347       event = Schedule->GetEventNumber(eventNumber);
1348       fprintf(f, "Event %i tableid = %i timestring = %s endtimestring = %s\n", eventNumber, event->GetTableID(), event->GetTimeString(), event->GetEndTimeString());
1349       fprintf(f, "Event %i date = %s isfollowing = %i ispresent = %i\n", eventNumber, event->GetDate(), event->IsFollowing(), event->IsPresent());
1350       fprintf(f, "Event %i extendeddescription = %s\n", eventNumber, event->GetExtendedDescription());
1351       fprintf(f, "Event %i subtitle = %s title = %s\n", eventNumber, event->GetSubtitle(), event->GetTitle());
1352       fprintf(f, "Event %i eventid = %u duration = %li time = %lu channelnumber = %i\n", eventNumber, event->GetEventID(), event->GetDuration(), event->GetTime(), event->GetChannelNumber());
1353       fprintf(f, "Event %u dump:\n", eventNumber);
1354       event->Dump(f);
1355       fprintf(f, "\n\n");
1356     }
1357 #else
1358 //  This whole section needs rewriting to walk the list.
1359     event = Schedule->Events()->First();
1360     while (event) {
1361       event = Schedule->Events()->Next(event);
1362     }
1363 #endif
1364
1365
1366     fprintf(f, "\nDump from object:\n");
1367     Schedule->Dump(f);
1368     fprintf(f, "\nEND\n");
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378     fprintf(f, "End of current Schedule\n\n\n");
1379
1380     Schedule = (const cSchedule *)Schedules->Next(Schedule);
1381     scheduleNumber++;
1382   }
1383
1384   fclose(f);
1385 }
1386
1387
1388
1389 */
1390
1391
1392
1393 /*
1394   const cEventInfo *GetPresentEvent(void) const;
1395   const cEventInfo *GetFollowingEvent(void) const;
1396   const cEventInfo *GetEvent(unsigned short uEventID, time_t tTime = 0) const;
1397   const cEventInfo *GetEventAround(time_t tTime) const;
1398   const cEventInfo *GetEventNumber(int n) const { return Events.Get(n); }
1399
1400
1401   const unsigned char GetTableID(void) const;
1402   const char *GetTimeString(void) const;
1403   const char *GetEndTimeString(void) const;
1404   const char *GetDate(void) const;
1405   bool IsFollowing(void) const;
1406   bool IsPresent(void) const;
1407   const char *GetExtendedDescription(void) const;
1408   const char *GetSubtitle(void) const;
1409   const char *GetTitle(void) const;
1410   unsigned short GetEventID(void) const;
1411   long GetDuration(void) const;
1412   time_t GetTime(void) const;
1413   tChannelID GetChannelID(void) const;
1414   int GetChannelNumber(void) const { return nChannelNumber; }
1415   void SetChannelNumber(int ChannelNumber) const { ((cEventInfo *)this)->nChannelNumber = ChannelNumber; } // doesn't modify the EIT data, so it's ok to make it 'const'
1416   void Dump(FILE *f, const char *Prefix = "") const;
1417
1418 */
1419
1420
1421 /*
1422 void MVPClient::test(int channelNumber)
1423 {
1424   FILE* f = fopen("/tmp/test.txt", "w");
1425
1426   cMutexLock MutexLock;
1427   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
1428
1429   if (!Schedules)
1430   {
1431     fprintf(f, "Schedules = NULL\n");
1432     fclose(f);
1433     return;
1434   }
1435
1436   fprintf(f, "Schedules dump:\n");
1437 //  Schedules->Dump(f);
1438
1439   const cSchedule *Schedule;
1440   cChannel *thisChannel;
1441   const cEventInfo *event;
1442
1443   thisChannel = channelFromNumber(channelNumber);
1444   if (!thisChannel)
1445   {
1446     fprintf(f, "thisChannel = NULL\n");
1447     fclose(f);
1448     return;
1449   }
1450
1451   Schedule = Schedules->GetSchedule(thisChannel->GetChannelID());
1452 //    Schedule = Schedules->GetSchedule();
1453 //  Schedule = Schedules->First();
1454   if (!Schedule)
1455   {
1456     fprintf(f, "First Schedule = NULL\n");
1457     fclose(f);
1458     return;
1459   }
1460
1461   fprintf(f, "NumEvents() = %i\n\n", Schedule->NumEvents());
1462
1463   // For some channels VDR seems to pick a random point in time to
1464   // start dishing out events, but they are in order
1465   // at some point in the list the time snaps to the current event
1466
1467
1468
1469
1470   for (int eventNumber = 0; eventNumber < Schedule->NumEvents(); eventNumber++)
1471   {
1472     event = Schedule->GetEventNumber(eventNumber);
1473     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", eventNumber, event->GetEventID(), event->GetTime(), event->GetDuration());
1474     fprintf(f, "Event %i title = %s subtitle = %s\n", eventNumber, event->GetTitle(), event->GetSubtitle());
1475     fprintf(f, "Event %i extendeddescription = %s\n", eventNumber, event->GetExtendedDescription());
1476     fprintf(f, "\n\n");
1477   }
1478
1479   fprintf(f, "\nEND\n");
1480
1481   fclose(f);
1482 }
1483
1484 */
1485
1486
1487
1488 /*
1489
1490
1491 Right, so
1492
1493 Schedules = the collection of all the Schedule objects
1494 Schedule  = One schedule, contants all the events for a channel
1495 Event     = One programme
1496
1497
1498 Want:
1499
1500 Event ID
1501 Time
1502 Duration
1503 Title
1504 Subtitle (used for "Programmes resume at ...")
1505 Description
1506
1507 IsPresent ? easy to work out tho. Oh it doesn't always work
1508
1509 */
1510
1511 /*
1512 void MVPClient::test2()
1513 {
1514   log->log("-", Log::DEBUG, "Timers List");
1515
1516   for (int i = 0; i < Timers.Count(); i++)
1517   {
1518     cTimer *timer = Timers.Get(i);
1519     //Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, timer->ToText());
1520     log->log("-", Log::DEBUG, "i=%i count=%i index=%d", i, Timers.Count(), timer->Index() + 1);
1521 #if VDRVERSNUM < 10300
1522     log->log("-", Log::DEBUG, "active=%i recording=%i pending=%i start=%li stop=%li priority=%i lifetime=%i", timer->Active(), timer->Recording(), timer->Pending(), timer->StartTime(), timer->StopTime(), timer->Priority(), timer->Lifetime());
1523 #else
1524     log->log("-", Log::DEBUG, "active=%i recording=%i pending=%i start=%li stop=%li priority=%i lifetime=%i", timer->HasFlags(tfActive), timer->Recording(), timer->Pending(), timer->StartTime(), timer->StopTime(), timer->Priority(), timer->Lifetime());
1525 #endif
1526     log->log("-", Log::DEBUG, "channel=%i file=%s summary=%s", timer->Channel()->Number(), timer->File(), timer->Summary());
1527     log->log("-", Log::DEBUG, "");
1528   }
1529
1530   // asprintf(&buffer, "%d:%s:%s  :%04d:%04d:%d:%d:%s:%s\n",
1531 //            active, (UseChannelID ? Channel()->GetChannelID().ToString() : itoa(Channel()->Number())),
1532 //            PrintDay(day, firstday), start, stop, priority, lifetime, file, summary ? summary : "");
1533 }
1534 */
1535
1536 /*
1537 Active seems to be a bool - whether the timer should be done or not. If set to inactive it stays around after its time
1538 recording is a bool, 0 for not currently recording, 1 for currently recording
1539 pending is a bool, 0 for would not be trying to record this right now, 1 for would/is trying to record this right now
1540 */
1541
1542
1543 int MVPClient::processGetTimers(UCHAR* buffer, int length)
1544 {
1545   UCHAR* sendBuffer = new UCHAR[50000]; // FIXME hope this is enough
1546   int count = 4; // leave space for the packet length
1547
1548   const char* fileName;
1549   cTimer *timer;
1550   int numTimers = Timers.Count();
1551
1552   *(ULONG*)&sendBuffer[count] = htonl(numTimers);    count += 4;
1553
1554   for (int i = 0; i < numTimers; i++)
1555   {
1556     if (count > 49000) break;
1557
1558     timer = Timers.Get(i);
1559
1560 #if VDRVERSNUM < 10300
1561     *(ULONG*)&sendBuffer[count] = htonl(timer->Active());                 count += 4;
1562 #else
1563     *(ULONG*)&sendBuffer[count] = htonl(timer->HasFlags(tfActive));       count += 4;
1564 #endif
1565     *(ULONG*)&sendBuffer[count] = htonl(timer->Recording());              count += 4;
1566     *(ULONG*)&sendBuffer[count] = htonl(timer->Pending());                count += 4;
1567     *(ULONG*)&sendBuffer[count] = htonl(timer->Priority());               count += 4;
1568     *(ULONG*)&sendBuffer[count] = htonl(timer->Lifetime());               count += 4;
1569     *(ULONG*)&sendBuffer[count] = htonl(timer->Channel()->Number());      count += 4;
1570     *(ULONG*)&sendBuffer[count] = htonl(timer->StartTime());              count += 4;
1571     *(ULONG*)&sendBuffer[count] = htonl(timer->StopTime());               count += 4;
1572     *(ULONG*)&sendBuffer[count] = htonl(timer->Day());                    count += 4;
1573     *(ULONG*)&sendBuffer[count] = htonl(timer->WeekDays());               count += 4;
1574
1575     fileName = timer->File();
1576     strcpy((char*)&sendBuffer[count], fileName);
1577     count += strlen(fileName) + 1;
1578   }
1579
1580   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
1581
1582   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
1583
1584   tcp.sendPacket(sendBuffer, count);
1585   delete[] sendBuffer;
1586   log->log("Client", Log::DEBUG, "Written timers list");
1587
1588   return 1;
1589 }
1590
1591 int MVPClient::processSetTimer(UCHAR* buffer, int length)
1592 {
1593   char* timerString = new char[strlen((char*)buffer) + 1];
1594   strcpy(timerString, (char*)buffer);
1595
1596 #if VDRVERSNUM < 10300
1597
1598   // If this is VDR 1.2 the date part of the timer string must be reduced
1599   // to just DD rather than YYYY-MM-DD
1600
1601   int s = 0; // source
1602   int d = 0; // destination
1603   int c = 0; // count
1604   while(c != 2) // copy up to date section, including the second ':'
1605   {
1606     timerString[d] = buffer[s];
1607     if (buffer[s] == ':') c++;
1608     ++s;
1609     ++d;
1610   }
1611   // now it has copied up to the date section
1612   c = 0;
1613   while(c != 2) // waste YYYY-MM-
1614   {
1615     if (buffer[s] == '-') c++;
1616     ++s;
1617   }
1618   // now source is at the DD
1619   memcpy(&timerString[d], &buffer[s], length - s);
1620   d += length - s;
1621   timerString[d] = '\0';
1622
1623   log->log("Client", Log::DEBUG, "Timer string after 1.2 conversion:");
1624   log->log("Client", Log::DEBUG, "%s", timerString);
1625
1626 #endif
1627
1628   cTimer *timer = new cTimer;
1629   if (timer->Parse((char*)timerString))
1630   {
1631     cTimer *t = Timers.GetTimer(timer);
1632     if (!t)
1633     {
1634       Timers.Add(timer);
1635 #if VDRVERSNUM < 10300
1636       Timers.Save();
1637 #else
1638       Timers.SetModified();
1639 #endif
1640       sendULONG(0);
1641       return 1;
1642     }
1643     else
1644     {
1645       sendULONG(1);
1646     }
1647   }
1648   else
1649   {
1650      sendULONG(2);
1651   }
1652   delete timer;
1653   return 1;
1654 }
1655
1656 void MVPClient::incClients()
1657 {
1658   pthread_mutex_lock(&threadClientMutex);
1659   MVPClient::nr_clients++;
1660   pthread_mutex_unlock(&threadClientMutex);
1661 }
1662
1663 void MVPClient::decClients()
1664 {
1665   pthread_mutex_lock(&threadClientMutex);
1666   MVPClient::nr_clients--;
1667   pthread_mutex_unlock(&threadClientMutex);
1668 }
1669
1670 int MVPClient::getNrClients()
1671 {
1672   int nrClients;
1673   pthread_mutex_lock(&threadClientMutex);
1674   nrClients = MVPClient::nr_clients;
1675   pthread_mutex_unlock(&threadClientMutex);
1676   return nrClients;
1677 }
1678
1679 int MVPClient::processGetRecInfo(UCHAR* data, int length)
1680 {
1681   // data is a pointer to the fileName string
1682
1683   cRecordings Recordings;
1684   Recordings.Load(); // probably have to do this
1685
1686   cRecording *recording = Recordings.GetByName((char*)data);
1687
1688   time_t timerStart = 0;
1689   time_t timerStop = 0;
1690   char* summary = NULL;
1691   ULONG resumePoint = 0;
1692
1693   if (!recording)
1694   {
1695     log->log("Client", Log::ERR, "GetRecInfo found no recording");
1696     sendULONG(0);
1697     return 1;
1698   }
1699
1700   ULONG sendBufferSize = 10000;
1701   UCHAR* sendBuffer = (UCHAR*)malloc(sendBufferSize);
1702   ULONG pos = 4; // leave first 4 bytes for size field
1703
1704
1705   /* Return packet:
1706   4 bytes: start time for timer
1707   4 bytes: end time for timer
1708   4 bytes: resume point
1709   string: summary
1710   4 bytes: num components
1711   {
1712     1 byte: stream
1713     1 byte: type
1714     string: language
1715     string: description
1716   }
1717
1718   */
1719
1720   // Get current timer
1721
1722   cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
1723   if (rc)
1724   {
1725     timerStart = rc->Timer()->StartTime();
1726     timerStop = rc->Timer()->StopTime();
1727     log->log("Client", Log::DEBUG, "GRI: RC: %lu %lu", timerStart, timerStop);
1728   }
1729
1730   *(time_t*)&sendBuffer[pos] = htonl(timerStart);    pos += 4;
1731   *(time_t*)&sendBuffer[pos] = htonl(timerStop);     pos += 4;
1732
1733   // Get resume point
1734
1735   char* value = config.getValueString("ResumeData", (char*)data);
1736   if (value)
1737   {
1738     resumePoint = strtoul(value, NULL, 10);
1739     delete[] value;
1740   }
1741   log->log("Client", Log::DEBUG, "GRI: RP: %lu", resumePoint);
1742
1743   *(ULONG*)&sendBuffer[pos] = htonl(resumePoint);    pos += 4;
1744
1745
1746   // Get summary
1747
1748 #if VDRVERSNUM < 10300
1749   summary = (char*)recording->Summary();
1750 #else
1751   const cRecordingInfo *Info = recording->Info();
1752   summary = (char*)Info->ShortText();
1753   if (isempty(summary)) summary = (char*)Info->Description();
1754 #endif
1755   log->log("Client", Log::DEBUG, "GRI: S: %s", summary);
1756   if (summary)
1757   {
1758     // memory insanity...
1759     if ((sendBufferSize - pos) < (strlen(summary) + 500)) // random
1760     {
1761       UCHAR* newBuffer = (UCHAR*)realloc(sendBuffer, sendBufferSize + strlen(summary) + 10000);
1762       if (newBuffer)
1763       {
1764         sendBuffer = newBuffer;
1765         sendBufferSize += strlen(summary) + 10000;
1766       }
1767       else
1768       {
1769         free(sendBuffer);
1770         sendULONG(0);
1771         return 1;
1772       }
1773     }
1774
1775     strcpy((char*)&sendBuffer[pos], summary);
1776     pos += strlen(summary) + 1;
1777   }
1778   else
1779   {
1780     strcpy((char*)&sendBuffer[pos], "");
1781     pos += 1;
1782   }
1783
1784
1785   // Get channels
1786
1787 #if VDRVERSNUM < 10300
1788
1789   // Send 0 for numchannels - this signals the client this info is not available
1790   *(ULONG*)&sendBuffer[pos] = 0;    pos += 4;
1791
1792 #else
1793   const cComponents* components = Info->Components();
1794
1795   log->log("Client", Log::DEBUG, "GRI: D1: %p", components);
1796
1797   if (!components)
1798   {
1799     *(ULONG*)&sendBuffer[pos] = htonl(0);    pos += 4;
1800   }
1801   else
1802   {
1803     *(ULONG*)&sendBuffer[pos] = htonl(components->NumComponents());    pos += 4;
1804
1805     tComponent* component;
1806     for (int i = 0; i < components->NumComponents(); i++)
1807     {
1808       component = components->Component(i);
1809
1810       // memory insanity...
1811       ULONG extraNeeded = 2 + (component->language ? strlen(component->language) : 0)
1812                             + (component->description ? strlen(component->description) : 0) + 2;
1813
1814       if ((sendBufferSize - pos) < extraNeeded)
1815       {
1816         UCHAR* newBuffer = (UCHAR*)realloc(sendBuffer, sendBufferSize + extraNeeded + 10000);
1817         if (newBuffer)
1818         {
1819           sendBuffer = newBuffer;
1820           sendBufferSize += extraNeeded + 10000;
1821         }
1822         else
1823         {
1824           free(sendBuffer);
1825           sendULONG(0);
1826           return 1;
1827         }
1828       }
1829
1830       log->log("Client", Log::DEBUG, "GRI: C: %i %u %u %s %s", i, component->stream, component->type, component->language, component->description);
1831       sendBuffer[pos] = component->stream;  pos += 1;
1832       sendBuffer[pos] = component->type;    pos += 1;
1833       if (component->language)
1834       {
1835         strcpy((char*)&sendBuffer[pos], component->language);
1836         pos += strlen(component->language) + 1;
1837       }
1838       else
1839       {
1840         strcpy((char*)&sendBuffer[pos], "");
1841         pos += 1;
1842       }
1843       if (component->description)
1844       {
1845         strcpy((char*)&sendBuffer[pos], component->description);
1846         pos += strlen(component->description) + 1;
1847       }
1848       else
1849       {
1850         strcpy((char*)&sendBuffer[pos], "");
1851         pos += 1;
1852       }
1853
1854     }
1855   }
1856
1857 #endif
1858
1859   // Done. send it
1860
1861   *(ULONG*)&sendBuffer[0] = htonl(pos - 4); // -4 :  take off the size field
1862
1863   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
1864
1865   tcp.sendPacket(sendBuffer, pos);
1866   delete[] sendBuffer;
1867   log->log("Client", Log::DEBUG, "Written getrecinfo");
1868
1869   return 1;
1870 }
1871
1872
1873
1874
1875 // FIXME obselete
1876
1877 int MVPClient::processReScanRecording(UCHAR* data, int length)
1878 {
1879   if (!rp)
1880   {
1881     log->log("Client", Log::DEBUG, "Rescan recording called when no recording being played!");
1882     return 0;
1883   }
1884
1885   rp->scan();
1886
1887   UCHAR sendBuffer[16];
1888   *(ULONG*)&sendBuffer[0] = htonl(12);
1889   *(ULLONG*)&sendBuffer[4] = htonll(rp->getLengthBytes());
1890   *(ULONG*)&sendBuffer[12] = htonl(rp->getLengthFrames());
1891
1892   tcp.sendPacket(sendBuffer, 16);
1893   log->log("Client", Log::DEBUG, "Rescan recording, wrote new length to client");
1894   return 1;
1895 }
1896
1897 // FIXME without client calling rescan, getblock wont work even tho more data is avail
1898
1899
1900 int MVPClient::processGetMarks(UCHAR* data, int length)
1901 {
1902   // data is a pointer to the fileName string
1903
1904   UCHAR* sendBuffer = new UCHAR[50000]; // FIXME hope this is enough
1905   int count = 4; // leave space for the packet length
1906
1907
1908   cMarks Marks;
1909   cRecordings Recordings;
1910   Recordings.Load(); // probably have to do this
1911
1912   cRecording *recording = Recordings.GetByName((char*)data);
1913
1914   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
1915
1916   if (recording)
1917   {
1918     Marks.Load(recording->FileName());
1919     if (Marks.Count())
1920     {
1921       for (const cMark *m = Marks.First(); m; m = Marks.Next(m))
1922       {
1923         log->log("Client", Log::DEBUG, "found Mark %i", m->position);
1924
1925         if (count > 49000) break;
1926         *(ULONG*)&sendBuffer[count] = htonl(m->position);
1927         count += 4;
1928       }
1929     }
1930     else
1931     {
1932       log->log("Client", Log::DEBUG, "no marks found, sending 0-mark");
1933       *(ULONG*)&sendBuffer[count] = htonl(0);
1934       count += 4;
1935     }
1936   }
1937
1938   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
1939
1940   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
1941
1942   tcp.sendPacket(sendBuffer, count);
1943   delete[] sendBuffer;
1944   log->log("Client", Log::DEBUG, "Written Marks list");
1945
1946   return 1;
1947 }
1948
1949
1950 /**
1951   * media List Request:
1952   * 4 length
1953   * 4 VDR_GETMEDIALIST
1954   * 4 flags (currently unused)
1955   * n dirname
1956   * n+1 0
1957   * Media List response:
1958   * 4 length
1959   * 4 VDR_
1960   * 4 numentries
1961   * per entry:
1962   * 4 media type
1963   * 4 time stamp
1964   * 4 flags
1965   * 4 strlen (incl. 0 Byte)
1966   * string
1967   * 0
1968 */
1969 #define MLISTBUF 500000
1970 int MVPClient::processGetMediaList(UCHAR* data, int length)
1971 {
1972   if (length < 4) {
1973     log->log("Client", Log::ERR, "getMediaList packet too short %d", length);
1974     return 0;
1975   }
1976   char * dirname=NULL;
1977   if (length > 4) {
1978     //we have a dirname provided
1979     dirname=(char *)&data[4];
1980     log->log("Client", Log::DEBUG, "getMediaList for %s", dirname);
1981   }
1982
1983
1984   UCHAR* sendBuffer = new UCHAR[MLISTBUF]; // FIXME hope this is enough
1985   int count = 4; // leave space for the header
1986
1987   MediaList * ml=MediaList::readList(baseConfig,dirname);
1988   if (ml == NULL) {
1989      log->log("Client", Log::ERR, "getMediaList returned NULL");
1990      return 0;
1991   }
1992   //response code (not yet set)
1993   *(ULONG*)&sendBuffer[count] = htonl(0);
1994   count += 4;
1995   //numentries
1996   *(ULONG*)&sendBuffer[count] = htonl(ml->Count());
1997   count += 4;
1998   for (int nm=0;nm<ml->Count() && count < (MLISTBUF-1000);nm++) {
1999       Media *m=ml->Get(nm);
2000       log->log("Client", Log::DEBUG, "found media entry %s, type=%d",m->getFilename(),m->getType());
2001       *(ULONG*)&sendBuffer[count] = htonl(m->getType());
2002       count += 4;
2003       //time stamp
2004       *(ULONG*)&sendBuffer[count] = htonl(m->getTime());
2005       count += 4;
2006       //flags
2007       *(ULONG*)&sendBuffer[count] = htonl(0);
2008       count += 4;
2009       int len=strlen(m->getFilename());
2010       //strlen
2011       *(ULONG*)&sendBuffer[count] = htonl(len+1);
2012       count += 4;
2013       //should have a check for strlen > 1000...
2014       strcpy((char *)&sendBuffer[count],m->getFilename());
2015       count+=len+1;
2016   }
2017   delete ml;
2018
2019   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
2020
2021   log->log("Client", Log::DEBUG, "getMediaList size  %u", ntohl(*(ULONG*)&sendBuffer[0]));
2022
2023   tcp.sendPacket(sendBuffer, count);
2024   delete[] sendBuffer;
2025   log->log("Client", Log::DEBUG, "Written Media list");
2026
2027   return 1;
2028 }
2029
2030 /**
2031   * get image Request:
2032   * 4 length
2033   * 4 VDR_GETIMAGE
2034   * 4 flags (currently unused)
2035   * 4 x size
2036   * 4 y size
2037   * n filename
2038   * n+1 0
2039   * get image response:
2040   * 4 length
2041   * 4 VDR_GETIMAGE
2042   * 4 len of image
2043 */
2044 #define MLISTBUF 500000
2045 int MVPClient::processGetPicture(UCHAR* data, int length)
2046 {
2047   if (length < 12) {
2048     log->log("Client", Log::ERR, "getPicture packet too short %d", length);
2049     return 0;
2050   }
2051   if (imageFile) {
2052     fclose(imageFile);
2053     imageFile=NULL;
2054   }
2055   char * filename=NULL;
2056   if (length > 4) {
2057     //we have a dirname provided
2058     filename=(char *)&data[4];
2059     log->log("Client", Log::DEBUG, "getPicture  %s", filename);
2060   }
2061   else {
2062     log->log("Client", Log::ERR, "getPicture  empty filename");
2063   }
2064   if (filename) {
2065     imageFile=fopen(filename,"r");
2066     if (!imageFile) log->log("Client", Log::ERR, "getPicture unable to open %s",filename);
2067   }
2068   int size=0;
2069   if (imageFile) {
2070     struct stat st;
2071     if ( fstat(fileno(imageFile),&st) == 0) size=st.st_size;
2072   }
2073   UCHAR* sendBuffer = new UCHAR[12];
2074   int count = 4; // leave space for the header
2075   //response code (not yet set)
2076   *(ULONG*)&sendBuffer[count] = htonl(31);
2077   count += 4;
2078   //size
2079   *(ULONG*)&sendBuffer[count] = htonl(size);
2080   count += 4;
2081   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
2082   log->log("Client", Log::DEBUG, "getPicture size  %u", size);
2083
2084   tcp.sendPacket(sendBuffer, count);
2085   delete[] sendBuffer;
2086   log->log("Client", Log::DEBUG, "Written Media list");
2087
2088   return 1;
2089 }
2090
2091
2092 int MVPClient::processGetImageBlock(UCHAR* data, int length)
2093 {
2094   if (!imageFile)
2095   {
2096     log->log("Client", Log::DEBUG, "Get image block called when no image active");
2097     return 0;
2098   }
2099
2100   ULLONG position = ntohll(*(ULLONG*)data);
2101   data += sizeof(ULLONG);
2102   ULONG amount = ntohl(*(ULONG*)data);
2103
2104   log->log("Client", Log::DEBUG, "getImageblock pos = %llu length = %lu", position, amount);
2105
2106   UCHAR sendBuffer[amount + 4];
2107   ULONG amountReceived = 0; // compiler moan.
2108   ULLONG cpos=ftell(imageFile);
2109   if (position != cpos) {
2110     fseek(imageFile,position-cpos,SEEK_CUR);
2111   }
2112   if (position != (ULLONG)ftell(imageFile)) {
2113     log->log("Client", Log::DEBUG, "getImageblock pos = %llu not available", position);
2114   }
2115   else {
2116     amountReceived=fread(&sendBuffer[4],1,amount,imageFile);
2117   }
2118
2119   if (!amountReceived)
2120   {
2121     sendULONG(0);
2122     log->log("Client", Log::DEBUG, "written 4(0) as getblock got 0");
2123   }
2124   else
2125   {
2126     *(ULONG*)&sendBuffer[0] = htonl(amountReceived);
2127     tcp.sendPacket(sendBuffer, amountReceived + 4);
2128     log->log("Client", Log::DEBUG, "written ok %lu", amountReceived);
2129   }
2130
2131   return 1;
2132 }
2133
2134 int MVPClient::processDeleteTimer(UCHAR* buffer, int length)
2135 {
2136   log->log("Client", Log::DEBUG, "Delete timer called");
2137   // get timer
2138   
2139   int position = 0;
2140   
2141   INT delChannel = ntohl(*(ULONG*)&buffer[position]); position += 4;
2142   INT delWeekdays = ntohl(*(ULONG*)&buffer[position]); position += 4;
2143   INT delDay = ntohl(*(ULONG*)&buffer[position]); position += 4;  
2144   INT delStart = ntohl(*(ULONG*)&buffer[position]); position += 4;  
2145   INT delStop = ntohl(*(ULONG*)&buffer[position]); position += 4;
2146     
2147   cTimer* ti = NULL;
2148   for (ti = Timers.First(); ti; ti = Timers.Next(ti))
2149   {
2150     if  ( (ti->Channel()->Number() == delChannel)
2151      &&   ((ti->WeekDays() && (ti->WeekDays() == delWeekdays)) || (!ti->WeekDays() && (ti->Day() == delDay)))
2152      &&   (ti->StartTime() == delStart)
2153      &&   (ti->StopTime() == delStop) )
2154        break;
2155   }
2156   
2157   if (!ti)
2158   {
2159     sendULONG(4);
2160     return 1;
2161   }
2162           
2163   if (!Timers.BeingEdited())
2164   {
2165     if (!ti->Recording())
2166     {
2167       Timers.Del(ti);
2168       Timers.SetModified();
2169       sendULONG(10);
2170       return 1;
2171     }
2172     else
2173     {
2174       log->log("Client", Log::ERR, "Unable to delete timer - timer is running");
2175       sendULONG(3);
2176       return 1;
2177     }  
2178   }
2179   else
2180   {
2181     log->log("Client", Log::ERR, "Unable to delete timer - timers being edited at VDR");
2182     sendULONG(1);
2183     return 1;
2184   }  
2185 }
2186