]> git.vomp.tv Git - vompserver.git/blob - mvpclient.c
Code to move recordings. Not quite finished yet
[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 MVPClient::MVPClient(char* tconfigDirExtra, int tsocket)
27  : tcp(tsocket)
28 {
29   lp = NULL;
30   rp = NULL;
31   recordingManager = NULL;
32   log = Log::getInstance();
33   loggedIn = false;
34   configDirExtra = tconfigDirExtra;
35 }
36
37 MVPClient::~MVPClient()
38 {
39   log->log("Client", Log::DEBUG, "MVP client destructor");
40   if (lp)
41   {
42     delete lp;
43     lp = NULL;
44   }
45   else if (rp)
46   {
47     writeResumeData();
48
49     delete rp;
50     delete recordingManager;
51     rp = NULL;
52     recordingManager = NULL;
53   }
54
55   if (loggedIn) cleanConfig();
56 }
57
58 ULLONG MVPClient::ntohll(ULLONG a)
59 {
60   return htonll(a);
61 }
62
63 ULLONG MVPClient::htonll(ULLONG a)
64 {
65   #if BYTE_ORDER == BIG_ENDIAN
66     return a;
67   #else
68     ULLONG b = 0;
69
70     b = ((a << 56) & 0xFF00000000000000ULL)
71       | ((a << 40) & 0x00FF000000000000ULL)
72       | ((a << 24) & 0x0000FF0000000000ULL)
73       | ((a <<  8) & 0x000000FF00000000ULL)
74       | ((a >>  8) & 0x00000000FF000000ULL)
75       | ((a >> 24) & 0x0000000000FF0000ULL)
76       | ((a >> 40) & 0x000000000000FF00ULL)
77       | ((a >> 56) & 0x00000000000000FFULL) ;
78
79     return b;
80   #endif
81 }
82
83 cChannel* MVPClient::channelFromNumber(ULONG channelNumber)
84 {
85   cChannel* channel = NULL;
86
87   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
88   {
89     if (!channel->GroupSep())
90     {
91       log->log("Client", Log::DEBUG, "Looking for channel %lu::: number: %i name: '%s'", channelNumber, channel->Number(), channel->Name());
92
93       if (channel->Number() == (int)channelNumber)
94       {
95         int vpid = channel->Vpid();
96 #if VDRVERSNUM < 10300
97         int apid1 = channel->Apid1();
98 #else
99         int apid1 = channel->Apid(0);
100 #endif
101         log->log("Client", Log::DEBUG, "Found channel number %lu, vpid = %i, apid1 = %i", channelNumber, vpid, apid1);
102         return channel;
103       }
104     }
105   }
106
107   if (!channel)
108   {
109     log->log("Client", Log::DEBUG, "Channel not found");
110   }
111
112   return channel;
113 }
114
115 void MVPClient::writeResumeData()
116 {
117   config.setValueLongLong("ResumeData", (char*)rp->getCurrentRecording()->FileName(), rp->getLastPosition());
118 }
119
120 void MVPClient::sendULONG(ULONG ul)
121 {
122   UCHAR sendBuffer[8];
123   *(ULONG*)&sendBuffer[0] = htonl(4);
124   *(ULONG*)&sendBuffer[4] = htonl(ul);
125
126   tcp.sendPacket(sendBuffer, 8);
127   log->log("Client", Log::DEBUG, "written ULONG %lu", ul);
128 }
129
130 void MVPClientStartThread(void* arg)
131 {
132   MVPClient* m = (MVPClient*)arg;
133   m->run2();
134   // Nothing external to this class has a reference to it
135   // This is the end of the thread.. so delete m
136   delete m;
137   pthread_exit(NULL);
138 }
139
140 int MVPClient::run()
141 {
142   if (pthread_create(&runThread, NULL, (void*(*)(void*))MVPClientStartThread, (void *)this) == -1) return 0;
143   log->log("Client", Log::DEBUG, "MVPClient run success");
144   return 1;
145 }
146
147 void MVPClient::run2()
148 {
149   // Thread stuff
150   sigset_t sigset;
151   sigfillset(&sigset);
152   pthread_sigmask(SIG_BLOCK, &sigset, NULL);
153   pthread_detach(runThread);  // Detach
154
155   tcp.disableReadTimeout();
156
157   tcp.setSoKeepTime(3);
158   tcp.setNonBlocking();
159
160   UCHAR* buffer;
161   UCHAR* data;
162   int packetLength;
163   ULONG opcode;
164   int result = 0;
165
166   while(1)
167   {
168     log->log("Client", Log::DEBUG, "Waiting");
169     buffer = (UCHAR*)tcp.receivePacket();
170     log->log("Client", Log::DEBUG, "Received packet, length = %u", tcp.getDataLength());
171     if (buffer == NULL)
172     {
173       log->log("Client", Log::DEBUG, "Detected connection closed");
174       break;
175     }
176
177     packetLength = tcp.getDataLength() - 4;
178     opcode = ntohl(*(ULONG*)buffer);
179     data = buffer + 4;
180
181     if (!loggedIn && (opcode != 1))
182     {
183       free(buffer);
184       break;
185     }
186
187     log->log("Client", Log::DEBUG, "SwitchOp");
188     switch(opcode)
189     {
190       case 1:
191         result = processLogin(data, packetLength);
192         break;
193       case 2:
194         result = processGetRecordingsList(data, packetLength);
195         break;
196       case 3:
197         result = processDeleteRecording(data, packetLength);
198         break;
199       case 4:
200         result = processGetSummary(data, packetLength);
201         break;
202       case 5:
203         result = processGetChannelsList(data, packetLength);
204         break;
205       case 6:
206         result = processStartStreamingChannel(data, packetLength);
207         break;
208       case 7:
209         result = processGetBlock(data, packetLength);
210         break;
211       case 8:
212         result = processStopStreaming(data, packetLength);
213         break;
214       case 9:
215         result = processStartStreamingRecording(data, packetLength);
216         break;
217       case 10:
218         result = processGetChannelSchedule(data, packetLength);
219         break;
220       case 11:
221         result = processConfigSave(data, packetLength);
222         break;
223       case 12:
224         result = processConfigLoad(data, packetLength);
225         break;
226       case 13:
227         result = processReScanRecording(data, packetLength);
228         break;
229       case 14:
230         result = processGetTimers(data, packetLength);
231         break;
232       case 15:
233         result = processSetTimer(data, packetLength);
234         break;
235       case 16:
236         result = processPositionFromFrameNumber(data, packetLength);
237         break;
238       case 17:
239         result = processFrameNumberFromPosition(data, packetLength);
240         break;
241       case 18:
242         result = processMoveRecording(data, packetLength);
243         break;
244     }
245
246     free(buffer);
247     if (!result) break;
248   }
249 }
250
251 int MVPClient::processLogin(UCHAR* buffer, int length)
252 {
253   if (length != 6) return 0;
254
255   // Open the config
256
257   const char* configDir = cPlugin::ConfigDirectory(configDirExtra);
258   if (!configDir)
259   {
260     log->log("Client", Log::DEBUG, "No config dir!");
261     return 0;
262   }
263
264   char configFileName[PATH_MAX];
265   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]);
266   config.init(configFileName);
267
268   // Send the login reply
269
270   time_t timeNow = time(NULL);
271   struct tm* timeStruct = localtime(&timeNow);
272   int timeOffset = timeStruct->tm_gmtoff;
273
274   UCHAR sendBuffer[12];
275   *(ULONG*)&sendBuffer[0] = htonl(8);
276   *(ULONG*)&sendBuffer[4] = htonl(timeNow);
277   *(signed int*)&sendBuffer[8] = htonl(timeOffset);
278
279   tcp.sendPacket(sendBuffer, 12);
280   log->log("Client", Log::DEBUG, "written login reply");
281
282   loggedIn = true;
283   return 1;
284 }
285
286 int MVPClient::processGetRecordingsList(UCHAR* data, int length)
287 {
288   UCHAR* sendBuffer = new UCHAR[50000]; // hope this is enough
289   int count = 4; // leave space for the packet length
290   char* point;
291
292
293   int FreeMB;
294   int Percent = VideoDiskSpace(&FreeMB);
295   int Total = (FreeMB / (100 - Percent)) * 100;
296
297   *(ULONG*)&sendBuffer[count] = htonl(Total);
298   count += sizeof(ULONG);
299   *(ULONG*)&sendBuffer[count] = htonl(FreeMB);
300   count += sizeof(ULONG);
301   *(ULONG*)&sendBuffer[count] = htonl(Percent);
302   count += sizeof(ULONG);
303
304
305   cRecordings Recordings;
306   Recordings.Load();
307
308   for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording))
309   {
310     if (count > 49000) break; // just how big is that hard disk?!
311     *(ULONG*)&sendBuffer[count] = htonl(recording->start);// + timeOffset);
312     count += 4;
313
314     point = (char*)recording->Name();
315     strcpy((char*)&sendBuffer[count], point);
316     count += strlen(point) + 1;
317
318     point = (char*)recording->FileName();
319     strcpy((char*)&sendBuffer[count], point);
320     count += strlen(point) + 1;
321   }
322
323   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
324
325   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
326
327   tcp.sendPacket(sendBuffer, count);
328   delete[] sendBuffer;
329   log->log("Client", Log::DEBUG, "Written list");
330
331   return 1;
332 }
333
334 int MVPClient::processDeleteRecording(UCHAR* data, int length)
335 {
336   // data is a pointer to the fileName string
337
338   cRecordings Recordings;
339   Recordings.Load(); // probably have to do this
340
341   cRecording* recording = Recordings.GetByName((char*)data);
342
343   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
344
345   if (recording)
346   {
347     log->log("Client", Log::DEBUG, "deleting recording: %s", recording->Name());
348
349     cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
350     if (!rc)
351     {
352       if (recording->Delete())
353       {
354         // Copy svdrp's way of doing this, see if it works
355 #if VDRVERSNUM > 10300
356         ::Recordings.DelByName(recording->FileName());
357 #endif
358         sendULONG(1);
359       }
360       else
361       {
362         sendULONG(2);
363       }
364     }
365     else
366     {
367       sendULONG(3);
368     }
369   }
370   else
371   {
372     sendULONG(4);
373   }
374
375   return 1;
376 }
377
378 int MVPClient::processMoveRecording(UCHAR* data, int length)
379 {
380   log->log("Client", Log::DEBUG, "Process move recording");
381   char* fileName = (char*)data;
382   char* newPath = NULL;
383
384   for (int k = 0; k < length; k++)
385   {
386     if (data[k] == '\0')
387     {
388       newPath = (char*)&data[k+1];
389       break;
390     }
391   }
392   if (!newPath) return 0;
393
394   cRecordings Recordings;
395   Recordings.Load(); // probably have to do this
396
397   cRecording* recording = Recordings.GetByName((char*)fileName);
398
399   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
400
401   if (recording)
402   {
403     cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
404     if (!rc)
405     {
406 //      if ()
407         log->log("Client", Log::DEBUG, "moving recording: %s", recording->Name());
408         log->log("Client", Log::DEBUG, "moving recording: %s", recording->FileName());
409         log->log("Client", Log::DEBUG, "to: %s", newPath);
410
411         const char* t = recording->FileName();
412
413         char* dateDirName = NULL;   int k;
414         char* titleDirName = NULL;  int j;
415
416         // Find the datedirname
417         for(k = strlen(t) - 1; k >= 0; k--)
418         {
419           if (t[k] == '/')
420           {
421             log->log("Client", Log::DEBUG, "l1: %i", strlen(&t[k+1]) + 1);
422             dateDirName = new char[strlen(&t[k+1]) + 1];
423             strcpy(dateDirName, &t[k+1]);
424             break;
425           }
426         }
427
428         // Find the titledirname
429
430         for(j = k-1; j >= 0; j--)
431         {
432           if (t[j] == '/')
433           {
434             log->log("Client", Log::DEBUG, "l2: %i", (k - j - 1) + 1);
435             titleDirName = new char[(k - j - 1) + 1];
436             memcpy(titleDirName, &t[j+1], k - j - 1);
437             titleDirName[k - j - 1] = '\0';
438             break;
439           }
440         }
441
442         log->log("Client", Log::DEBUG, "datedirname: %s", dateDirName);
443         log->log("Client", Log::DEBUG, "titledirname: %s", titleDirName);
444
445         log->log("Client", Log::DEBUG, "viddir: %s", VideoDirectory);
446
447         char* newContainer = new char[strlen(VideoDirectory) + strlen(newPath) + 1 + strlen(titleDirName) + 1];
448         log->log("Client", Log::DEBUG, "l10: %i", strlen(VideoDirectory) + strlen(newPath) + 1 + strlen(titleDirName) + 1);
449         sprintf(newContainer, "%s%s/%s", VideoDirectory, newPath, titleDirName);
450
451         // FIXME Check whether this already exists before mkdiring it
452
453         log->log("Client", Log::DEBUG, "%s", newContainer);
454
455
456         struct stat dstat;
457         int statret = stat(newContainer, &dstat);
458         if ((statret == -1) && (errno == ENOENT)) // Dir does not exist
459         {
460           log->log("Client", Log::DEBUG, "new dir does not exist");
461           int mkdirret = mkdir(newContainer, 0755);
462           if (mkdirret != 0)
463           {
464             delete[] dateDirName;
465             delete[] titleDirName;
466             delete[] newContainer;
467             sendULONG(5);
468             return 1;
469           }
470         }
471         else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR))) // Something exists but it's not a dir
472         {
473           delete[] dateDirName;
474           delete[] titleDirName;
475           delete[] newContainer;
476           sendULONG(5);
477           return 1;
478         }
479
480         // Ok, the directory container has been made, or it pre-existed.
481
482         char* newDir = new char[strlen(newContainer) + 1 + strlen(dateDirName) + 1];
483         sprintf(newDir, "%s/%s", newContainer, dateDirName);
484
485         log->log("Client", Log::DEBUG, "doing rename '%s' '%s'", t, newDir);
486         int renameret = rename(t, newDir);
487         if (renameret == 0)
488         {
489           // Success. Test for remove old dir containter
490           char* oldTitleDir = new char[k+1];
491           memcpy(oldTitleDir, t, k);
492           oldTitleDir[k] = '\0';
493           log->log("Client", Log::DEBUG, "len: %i, cp: %i, strlen: %i, oldtitledir: %s", k+1, k, strlen(oldTitleDir), oldTitleDir);
494           rmdir(oldTitleDir); // can't do anything about a fail result at this point.
495           delete[] oldTitleDir;
496         }
497
498         delete[] dateDirName;
499         delete[] titleDirName;
500         delete[] newContainer;
501         delete[] newDir;
502
503         if (renameret == 0) sendULONG(1);
504         else sendULONG(5);
505 //      }
506 //      else
507 //      {
508 //        sendULONG(2);
509 //      }
510     }
511     else
512     {
513       sendULONG(3);
514     }
515   }
516   else
517   {
518     sendULONG(4);
519   }
520
521   return 1;
522 }
523
524 int MVPClient::processGetSummary(UCHAR* data, int length)
525 {
526   // data is a pointer to the fileName string
527
528   cRecordings Recordings;
529   Recordings.Load(); // probably have to do this
530
531   cRecording *recording = Recordings.GetByName((char*)data);
532
533   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
534
535   if (recording)
536   {
537     UCHAR* sendBuffer = new UCHAR[50000]; // hope this is enough
538     int count = 4; // leave space for the packet length
539     char* point;
540
541 #if VDRVERSNUM < 10300
542     point = (char*)recording->Summary();
543 #else
544     const cRecordingInfo *Info = recording->Info();
545     point = (char*)Info->ShortText();
546     log->log("Client", Log::DEBUG, "info pointer %p summary pointer %p", Info, point);
547     if (isempty(point))
548     {
549       point = (char*)Info->Description();
550       log->log("Client", Log::DEBUG, "description pointer %p", point);
551     }
552 #endif
553
554     if (point)
555     {
556       strcpy((char*)&sendBuffer[count], point);
557       count += strlen(point) + 1;
558     }
559     else
560     {
561       strcpy((char*)&sendBuffer[count], "");
562       count += 1;
563     }
564
565     *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
566
567     log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
568
569     tcp.sendPacket(sendBuffer, count);
570     delete[] sendBuffer;
571     log->log("Client", Log::DEBUG, "Written summary");
572
573
574   }
575   else
576   {
577     sendULONG(0);
578   }
579
580   return 1;
581 }
582
583 int MVPClient::processGetChannelsList(UCHAR* data, int length)
584 {
585   UCHAR* sendBuffer = new UCHAR[50000]; // FIXME hope this is enough
586   int count = 4; // leave space for the packet length
587   char* point;
588   ULONG type;
589
590   char* chanConfig = config.getValueString("General", "Channels");
591   int allChans = 1;
592   if (chanConfig) allChans = strcasecmp(chanConfig, "FTA only");
593
594   for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
595   {
596 #if VDRVERSNUM < 10300
597     if (!channel->GroupSep() && (!channel->Ca() || allChans))
598 #else
599     if (!channel->GroupSep() && (!channel->Ca(0) || allChans))
600 #endif
601     {
602       log->log("Client", Log::DEBUG, "name: '%s'", channel->Name());
603
604       if (channel->Vpid()) type = 1;
605 #if VDRVERSNUM < 10300
606       else type = 2;
607 #else
608       else if (channel->Apid(0)) type = 2;
609       else continue;
610 #endif
611
612       if (count > 49000) break;
613       *(ULONG*)&sendBuffer[count] = htonl(channel->Number());
614       count += 4;
615
616       *(ULONG*)&sendBuffer[count] = htonl(type);
617       count += 4;
618
619       point = (char*)channel->Name();
620       strcpy((char*)&sendBuffer[count], point);
621       count += strlen(point) + 1;
622     }
623   }
624
625   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
626
627   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
628
629   tcp.sendPacket(sendBuffer, count);
630   delete[] sendBuffer;
631   log->log("Client", Log::DEBUG, "Written channels list");
632
633   return 1;
634 }
635
636 int MVPClient::processStartStreamingChannel(UCHAR* data, int length)
637 {
638   log->log("Client", Log::DEBUG, "length = %i", length);
639   ULONG channelNumber = ntohl(*(ULONG*)data);
640
641   cChannel* channel = channelFromNumber(channelNumber);
642   if (!channel)
643   {
644     sendULONG(0);
645     return 1;
646   }
647
648   // get the priority we should use
649   int fail = 1;
650   int priority = config.getValueLong("General", "Live priority", &fail);
651   if (!fail)
652   {
653     log->log("Client", Log::DEBUG, "Config: Live TV priority: %i", priority);
654   }
655   else
656   {
657     log->log("Client", Log::DEBUG, "Config: Live TV priority config fail");
658     priority = 0;
659   }
660
661   // a bit of sanity..
662   if (priority < 0) priority = 0;
663   if (priority > 99) priority = 99;
664
665   log->log("Client", Log::DEBUG, "Using live TV priority %i", priority);
666   lp = MVPReceiver::create(channel, priority);
667
668   if (!lp)
669   {
670     sendULONG(0);
671     return 1;
672   }
673
674   if (!lp->init())
675   {
676     delete lp;
677     lp = NULL;
678     sendULONG(0);
679     return 1;
680   }
681
682   sendULONG(1);
683   return 1;
684 }
685
686 int MVPClient::processStopStreaming(UCHAR* data, int length)
687 {
688   log->log("Client", Log::DEBUG, "STOP STREAMING RECEIVED");
689   if (lp)
690   {
691     delete lp;
692     lp = NULL;
693   }
694   else if (rp)
695   {
696     writeResumeData();
697
698     delete rp;
699     delete recordingManager;
700     rp = NULL;
701     recordingManager = NULL;
702   }
703
704   sendULONG(1);
705   return 1;
706 }
707
708 int MVPClient::processGetBlock(UCHAR* data, int length)
709 {
710   if (!lp && !rp)
711   {
712     log->log("Client", Log::DEBUG, "Get block called when no streaming happening!");
713     return 0;
714   }
715
716   ULLONG position = ntohll(*(ULLONG*)data);
717   data += sizeof(ULLONG);
718   ULONG amount = ntohl(*(ULONG*)data);
719
720   log->log("Client", Log::DEBUG, "getblock pos = %llu length = %lu", position, amount);
721
722   UCHAR sendBuffer[amount + 4];
723   ULONG amountReceived = 0; // compiler moan.
724   if (lp)
725   {
726     log->log("Client", Log::DEBUG, "getting from live");
727     amountReceived = lp->getBlock(&sendBuffer[4], amount);
728
729     if (!amountReceived)
730     {
731       // vdr has possibly disconnected the receiver
732       log->log("Client", Log::DEBUG, "VDR has disconnected the live receiver");
733       delete lp;
734       lp = NULL;
735     }
736   }
737   else if (rp)
738   {
739     log->log("Client", Log::DEBUG, "getting from recording");
740     amountReceived = rp->getBlock(&sendBuffer[4], position, amount);
741   }
742
743   if (!amountReceived)
744   {
745     sendULONG(0);
746     log->log("Client", Log::DEBUG, "written 4(0) as getblock got 0");
747   }
748   else
749   {
750     *(ULONG*)&sendBuffer[0] = htonl(amountReceived);
751     tcp.sendPacket(sendBuffer, amountReceived + 4);
752     log->log("Client", Log::DEBUG, "written ok %lu", amountReceived);
753   }
754
755   return 1;
756 }
757
758 int MVPClient::processStartStreamingRecording(UCHAR* data, int length)
759 {
760   // data is a pointer to the fileName string
761
762   recordingManager = new cRecordings;
763   recordingManager->Load();
764
765   cRecording* recording = recordingManager->GetByName((char*)data);
766
767   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
768
769   if (recording)
770   {
771     rp = new RecPlayer(recording);
772
773     UCHAR sendBuffer[12];
774     *(ULONG*)&sendBuffer[0] = htonl(8);
775     *(ULLONG*)&sendBuffer[4] = htonll(rp->getTotalLength());
776
777     tcp.sendPacket(sendBuffer, 12);
778     log->log("Client", Log::DEBUG, "written totalLength");
779   }
780   else
781   {
782     delete recordingManager;
783     recordingManager = NULL;
784   }
785   return 1;
786 }
787
788 int MVPClient::processReScanRecording(UCHAR* data, int length)
789 {
790   ULLONG retval = 0;
791
792   if (!rp)
793   {
794     log->log("Client", Log::DEBUG, "Rescan recording called when no recording being played!");
795   }
796   else
797   {
798     rp->scan();
799     retval = rp->getTotalLength();
800   }
801
802   UCHAR sendBuffer[12];
803   *(ULONG*)&sendBuffer[0] = htonl(8);
804   *(ULLONG*)&sendBuffer[4] = htonll(retval);
805
806   tcp.sendPacket(sendBuffer, 12);
807   log->log("Client", Log::DEBUG, "Rescan recording, wrote new length to client");
808   return 1;
809 }
810
811 int MVPClient::processPositionFromFrameNumber(UCHAR* data, int length)
812 {
813   ULLONG retval = 0;
814
815   ULONG frameNumber = ntohl(*(ULONG*)data);
816   data += 4;
817
818   if (!rp)
819   {
820     log->log("Client", Log::DEBUG, "Rescan recording called when no recording being played!");
821   }
822   else
823   {
824     retval = rp->positionFromFrameNumber(frameNumber);
825   }
826
827   UCHAR sendBuffer[12];
828   *(ULONG*)&sendBuffer[0] = htonl(8);
829   *(ULLONG*)&sendBuffer[4] = htonll(retval);
830
831   tcp.sendPacket(sendBuffer, 12);
832   log->log("Client", Log::DEBUG, "Wrote posFromFrameNum reply to client");
833   return 1;
834 }
835
836 int MVPClient::processFrameNumberFromPosition(UCHAR* data, int length)
837 {
838   ULONG retval = 0;
839
840   ULLONG position = ntohll(*(ULLONG*)data);
841   data += 8;
842
843   if (!rp)
844   {
845     log->log("Client", Log::DEBUG, "Rescan recording called when no recording being played!");
846   }
847   else
848   {
849     retval = rp->frameNumberFromPosition(position);
850   }
851
852   UCHAR sendBuffer[8];
853   *(ULONG*)&sendBuffer[0] = htonl(4);
854   *(ULONG*)&sendBuffer[4] = htonl(retval);
855
856   tcp.sendPacket(sendBuffer, 8);
857   log->log("Client", Log::DEBUG, "Wrote frameNumFromPos reply to client");
858   return 1;
859 }
860
861 int MVPClient::processGetChannelSchedule(UCHAR* data, int length)
862 {
863   ULONG channelNumber = ntohl(*(ULONG*)data);
864   data += 4;
865   ULONG startTime = ntohl(*(ULONG*)data);
866   data += 4;
867   ULONG duration = ntohl(*(ULONG*)data);
868
869   log->log("Client", Log::DEBUG, "get schedule called for channel %lu", channelNumber);
870
871   cChannel* channel = channelFromNumber(channelNumber);
872   if (!channel)
873   {
874     sendULONG(0);
875     log->log("Client", Log::DEBUG, "written 0 because channel = NULL");
876     return 1;
877   }
878
879   log->log("Client", Log::DEBUG, "Got channel");
880
881 #if VDRVERSNUM < 10300
882   cMutexLock MutexLock;
883   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
884 #else
885   cSchedulesLock MutexLock;
886   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
887 #endif
888   if (!Schedules)
889   {
890     sendULONG(0);
891     log->log("Client", Log::DEBUG, "written 0 because Schedule!s! = NULL");
892     return 1;
893   }
894
895   log->log("Client", Log::DEBUG, "Got schedule!s! object");
896
897   const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
898   if (!Schedule)
899   {
900     sendULONG(0);
901     log->log("Client", Log::DEBUG, "written 0 because Schedule = NULL");
902     return 1;
903   }
904
905   log->log("Client", Log::DEBUG, "Got schedule object");
906
907   UCHAR* sendBuffer = (UCHAR*)malloc(100000);
908   ULONG sendBufferLength = 100000;
909   ULONG sendBufferUsed = sizeof(ULONG); // leave a hole for the entire packet length
910
911   char* empty = "";
912
913   // assign all the event info to temp vars then we know exactly what size they are
914   ULONG thisEventID;
915   ULONG thisEventTime;
916   ULONG thisEventDuration;
917   const char* thisEventTitle;
918   const char* thisEventSubTitle;
919   const char* thisEventDescription;
920
921   ULONG constEventLength = sizeof(thisEventID) + sizeof(thisEventTime) + sizeof(thisEventDuration);
922   ULONG thisEventLength;
923
924 #if VDRVERSNUM < 10300
925
926   const cEventInfo *event;
927   for (int eventNumber = 0; eventNumber < Schedule->NumEvents(); eventNumber++)
928   {
929     event = Schedule->GetEventNumber(eventNumber);
930
931     thisEventID = event->GetEventID();
932     thisEventTime = event->GetTime();
933     thisEventDuration = event->GetDuration();
934     thisEventTitle = event->GetTitle();
935     thisEventSubTitle = event->GetSubtitle();
936     thisEventDescription = event->GetExtendedDescription();
937
938 #else
939
940   for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event))
941   {
942     thisEventID = event->EventID();
943     thisEventTime = event->StartTime();
944     thisEventDuration = event->Duration();
945     thisEventTitle = event->Title();
946     thisEventSubTitle = NULL;
947     thisEventDescription = event->Description();
948
949 #endif
950
951     log->log("Client", Log::DEBUG, "Got an event object %p", event);
952
953     //in the past filter
954     if ((thisEventTime + thisEventDuration) < (ULONG)time(NULL)) continue;
955
956     //start time filter
957     if ((thisEventTime + thisEventDuration) <= startTime) continue;
958
959     //duration filter
960     if (thisEventTime >= (startTime + duration)) continue;
961
962     if (!thisEventTitle) thisEventTitle = empty;
963     if (!thisEventSubTitle) thisEventSubTitle = empty;
964     if (!thisEventDescription) thisEventDescription = empty;
965
966     thisEventLength = constEventLength + strlen(thisEventTitle) + 1 + strlen(thisEventSubTitle) + 1 + strlen(thisEventDescription) + 1;
967
968     log->log("Client", Log::DEBUG, "Done s1");
969
970     // now extend the buffer if necessary
971     if ((sendBufferUsed + thisEventLength) > sendBufferLength)
972     {
973       log->log("Client", Log::DEBUG, "Extending buffer");
974       sendBufferLength += 100000;
975       UCHAR* temp = (UCHAR*)realloc(sendBuffer, sendBufferLength);
976       if (temp == NULL)
977       {
978         free(sendBuffer);
979         UCHAR sendBuffer2[8];
980         *(ULONG*)&sendBuffer2[0] = htonl(4);
981         *(ULONG*)&sendBuffer2[4] = htonl(0);
982         tcp.sendPacket(sendBuffer2, 8);
983         log->log("Client", Log::DEBUG, "written 0 because failed to realloc packet");
984         return 1;
985       }
986       sendBuffer = temp;
987     }
988
989     log->log("Client", Log::DEBUG, "Done s2");
990
991     *(ULONG*)&sendBuffer[sendBufferUsed] = htonl(thisEventID);       sendBufferUsed += sizeof(ULONG);
992     *(ULONG*)&sendBuffer[sendBufferUsed] = htonl(thisEventTime);     sendBufferUsed += sizeof(ULONG);
993     *(ULONG*)&sendBuffer[sendBufferUsed] = htonl(thisEventDuration); sendBufferUsed += sizeof(ULONG);
994
995     strcpy((char*)&sendBuffer[sendBufferUsed], thisEventTitle);       sendBufferUsed += strlen(thisEventTitle) + 1;
996     strcpy((char*)&sendBuffer[sendBufferUsed], thisEventSubTitle);    sendBufferUsed += strlen(thisEventSubTitle) + 1;
997     strcpy((char*)&sendBuffer[sendBufferUsed], thisEventDescription); sendBufferUsed += strlen(thisEventDescription) + 1;
998
999     log->log("Client", Log::DEBUG, "Done s3 %lu", sendBufferUsed);
1000   }
1001
1002   log->log("Client", Log::DEBUG, "Got all event data");
1003
1004   if (sendBufferUsed == sizeof(ULONG))
1005   {
1006     // No data
1007     sendULONG(0);
1008     log->log("Client", Log::DEBUG, "Written 0 because no data");
1009   }
1010   else
1011   {
1012     // Write the length into the first 4 bytes. It's sendBufferUsed - 4 because of the hole!
1013     *(ULONG*)&sendBuffer[0] = htonl(sendBufferUsed - sizeof(ULONG));
1014     tcp.sendPacket(sendBuffer, sendBufferUsed);
1015     log->log("Client", Log::DEBUG, "written %lu schedules packet", sendBufferUsed);
1016   }
1017
1018   free(sendBuffer);
1019
1020   return 1;
1021 }
1022
1023 int MVPClient::processConfigSave(UCHAR* buffer, int length)
1024 {
1025   char* section = (char*)buffer;
1026   char* key = NULL;
1027   char* value = NULL;
1028
1029   for (int k = 0; k < length; k++)
1030   {
1031     if (buffer[k] == '\0')
1032     {
1033       if (!key)
1034       {
1035         key = (char*)&buffer[k+1];
1036       }
1037       else
1038       {
1039         value = (char*)&buffer[k+1];
1040         break;
1041       }
1042     }
1043   }
1044
1045   // if the last string (value) doesnt have null terminator, give up
1046   if (buffer[length - 1] != '\0') return 0;
1047
1048   log->log("Client", Log::DEBUG, "Config save: %s %s %s", section, key, value);
1049   if (config.setValueString(section, key, value))
1050   {
1051     sendULONG(1);
1052   }
1053   else
1054   {
1055     sendULONG(0);
1056   }
1057
1058   return 1;
1059 }
1060
1061 int MVPClient::processConfigLoad(UCHAR* buffer, int length)
1062 {
1063   char* section = (char*)buffer;
1064   char* key = NULL;
1065
1066   for (int k = 0; k < length; k++)
1067   {
1068     if (buffer[k] == '\0')
1069     {
1070       key = (char*)&buffer[k+1];
1071       break;
1072     }
1073   }
1074
1075   char* value = config.getValueString(section, key);
1076
1077   if (value)
1078   {
1079     UCHAR sendBuffer[4 + strlen(value) + 1];
1080     *(ULONG*)&sendBuffer[0] = htonl(strlen(value) + 1);
1081     strcpy((char*)&sendBuffer[4], value);
1082     tcp.sendPacket(sendBuffer, 4 + strlen(value) + 1);
1083
1084     log->log("Client", Log::DEBUG, "Written config load packet");
1085     delete[] value;
1086   }
1087   else
1088   {
1089     UCHAR sendBuffer[8];
1090     *(ULONG*)&sendBuffer[0] = htonl(4);
1091     *(ULONG*)&sendBuffer[4] = htonl(0);
1092     tcp.sendPacket(sendBuffer, 8);
1093
1094     log->log("Client", Log::DEBUG, "Written config load failed packet");
1095   }
1096
1097   return 1;
1098 }
1099
1100 void MVPClient::cleanConfig()
1101 {
1102   log->log("Client", Log::DEBUG, "Clean config");
1103
1104   cRecordings Recordings;
1105   Recordings.Load();
1106
1107   int numReturns;
1108   int length;
1109   char* resumes = config.getSectionKeyNames("ResumeData", numReturns, length);
1110   char* position = resumes;
1111   for(int k = 0; k < numReturns; k++)
1112   {
1113     log->log("Client", Log::DEBUG, "EXAMINING: %i %i %p %s", k, numReturns, position, position);
1114
1115     cRecording* recording = Recordings.GetByName(position);
1116     if (!recording)
1117     {
1118       // doesn't exist anymore
1119       log->log("Client", Log::DEBUG, "Found a recording that doesn't exist anymore");
1120       config.deleteValue("ResumeData", position);
1121     }
1122     else
1123     {
1124       log->log("Client", Log::DEBUG, "This recording still exists");
1125     }
1126
1127     position += strlen(position) + 1;
1128   }
1129
1130   delete[] resumes;
1131 }
1132
1133
1134
1135
1136
1137
1138 /*
1139     event = Schedule->GetPresentEvent();
1140
1141     fprintf(f, "\n\nCurrent event\n\n");
1142
1143     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", 0, event->GetEventID(), event->GetTime(), event->GetDuration());
1144     fprintf(f, "Event %i title = %s subtitle = %s\n", 0, event->GetTitle(), event->GetSubtitle());
1145     fprintf(f, "Event %i extendeddescription = %s\n", 0, event->GetExtendedDescription());
1146     fprintf(f, "Event %i isFollowing = %i, isPresent = %i\n", 0, event->IsFollowing(), event->IsPresent());
1147
1148     event = Schedule->GetFollowingEvent();
1149
1150     fprintf(f, "\n\nFollowing event\n\n");
1151
1152     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", 0, event->GetEventID(), event->GetTime(), event->GetDuration());
1153     fprintf(f, "Event %i title = %s subtitle = %s\n", 0, event->GetTitle(), event->GetSubtitle());
1154     fprintf(f, "Event %i extendeddescription = %s\n", 0, event->GetExtendedDescription());
1155     fprintf(f, "Event %i isFollowing = %i, isPresent = %i\n", 0, event->IsFollowing(), event->IsPresent());
1156
1157     fprintf(f, "\n\n");
1158 */
1159
1160 /*
1161     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", eventNumber, event->GetEventID(), event->GetTime(), event->GetDuration());
1162     fprintf(f, "Event %i title = %s subtitle = %s\n", eventNumber, event->GetTitle(), event->GetSubtitle());
1163     fprintf(f, "Event %i extendeddescription = %s\n", eventNumber, event->GetExtendedDescription());
1164     fprintf(f, "Event %i isFollowing = %i, isPresent = %i\n", eventNumber, event->IsFollowing(), event->IsPresent());
1165
1166     fprintf(f, "\n\n");
1167 */
1168
1169 /*
1170
1171
1172 void MVPClient::test2()
1173 {
1174   FILE* f = fopen("/tmp/s.txt", "w");
1175
1176 #if VDRVERSNUM < 10300
1177   cMutexLock MutexLock;
1178   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
1179 #else
1180   cSchedulesLock MutexLock;
1181   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
1182 #endif
1183
1184   if (!Schedules)
1185   {
1186     fprintf(f, "Schedules = NULL\n");
1187     fclose(f);
1188     return;
1189   }
1190
1191   fprintf(f, "Schedules dump:\n");
1192   Schedules->Dump(f);
1193
1194
1195   const cSchedule *Schedule;
1196   int scheduleNumber = 0;
1197
1198   tChannelID tchid;
1199   cChannel *thisChannel;
1200
1201 #if VDRVERSNUM < 10300
1202   const cEventInfo *event;
1203   int eventNumber = 0;
1204 #else
1205   const cEvent *event;
1206 #endif
1207
1208 //    Schedule = Schedules->GetSchedule(channel->GetChannelID());
1209 //    Schedule = Schedules->GetSchedule();
1210   Schedule = Schedules->First();
1211   if (!Schedule)
1212   {
1213     fprintf(f, "First Schedule = NULL\n");
1214     fclose(f);
1215     return;
1216   }
1217
1218   while (Schedule)
1219   {
1220     fprintf(f, "Schedule #%i\n", scheduleNumber);
1221     fprintf(f, "-------------\n\n");
1222
1223 #if VDRVERSNUM < 10300
1224     tchid = Schedule->GetChannelID();
1225 #else
1226     tchid = Schedule->ChannelID();
1227 #endif
1228
1229 #if VDRVERSNUM < 10300
1230     fprintf(f, "ChannelID.ToString() = %s\n", tchid.ToString());
1231     fprintf(f, "NumEvents() = %i\n", Schedule->NumEvents());
1232 #else
1233 //  put the count at the end.
1234 #endif
1235
1236     thisChannel = Channels.GetByChannelID(tchid, true);
1237     if (thisChannel)
1238     {
1239       fprintf(f, "Channel Number: %p %i\n", thisChannel, thisChannel->Number());
1240     }
1241     else
1242     {
1243       fprintf(f, "thisChannel = NULL for tchid\n");
1244     }
1245
1246 #if VDRVERSNUM < 10300
1247     for (eventNumber = 0; eventNumber < Schedule->NumEvents(); eventNumber++)
1248     {
1249       event = Schedule->GetEventNumber(eventNumber);
1250       fprintf(f, "Event %i tableid = %i timestring = %s endtimestring = %s\n", eventNumber, event->GetTableID(), event->GetTimeString(), event->GetEndTimeString());
1251       fprintf(f, "Event %i date = %s isfollowing = %i ispresent = %i\n", eventNumber, event->GetDate(), event->IsFollowing(), event->IsPresent());
1252       fprintf(f, "Event %i extendeddescription = %s\n", eventNumber, event->GetExtendedDescription());
1253       fprintf(f, "Event %i subtitle = %s title = %s\n", eventNumber, event->GetSubtitle(), event->GetTitle());
1254       fprintf(f, "Event %i eventid = %u duration = %li time = %lu channelnumber = %i\n", eventNumber, event->GetEventID(), event->GetDuration(), event->GetTime(), event->GetChannelNumber());
1255       fprintf(f, "Event %u dump:\n", eventNumber);
1256       event->Dump(f);
1257       fprintf(f, "\n\n");
1258     }
1259 #else
1260 //  This whole section needs rewriting to walk the list.
1261     event = Schedule->Events()->First();
1262     while (event) {
1263       event = Schedule->Events()->Next(event);
1264     }
1265 #endif
1266
1267
1268     fprintf(f, "\nDump from object:\n");
1269     Schedule->Dump(f);
1270     fprintf(f, "\nEND\n");
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280     fprintf(f, "End of current Schedule\n\n\n");
1281
1282     Schedule = (const cSchedule *)Schedules->Next(Schedule);
1283     scheduleNumber++;
1284   }
1285
1286   fclose(f);
1287 }
1288
1289
1290
1291 */
1292
1293
1294
1295 /*
1296   const cEventInfo *GetPresentEvent(void) const;
1297   const cEventInfo *GetFollowingEvent(void) const;
1298   const cEventInfo *GetEvent(unsigned short uEventID, time_t tTime = 0) const;
1299   const cEventInfo *GetEventAround(time_t tTime) const;
1300   const cEventInfo *GetEventNumber(int n) const { return Events.Get(n); }
1301
1302
1303   const unsigned char GetTableID(void) const;
1304   const char *GetTimeString(void) const;
1305   const char *GetEndTimeString(void) const;
1306   const char *GetDate(void) const;
1307   bool IsFollowing(void) const;
1308   bool IsPresent(void) const;
1309   const char *GetExtendedDescription(void) const;
1310   const char *GetSubtitle(void) const;
1311   const char *GetTitle(void) const;
1312   unsigned short GetEventID(void) const;
1313   long GetDuration(void) const;
1314   time_t GetTime(void) const;
1315   tChannelID GetChannelID(void) const;
1316   int GetChannelNumber(void) const { return nChannelNumber; }
1317   void SetChannelNumber(int ChannelNumber) const { ((cEventInfo *)this)->nChannelNumber = ChannelNumber; } // doesn't modify the EIT data, so it's ok to make it 'const'
1318   void Dump(FILE *f, const char *Prefix = "") const;
1319
1320 */
1321
1322
1323 /*
1324 void MVPClient::test(int channelNumber)
1325 {
1326   FILE* f = fopen("/tmp/test.txt", "w");
1327
1328   cMutexLock MutexLock;
1329   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
1330
1331   if (!Schedules)
1332   {
1333     fprintf(f, "Schedules = NULL\n");
1334     fclose(f);
1335     return;
1336   }
1337
1338   fprintf(f, "Schedules dump:\n");
1339 //  Schedules->Dump(f);
1340
1341   const cSchedule *Schedule;
1342   cChannel *thisChannel;
1343   const cEventInfo *event;
1344
1345   thisChannel = channelFromNumber(channelNumber);
1346   if (!thisChannel)
1347   {
1348     fprintf(f, "thisChannel = NULL\n");
1349     fclose(f);
1350     return;
1351   }
1352
1353   Schedule = Schedules->GetSchedule(thisChannel->GetChannelID());
1354 //    Schedule = Schedules->GetSchedule();
1355 //  Schedule = Schedules->First();
1356   if (!Schedule)
1357   {
1358     fprintf(f, "First Schedule = NULL\n");
1359     fclose(f);
1360     return;
1361   }
1362
1363   fprintf(f, "NumEvents() = %i\n\n", Schedule->NumEvents());
1364
1365   // For some channels VDR seems to pick a random point in time to
1366   // start dishing out events, but they are in order
1367   // at some point in the list the time snaps to the current event
1368
1369
1370
1371
1372   for (int eventNumber = 0; eventNumber < Schedule->NumEvents(); eventNumber++)
1373   {
1374     event = Schedule->GetEventNumber(eventNumber);
1375     fprintf(f, "Event %i eventid = %u time = %lu duration = %li\n", eventNumber, event->GetEventID(), event->GetTime(), event->GetDuration());
1376     fprintf(f, "Event %i title = %s subtitle = %s\n", eventNumber, event->GetTitle(), event->GetSubtitle());
1377     fprintf(f, "Event %i extendeddescription = %s\n", eventNumber, event->GetExtendedDescription());
1378     fprintf(f, "\n\n");
1379   }
1380
1381   fprintf(f, "\nEND\n");
1382
1383   fclose(f);
1384 }
1385
1386 */
1387
1388
1389
1390 /*
1391
1392
1393 Right, so
1394
1395 Schedules = the collection of all the Schedule objects
1396 Schedule  = One schedule, contants all the events for a channel
1397 Event     = One programme
1398
1399
1400 Want:
1401
1402 Event ID
1403 Time
1404 Duration
1405 Title
1406 Subtitle (used for "Programmes resume at ...")
1407 Description
1408
1409 IsPresent ? easy to work out tho. Oh it doesn't always work
1410
1411 */
1412
1413 /*
1414 void MVPClient::test2()
1415 {
1416   log->log("-", Log::DEBUG, "Timers List");
1417
1418   for (int i = 0; i < Timers.Count(); i++)
1419   {
1420     cTimer *timer = Timers.Get(i);
1421     //Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, timer->ToText());
1422     log->log("-", Log::DEBUG, "i=%i count=%i index=%d", i, Timers.Count(), timer->Index() + 1);
1423 #if VDRVERSNUM < 10300
1424     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());
1425 #else
1426     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());
1427 #endif
1428     log->log("-", Log::DEBUG, "channel=%i file=%s summary=%s", timer->Channel()->Number(), timer->File(), timer->Summary());
1429     log->log("-", Log::DEBUG, "");
1430   }
1431
1432   // asprintf(&buffer, "%d:%s:%s  :%04d:%04d:%d:%d:%s:%s\n",
1433 //            active, (UseChannelID ? Channel()->GetChannelID().ToString() : itoa(Channel()->Number())),
1434 //            PrintDay(day, firstday), start, stop, priority, lifetime, file, summary ? summary : "");
1435 }
1436 */
1437
1438 /*
1439 Active seems to be a bool - whether the timer should be done or not. If set to inactive it stays around after its time
1440 recording is a bool, 0 for not currently recording, 1 for currently recording
1441 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
1442 */
1443
1444
1445 int MVPClient::processGetTimers(UCHAR* buffer, int length)
1446 {
1447   UCHAR* sendBuffer = new UCHAR[50000]; // FIXME hope this is enough
1448   int count = 4; // leave space for the packet length
1449
1450   const char* fileName;
1451   cTimer *timer;
1452   int numTimers = Timers.Count();
1453
1454   *(ULONG*)&sendBuffer[count] = htonl(numTimers);    count += 4;
1455
1456   for (int i = 0; i < numTimers; i++)
1457   {
1458     if (count > 49000) break;
1459
1460     timer = Timers.Get(i);
1461
1462 #if VDRVERSNUM < 10300
1463     *(ULONG*)&sendBuffer[count] = htonl(timer->Active());                 count += 4;
1464 #else
1465     *(ULONG*)&sendBuffer[count] = htonl(timer->HasFlags(tfActive));       count += 4;
1466 #endif
1467     *(ULONG*)&sendBuffer[count] = htonl(timer->Recording());              count += 4;
1468     *(ULONG*)&sendBuffer[count] = htonl(timer->Pending());                count += 4;
1469     *(ULONG*)&sendBuffer[count] = htonl(timer->Priority());               count += 4;
1470     *(ULONG*)&sendBuffer[count] = htonl(timer->Lifetime());               count += 4;
1471     *(ULONG*)&sendBuffer[count] = htonl(timer->Channel()->Number());      count += 4;
1472     *(ULONG*)&sendBuffer[count] = htonl(timer->StartTime());              count += 4;
1473     *(ULONG*)&sendBuffer[count] = htonl(timer->StopTime());               count += 4;
1474
1475     fileName = timer->File();
1476     strcpy((char*)&sendBuffer[count], fileName);
1477     count += strlen(fileName) + 1;
1478   }
1479
1480   *(ULONG*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
1481
1482   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(ULONG*)&sendBuffer[0]));
1483
1484   tcp.sendPacket(sendBuffer, count);
1485   delete[] sendBuffer;
1486   log->log("Client", Log::DEBUG, "Written timers list");
1487
1488   return 1;
1489 }
1490
1491 int MVPClient::processSetTimer(UCHAR* buffer, int length)
1492 {
1493   char* timerString = new char[strlen((char*)buffer) + 1];
1494   strcpy(timerString, (char*)buffer);
1495
1496 #if VDRVERSNUM < 10300
1497
1498   // If this is VDR 1.2 the date part of the timer string must be reduced
1499   // to just DD rather than YYYY-MM-DD
1500
1501   int s = 0; // source
1502   int d = 0; // destination
1503   int c = 0; // count
1504   while(c != 2) // copy up to date section, including the second ':'
1505   {
1506     timerString[d] = buffer[s];
1507     if (buffer[s] == ':') c++;
1508     ++s;
1509     ++d;
1510   }
1511   // now it has copied up to the date section
1512   c = 0;
1513   while(c != 2) // waste YYYY-MM-
1514   {
1515     if (buffer[s] == '-') c++;
1516     ++s;
1517   }
1518   // now source is at the DD
1519   memcpy(&timerString[d], &buffer[s], length - s);
1520   d += length - s;
1521   timerString[d] = '\0';
1522
1523   log->log("Client", Log::DEBUG, "Timer string after 1.2 conversion:");
1524   log->log("Client", Log::DEBUG, "%s", timerString);
1525
1526 #endif
1527
1528   cTimer *timer = new cTimer;
1529   if (timer->Parse((char*)timerString))
1530   {
1531     cTimer *t = Timers.GetTimer(timer);
1532     if (!t)
1533     {
1534       Timers.Add(timer);
1535 #if VDRVERSNUM < 10300
1536       Timers.Save();
1537 #else
1538       Timers.SetModified();
1539 #endif
1540       sendULONG(0);
1541       return 1;
1542     }
1543     else
1544     {
1545       sendULONG(1);
1546     }
1547   }
1548   else
1549   {
1550      sendULONG(2);
1551   }
1552   delete timer;
1553   return 1;
1554 }