]> git.vomp.tv Git - vompserver.git/blob - mvpclient.c
New logging system, bug fixed where client could gazump recordings or
[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 MVPClient::MVPClient(int tsocket)
24  : tcp(tsocket)
25 {
26   cm = NULL;
27   rp = NULL;
28   recordingManager = NULL;
29   log = Log::getInstance();
30
31   // Get IP address of client for config module
32
33   char ipa[20];
34   struct sockaddr_in peer;
35   socklen_t salen = sizeof(struct sockaddr);
36   if(getpeername(tsocket, (struct sockaddr*)&peer, &salen) == 0)
37   {
38     strcpy(ipa, inet_ntoa(peer.sin_addr));
39   }
40   else
41   {
42     ipa[0] = '\0';
43     log->log("Client", Log::DEBUG, "Cannot get peer name!");
44   }
45
46   const char* configDir = cPlugin::ConfigDirectory();
47   if (!configDir)
48   {
49     log->log("Client", Log::DEBUG, "No config dir!");
50     return;
51   }
52
53   char configFileName[PATH_MAX];
54   snprintf(configFileName, PATH_MAX - strlen(configDir) - strlen(ipa) - 20, "%s/vomp-%s.conf", configDir, ipa);
55   config.init(configFileName);
56
57   log->log("Client", Log::DEBUG, "Config file name: %s", configFileName);
58
59 //  processGetChannelSchedule(NULL, 0);
60
61 //test();
62
63 }
64
65 MVPClient::~MVPClient()
66 {
67   log->log("Client", Log::DEBUG, "MVP client destructor");
68   if (cm)
69   {
70     cm->Stop();
71     delete cm;
72     cm = NULL;
73   }
74   else if (rp)
75   {
76     writeResumeData();
77
78     delete rp;
79     delete recordingManager;
80     rp = NULL;
81     recordingManager = NULL;
82   }
83
84   cleanConfig();
85 }
86
87 cChannel* MVPClient::channelFromNumber(unsigned long channelNumber)
88 {
89   cChannel* channel = NULL;
90
91   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
92   {
93     if (!channel->GroupSep())
94     {
95       log->log("Client", Log::DEBUG, "Looking for channel %lu::: number: %i name: '%s'", channelNumber, channel->Number(), channel->Name());
96
97       if (channel->Number() == (int)channelNumber)
98       {
99         int vpid = channel->Vpid();
100 #if VDRVERSNUM < 10300
101         int apid1 = channel->Apid1();
102 #else
103         int apid1 = channel->Apid(0);
104 #endif
105         log->log("Client", Log::DEBUG, "Found channel number %lu, vpid = %i, apid1 = %i", channelNumber, vpid, apid1);
106         return channel;
107       }
108     }
109   }
110
111   if (!channel)
112   {
113     log->log("Client", Log::DEBUG, "Channel not found");
114   }
115
116   return channel;
117 }
118
119
120 void MVPClient::writeResumeData()
121 {
122   config.setValueLongLong("ResumeData", (char*)rp->getCurrentRecording()->FileName(), rp->getLastPosition());
123 }
124
125 void MVPClient::sendULONG(ULONG ul)
126 {
127   unsigned char sendBuffer[8];
128   *(unsigned long*)&sendBuffer[0] = htonl(4);
129   *(unsigned long*)&sendBuffer[4] = htonl(ul);
130
131   tcp.sendPacket(sendBuffer, 8);
132   log->log("Client", Log::DEBUG, "written ULONG %lu", ul);
133 }
134
135 void MVPClientStartThread(void* arg)
136 {
137   MVPClient* m = (MVPClient*)arg;
138   m->run2();
139   // Nothing external to this class has a reference to it
140   // This is the end of the thread.. so delete m
141   delete m;
142   pthread_exit(NULL);
143 }
144
145 int MVPClient::run()
146 {
147   if (pthread_create(&runThread, NULL, (void*(*)(void*))MVPClientStartThread, (void *)this) == -1) return 0;
148   log->log("Client", Log::DEBUG, "MVPClient run success");
149   return 1;
150 }
151
152 void MVPClient::run2()
153 {
154   // Thread stuff
155   sigset_t sigset;
156   sigfillset(&sigset);
157   pthread_sigmask(SIG_BLOCK, &sigset, NULL);
158   pthread_detach(runThread);  // Detach
159
160   tcp.disableReadTimeout();
161
162   tcp.setSoKeepTime(3);
163   tcp.setNonBlocking();
164
165   unsigned char* buffer;
166   unsigned char* data;
167   int packetLength;
168   unsigned long opcode;
169
170   while(1)
171   {
172     log->log("Client", Log::DEBUG, "Waiting");
173     buffer = (unsigned char*)tcp.receivePacket();
174     log->log("Client", Log::DEBUG, "Received packet, length = %u", tcp.getDataLength());
175     if (buffer == NULL)
176     {
177       log->log("Client", Log::DEBUG, "Detected connection closed");
178       break;
179     }
180
181     packetLength = tcp.getDataLength() - 4;
182     opcode = ntohl(*(unsigned long*)buffer);
183     data = buffer + 4;
184
185
186     switch(opcode)
187     {
188       case 1:
189         processLogin(data, packetLength);
190         break;
191       case 2:
192         processGetRecordingsList(data, packetLength);
193         break;
194       case 3:
195         processDeleteRecording(data, packetLength);
196         break;
197       case 4:
198         processGetSummary(data, packetLength);
199         break;
200       case 5:
201         processGetChannelsList(data, packetLength);
202         break;
203       case 6:
204         processStartStreamingChannel(data, packetLength);
205         break;
206       case 7:
207         processGetBlock(data, packetLength);
208         break;
209       case 8:
210         processStopStreaming(data, packetLength);
211         break;
212       case 9:
213         processStartStreamingRecording(data, packetLength);
214         break;
215       case 10:
216         processGetChannelSchedule(data, packetLength);
217         break;
218       case 11:
219         processConfigSave(data, packetLength);
220         break;
221       case 12:
222         processConfigLoad(data, packetLength);
223         break;
224     }
225
226     free(buffer);
227   }
228 }
229
230 void MVPClient::processLogin(unsigned char* buffer, int length)
231 {
232   time_t timeNow = time(NULL);
233   struct tm* timeStruct = localtime(&timeNow);
234   int timeOffset = timeStruct->tm_gmtoff;
235
236   unsigned char sendBuffer[12];
237   *(unsigned long*)&sendBuffer[0] = htonl(8);
238   *(unsigned long*)&sendBuffer[4] = htonl(timeNow);
239   *(signed int*)&sendBuffer[8] = htonl(timeOffset);
240
241   tcp.sendPacket(sendBuffer, 12);
242   log->log("Client", Log::DEBUG, "written login reply");
243 }
244
245 void MVPClient::processGetRecordingsList(unsigned char* data, int length)
246 {
247   unsigned char* sendBuffer = new unsigned char[50000]; // hope this is enough
248   int count = 4; // leave space for the packet length
249   char* point;
250
251
252   int FreeMB;
253   int Percent = VideoDiskSpace(&FreeMB);
254   int Total = (FreeMB / (100 - Percent)) * 100;
255
256   *(unsigned long*)&sendBuffer[count] = htonl(Total);
257   count += sizeof(unsigned long);
258   *(unsigned long*)&sendBuffer[count] = htonl(FreeMB);
259   count += sizeof(unsigned long);
260   *(unsigned long*)&sendBuffer[count] = htonl(Percent);
261   count += sizeof(unsigned long);
262
263
264   cRecordings Recordings;
265   Recordings.Load();
266
267   for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording))
268   {
269     if (count > 49000) break; // just how big is that hard disk?!
270     *(unsigned long*)&sendBuffer[count] = htonl(recording->start);// + timeOffset);
271     count += 4;
272
273     point = (char*)recording->Name();
274     strcpy((char*)&sendBuffer[count], point);
275     count += strlen(point) + 1;
276
277     point = (char*)recording->FileName();
278     strcpy((char*)&sendBuffer[count], point);
279     count += strlen(point) + 1;
280   }
281
282   *(unsigned long*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
283
284   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(unsigned long*)&sendBuffer[0]));
285
286   tcp.sendPacket(sendBuffer, count);
287   delete[] sendBuffer;
288   log->log("Client", Log::DEBUG, "Written list");
289 }
290
291 void MVPClient::processDeleteRecording(unsigned char* data, int length)
292 {
293   // data is a pointer to the fileName string
294
295   cRecordings Recordings;
296   Recordings.Load(); // probably have to do this
297
298   cRecording* recording = Recordings.GetByName((char*)data);
299
300   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
301
302   if (recording)
303   {
304     log->log("Client", Log::DEBUG, "deleting recording: %s", recording->Name());
305     recording->Delete();
306     sendULONG(1);
307   }
308   else
309   {
310     sendULONG(0);
311   }
312 }
313
314 void MVPClient::processGetSummary(unsigned char* data, int length)
315 {
316   // data is a pointer to the fileName string
317
318   cRecordings Recordings;
319   Recordings.Load(); // probably have to do this
320
321   cRecording *recording = Recordings.GetByName((char*)data);
322
323   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
324
325   if (recording)
326   {
327     unsigned char* sendBuffer = new unsigned char[50000]; // hope this is enough
328     int count = 4; // leave space for the packet length
329
330     char* point;
331 #if VDRVERSNUM < 10300
332     point = (char*)recording->Summary();
333 #else
334     const cRecordingInfo *Info = recording->Info();
335     point = (char*)Info->ShortText();
336     log->log("Client", Log::DEBUG, "info pointer %p summary pointer %p", Info, point);
337     if (isempty(point))
338     {
339       point = (char*)Info->Description();
340       log->log("Client", Log::DEBUG, "description pointer %p", point);
341     }
342 #endif
343     strcpy((char*)&sendBuffer[count], point);
344     count += strlen(point) + 1;
345     *(unsigned long*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
346
347     log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(unsigned long*)&sendBuffer[0]));
348
349     tcp.sendPacket(sendBuffer, count);
350     delete[] sendBuffer;
351     log->log("Client", Log::DEBUG, "Written summary");
352
353
354   }
355   else
356   {
357     sendULONG(0);
358   }
359 }
360
361 void MVPClient::processGetChannelsList(unsigned char* data, int length)
362 {
363   unsigned char* sendBuffer = new unsigned char[50000]; // FIXME hope this is enough
364   int count = 4; // leave space for the packet length
365   char* point;
366   unsigned long type;
367
368   for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
369   {
370 #if VDRVERSNUM < 10300
371     if (!channel->GroupSep() && !channel->Ca())
372 #else
373     if (!channel->GroupSep() && !channel->Ca(0))
374 #endif
375     {
376       log->log("Client", Log::DEBUG, "name: '%s'", channel->Name());
377
378       if (channel->Vpid()) type = 1;
379 #if VDRVERSNUM < 10300
380       else type = 2;
381 #else
382       else if (channel->Apid(0)) type = 2;
383       else continue;
384 #endif
385
386       if (count > 49000) break;
387       *(unsigned long*)&sendBuffer[count] = htonl(channel->Number());
388       count += 4;
389
390       *(unsigned long*)&sendBuffer[count] = htonl(type);
391       count += 4;
392
393       point = (char*)channel->Name();
394       strcpy((char*)&sendBuffer[count], point);
395       count += strlen(point) + 1;
396     }
397   }
398
399   *(unsigned long*)&sendBuffer[0] = htonl(count - 4); // -4 :  take off the size field
400
401   log->log("Client", Log::DEBUG, "recorded size as %u", ntohl(*(unsigned long*)&sendBuffer[0]));
402
403   tcp.sendPacket(sendBuffer, count);
404   delete[] sendBuffer;
405   log->log("Client", Log::DEBUG, "Written channels list");
406 }
407
408 void MVPClient::processStartStreamingChannel(unsigned char* data, int length)
409 {
410   log->log("Client", Log::DEBUG, "length = %i", length);
411   unsigned long channelNumber = ntohl(*(unsigned long*)data);
412
413   cChannel* channel = channelFromNumber(channelNumber);
414   if (!channel)
415   {
416     sendULONG(0);
417     return;
418   }
419
420
421 //  static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL);
422          ///< Returns a device that is able to receive the given Channel at the
423          ///< given Priority.
424          ///< See ProvidesChannel() for more information on how
425          ///< priorities are handled, and the meaning of NeedsDetachReceivers.
426
427   bool NeedsDetachReceivers;
428   cDevice* device = cDevice::GetDevice(channel, 0, &NeedsDetachReceivers);
429   if (!device)
430   {
431     log->log("Client", Log::DEBUG, "No device found to receive this channel at this priority");
432     sendULONG(0);
433   }
434   else if (NeedsDetachReceivers)
435   {
436     // can't really happen since we stream with priority zero. if a rec has pri zero maybe
437     log->log("Client", Log::DEBUG, "Needs detach receivers");
438     sendULONG(0);
439   }
440   else
441   {
442     cm = new cMediamvpTransceiver(channel, 0, 0, device);
443     device->AttachReceiver(cm);
444     sendULONG(1);
445   }
446
447
448 //////  MVPReceiver* m = new MVPReceiver(channel->Vpid(), channel->Apid1());
449 //  cm = new cMediamvpTransceiver(channel, 0, 0, cDevice::ActualDevice());
450 //  cDevice::ActualDevice()->AttachReceiver(cm);
451 ////  //cDevice::ActualDevice()->SwitchChannel(channel, false);
452
453 //  sendULONG(1);
454 }
455
456 void MVPClient::processStopStreaming(unsigned char* data, int length)
457 {
458   log->log("Client", Log::DEBUG, "STOP STREAMING RECEIVED");
459   if (cm)
460   {
461     delete cm;
462     cm = NULL;
463   }
464   else if (rp)
465   {
466     writeResumeData();
467
468     delete rp;
469     delete recordingManager;
470     rp = NULL;
471     recordingManager = NULL;
472   }
473
474   sendULONG(1);
475 }
476
477 void MVPClient::processGetBlock(unsigned char* data, int length)
478 {
479   if (!cm && !rp)
480   {
481     log->log("Client", Log::DEBUG, "Get block called when no streaming happening!");
482     return;
483   }
484
485   ULLONG position = ntohll(*(ULLONG*)data);
486   data += sizeof(ULLONG);
487   unsigned long amount = ntohl(*(unsigned long*)data);
488
489   log->log("Client", Log::DEBUG, "getblock pos = %llu length = %lu", position, amount);
490
491   unsigned char sendBuffer[amount + 4];
492   unsigned long amountReceived = 0; // compiler moan.
493   if (cm)
494   {
495     log->log("Client", Log::DEBUG, "getting from live");
496     amountReceived = cm->getBlock(&sendBuffer[4], amount);
497   }
498   else if (rp)
499   {
500     log->log("Client", Log::DEBUG, "getting from recording");
501     amountReceived = rp->getBlock(&sendBuffer[4], position, amount);
502   }
503
504   *(unsigned long*)&sendBuffer[0] = htonl(amountReceived);
505   tcp.sendPacket(sendBuffer, amountReceived + 4);
506   log->log("Client", Log::DEBUG, "written ok %lu", amountReceived);
507 }
508
509 void MVPClient::processStartStreamingRecording(unsigned char* data, int length)
510 {
511   // data is a pointer to the fileName string
512
513   recordingManager = new cRecordings;
514   recordingManager->Load();
515
516   cRecording* recording = recordingManager->GetByName((char*)data);
517
518   log->log("Client", Log::DEBUG, "recording pointer %p", recording);
519
520   if (recording)
521   {
522     rp = new RecPlayer(recording);
523
524     unsigned char sendBuffer[12];
525     *(unsigned long*)&sendBuffer[0] = htonl(8);
526     *(ULLONG*)&sendBuffer[4] = htonll(rp->getTotalLength());
527
528     tcp.sendPacket(sendBuffer, 12);
529     log->log("Client", Log::DEBUG, "written totalLength");
530   }
531   else
532   {
533     delete recordingManager;
534     recordingManager = NULL;
535   }
536 }
537
538 void MVPClient::processGetChannelSchedule(unsigned char* data, int length)
539 {
540   ULONG channelNumber = ntohl(*(ULLONG*)data);
541   log->log("Client", Log::DEBUG, "get schedule called for channel %lu", channelNumber);
542
543   cChannel* channel = channelFromNumber(channelNumber);
544   if (!channel)
545   {
546     unsigned char sendBuffer[4];
547     *(unsigned long*)&sendBuffer[0] = htonl(0);
548     tcp.sendPacket(sendBuffer, 4);
549     log->log("Client", Log::DEBUG, "written null");
550     return;
551   }
552
553 #if VDRVERSNUM < 10300
554   cMutexLock MutexLock;
555   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
556 #else
557   cSchedulesLock MutexLock;
558   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
559 #endif
560   if (!Schedules)
561   {
562     unsigned char sendBuffer[8];
563     *(unsigned long*)&sendBuffer[0] = htonl(4);
564     *(unsigned long*)&sendBuffer[4] = htonl(0);
565     tcp.sendPacket(sendBuffer, 8);
566     log->log("Client", Log::DEBUG, "written 0");
567     return;
568   }
569
570   unsigned char sendBuffer[8];
571   *(unsigned long*)&sendBuffer[0] = htonl(4);
572   *(unsigned long*)&sendBuffer[4] = htonl(1);
573   tcp.sendPacket(sendBuffer, 8);
574   log->log("Client", Log::DEBUG, "written 1");
575
576
577 }
578
579 void MVPClient::testChannelSchedule(unsigned char* data, int length)
580 {
581   FILE* f = fopen("/tmp/s.txt", "w");
582
583 #if VDRVERSNUM < 10300
584   cMutexLock MutexLock;
585   const cSchedules *Schedules = cSIProcessor::Schedules(MutexLock);
586 #else
587   cSchedulesLock MutexLock;
588   const cSchedules *Schedules = cSchedules::Schedules(MutexLock);
589 #endif
590   if (!Schedules)
591   {
592     fprintf(f, "Schedules = NULL\n");
593     fclose(f);
594     return;
595   }
596
597   fprintf(f, "Schedules dump:\n");
598   Schedules->Dump(f);
599
600
601   const cSchedule *Schedule;
602   int scheduleNumber = 0;
603
604   tChannelID tchid;
605   cChannel *thisChannel;
606
607 #if VDRVERSNUM < 10300
608   const cEventInfo *event;
609   int eventNumber = 0;
610 #else
611   const cEvent *event;
612 #endif
613
614 //    Schedule = Schedules->GetSchedule(channel->GetChannelID());
615 //    Schedule = Schedules->GetSchedule();
616   Schedule = Schedules->First();
617   if (!Schedule)
618   {
619     fprintf(f, "First Schedule = NULL\n");
620     fclose(f);
621     return;
622   }
623
624   while (Schedule)
625   {
626     fprintf(f, "Schedule #%i\n", scheduleNumber);
627     fprintf(f, "-------------\n\n");
628
629 #if VDRVERSNUM < 10300
630     tchid = Schedule->GetChannelID();
631 #else
632     tchid = Schedule->ChannelID();
633 #endif
634 #if VDRVERSNUM < 10300
635     fprintf(f, "ChannelID.ToString() = %s\n", tchid.ToString());
636     fprintf(f, "NumEvents() = %i\n", Schedule->NumEvents());
637 #else
638 //  put the count at the end.
639 #endif
640     thisChannel = Channels.GetByChannelID(tchid, true);
641     if (thisChannel)
642     {
643       fprintf(f, "Channel Number: %p %i\n", thisChannel, thisChannel->Number());
644     }
645     else
646     {
647       fprintf(f, "thisChannel = NULL for tchid\n");
648     }
649
650 #if VDRVERSNUM < 10300
651     for (eventNumber = 0; eventNumber < Schedule->NumEvents(); eventNumber++)
652     {
653       event = Schedule->GetEventNumber(eventNumber);
654       fprintf(f, "Event %i tableid = %i timestring = %s endtimestring = %s\n", eventNumber, event->GetTableID(), event->GetTimeString(), event->GetEndTimeString());
655       fprintf(f, "Event %i date = %s isfollowing = %i ispresent = %i\n", eventNumber, event->GetDate(), event->IsFollowing(), event->IsPresent());
656       fprintf(f, "Event %i extendeddescription = %s\n", eventNumber, event->GetExtendedDescription());
657       fprintf(f, "Event %i subtitle = %s title = %s\n", eventNumber, event->GetSubtitle(), event->GetTitle());
658       fprintf(f, "Event %i eventid = %u duration = %li time = %lu channelnumber = %i\n", eventNumber, event->GetEventID(), event->GetDuration(), event->GetTime(), event->GetChannelNumber());
659       fprintf(f, "Event %u dump:\n", eventNumber);
660       event->Dump(f);
661       fprintf(f, "\n\n");
662     }
663 #else
664 //  This whole section needs rewriting to walk the list.
665     event = Schedule->Events()->First();
666     while (event) {
667       event = Schedule->Events()->Next(event);
668     }
669 #endif
670
671
672     fprintf(f, "\nDump from object:\n");
673     Schedule->Dump(f);
674     fprintf(f, "\nEND\n");
675
676
677
678
679 /*
680   const cEventInfo *GetPresentEvent(void) const;
681   const cEventInfo *GetFollowingEvent(void) const;
682   const cEventInfo *GetEvent(unsigned short uEventID, time_t tTime = 0) const;
683   const cEventInfo *GetEventAround(time_t tTime) const;
684   const cEventInfo *GetEventNumber(int n) const { return Events.Get(n); }
685
686
687   const unsigned char GetTableID(void) const;
688   const char *GetTimeString(void) const;
689   const char *GetEndTimeString(void) const;
690   const char *GetDate(void) const;
691   bool IsFollowing(void) const;
692   bool IsPresent(void) const;
693   const char *GetExtendedDescription(void) const;
694   const char *GetSubtitle(void) const;
695   const char *GetTitle(void) const;
696   unsigned short GetEventID(void) const;
697   long GetDuration(void) const;
698   time_t GetTime(void) const;
699   tChannelID GetChannelID(void) const;
700   int GetChannelNumber(void) const { return nChannelNumber; }
701   void SetChannelNumber(int ChannelNumber) const { ((cEventInfo *)this)->nChannelNumber = ChannelNumber; } // doesn't modify the EIT data, so it's ok to make it 'const'
702   void Dump(FILE *f, const char *Prefix = "") const;
703
704 */
705
706
707
708
709
710     fprintf(f, "End of current Schedule\n\n\n");
711
712     Schedule = (const cSchedule *)Schedules->Next(Schedule);
713     scheduleNumber++;
714   }
715
716   fclose(f);
717 }
718
719 void MVPClient::processConfigSave(unsigned char* buffer, int length)
720 {
721   char* section = (char*)buffer;
722   char* key = NULL;
723   char* value = NULL;
724
725   for (int k = 0; k < length; k++)
726   {
727     if (buffer[k] == '\0')
728     {
729       if (!key)
730       {
731         key = (char*)&buffer[k+1];
732       }
733       else
734       {
735         value = (char*)&buffer[k+1];
736         break;
737       }
738     }
739   }
740
741   // if the last string (value) doesnt have null terminator, give up
742   if (buffer[length - 1] != '\0') return;
743
744   log->log("Client", Log::DEBUG, "Config save: %s %s %s", section, key, value);
745   if (config.setValueString(section, key, value))
746   {
747     sendULONG(1);
748   }
749   else
750   {
751     sendULONG(0);
752   }
753 }
754
755 void MVPClient::processConfigLoad(unsigned char* buffer, int length)
756 {
757   char* section = (char*)buffer;
758   char* key = NULL;
759
760   for (int k = 0; k < length; k++)
761   {
762     if (buffer[k] == '\0')
763     {
764       key = (char*)&buffer[k+1];
765       break;
766     }
767   }
768
769   char* value = config.getValueString(section, key);
770
771   if (value)
772   {
773     unsigned char sendBuffer[4 + strlen(value) + 1];
774     *(unsigned long*)&sendBuffer[0] = htonl(strlen(value) + 1);
775     strcpy((char*)&sendBuffer[4], value);
776     tcp.sendPacket(sendBuffer, 4 + strlen(value) + 1);
777
778     log->log("Client", Log::DEBUG, "Written config load packet");
779     delete[] value;
780   }
781   else
782   {
783     unsigned char sendBuffer[8];
784     *(unsigned long*)&sendBuffer[0] = htonl(4);
785     *(unsigned long*)&sendBuffer[4] = htonl(0);
786     tcp.sendPacket(sendBuffer, 8);
787
788     log->log("Client", Log::DEBUG, "Written config load failed packet");
789   }
790 }
791
792 void MVPClient::cleanConfig()
793 {
794   log->log("Client", Log::DEBUG, "Clean config");
795
796   cRecordings Recordings;
797   Recordings.Load();
798
799   int numReturns;
800   int length;
801   char* resumes = config.getSectionKeyNames("ResumeData", numReturns, length);
802   char* position = resumes;
803   for(int k = 0; k < numReturns; k++)
804   {
805     log->log("Client", Log::DEBUG, "EXAMINING: %i %i %p %s", k, numReturns, position, position);
806
807     cRecording* recording = Recordings.GetByName(position);
808     if (!recording)
809     {
810       // doesn't exist anymore
811       log->log("Client", Log::DEBUG, "Found a recording that doesn't exist anymore");
812       config.deleteValue("ResumeData", position);
813     }
814     else
815     {
816       log->log("Client", Log::DEBUG, "This recording still exists");
817     }
818
819     position += strlen(position) + 1;
820   }
821
822   delete[] resumes;
823 }
824
825