]> git.vomp.tv Git - vompserver.git/blob - vompclient.c
New VOMP discovery protocol
[vompserver.git] / vompclient.c
1 /*
2     Copyright 2004-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21 #include "mediaplayer.h"
22 #include "media.h"
23 #include "servermediafile.h"
24 #include "vdrcommand.h"
25 #include "vompclient.h"
26
27 #include "responsepacket.h"
28
29 #ifndef VOMPSTANDALONE
30 #include <vdr/channels.h>
31 #include <vdr/recording.h>
32 #include "recplayer.h"
33 #include "mvpreceiver.h"
34 #endif
35
36
37
38 pthread_mutex_t threadClientMutex;
39 int VompClient::nr_clients = 0;
40
41
42 VompClient::VompClient(Config* cfgBase, char* tconfigDir, int tsocket)
43  : rrproc(*this), tcp(tsocket), i18n(tconfigDir)
44 {
45 #ifndef VOMPSTANDALONE
46   lp = NULL;
47   recplayer = NULL;
48   recordingManager = NULL;
49 #endif
50   log = Log::getInstance();
51   loggedIn = false;
52   configDir = tconfigDir;
53   log->log("Client", Log::DEBUG, "Config dir: %s", configDir);
54   baseConfig = cfgBase;
55   incClients();
56   media=new MediaPlayer();
57   mediaprovider=new ServerMediaFile(cfgBase,media);
58   netLogFile = NULL;
59   charcoding=1; //latin1 is default
60   charconvsys=NULL;
61   charconvutf8=NULL;
62   setCharset(charcoding);
63   
64   rrproc.init();
65 }
66
67 VompClient::~VompClient()
68 {
69   log->log("Client", Log::DEBUG, "Vomp client destructor");
70 #ifndef VOMPSTANDALONE  
71   if (lp)
72   {
73     lp->detachMVPReceiver();
74     delete lp;
75     lp = NULL;
76   }
77   else if (recplayer)
78   {
79     writeResumeData();
80
81     delete recplayer;
82     delete recordingManager;
83     recplayer = NULL;
84     recordingManager = NULL;
85   }
86 #endif
87   //if (loggedIn) cleanConfig();
88   decClients();
89   
90   delete media;
91   delete mediaprovider;
92
93   if (charconvsys) delete charconvsys;
94   if (charconvutf8) delete charconvutf8;
95
96   
97   if (netLogFile)
98   {
99     fclose(netLogFile);
100     netLogFile = NULL;
101   }
102 }
103
104 void VompClient::setCharset(int charset)
105 {
106    charcoding=charset;
107    cCharSetConv *oldcharconvsys=charconvsys;
108    cCharSetConv *oldcharconvutf8=charconvutf8;
109    switch (charcoding) {
110    case 2: //UTF-8
111    charconvsys=new cCharSetConv(NULL,"UTF-8");
112    charconvutf8=new cCharSetConv("UTF-8","UTF-8");
113    break;
114    case 1:
115    default://latin1
116    charconvsys=new cCharSetConv(NULL,"ISO-8859-1");
117    charconvutf8=new cCharSetConv("UTF-8","ISO-8859-1");
118    break;
119    };
120    if (oldcharconvsys) delete oldcharconvsys;
121    if (oldcharconvutf8) delete oldcharconvutf8;
122
123 }
124
125 void VompClient::incClients()
126 {
127   pthread_mutex_lock(&threadClientMutex);
128   VompClient::nr_clients++;
129   pthread_mutex_unlock(&threadClientMutex);
130 }
131
132 void VompClient::decClients()
133 {
134   pthread_mutex_lock(&threadClientMutex);
135   VompClient::nr_clients--;
136   pthread_mutex_unlock(&threadClientMutex);
137 }
138
139 int VompClient::getNrClients()
140 {
141   int nrClients;
142   pthread_mutex_lock(&threadClientMutex);
143   nrClients = VompClient::nr_clients;
144   pthread_mutex_unlock(&threadClientMutex);
145   return nrClients;
146 }
147
148 /*
149 void VompClient::cleanConfig()
150 {
151   log->log("Client", Log::DEBUG, "Clean config");
152
153 #ifndef VOMPSTANDALONE
154
155   cRecordings Recordings;
156   Recordings.Load();
157
158   int numReturns;
159   int length;
160   char* resumes = config.getSectionKeyNames("ResumeData", numReturns, length);
161   char* position = resumes;
162   for(int k = 0; k < numReturns; k++)
163   {
164     log->log("Client", Log::DEBUG, "EXAMINING: %i %i %p %s", k, numReturns, position, position);
165
166     cRecording* recording = Recordings.GetByName(position);
167     if (!recording)
168     {
169       // doesn't exist anymore
170       log->log("Client", Log::DEBUG, "Found a recording that doesn't exist anymore");
171       config.deleteValue("ResumeData", position);
172     }
173     else
174     {
175       log->log("Client", Log::DEBUG, "This recording still exists");
176     }
177
178     position += strlen(position) + 1;
179   }
180
181   delete[] resumes;
182 #endif
183 } */
184
185 void VompClient::netLog()
186 {
187   // Hook, called from rrproc login after client has logged in.
188   // See if this MVP config says to do network logging, if so open a log
189   // The config object will be usable now since it's set up in login
190   
191   char* doNetLogging = config.getValueString("Advanced", "Network logging");
192   if (doNetLogging)
193   {
194     if (!strcasecmp(doNetLogging, "on"))
195     {
196       char* netLogFileName = config.getValueString("Advanced", "Network logging file");
197       if (netLogFileName)
198       {
199         netLogFile = fopen(netLogFileName, "a");
200         if (netLogFile) log->log("Client", Log::DEBUG, "Client network logging started");
201
202         delete[] netLogFileName;
203       }
204     }
205     
206     delete[] doNetLogging;
207   }
208 }
209
210 void VompClientStartThread(void* arg)
211 {
212   VompClient* m = (VompClient*)arg;
213   m->run2();
214   // Nothing external to this class has a reference to it
215   // This is the end of the thread.. so delete m
216   delete m;
217   pthread_exit(NULL);
218 }
219
220 int VompClient::run()
221 {
222   if (pthread_create(&runThread, NULL, (void*(*)(void*))VompClientStartThread, (void *)this) == -1) return 0;
223   log->log("Client", Log::DEBUG, "VompClient run success");
224   return 1;
225 }
226
227 void VompClient::run2()
228 {
229   // Thread stuff
230   sigset_t sigset;
231   sigfillset(&sigset);
232   pthread_sigmask(SIG_BLOCK, &sigset, NULL);
233   pthread_detach(runThread);  // Detach
234
235 //  tcp.disableReadTimeout();
236
237 //  tcp.setSoKeepTime(3);
238   tcp.setNonBlocking();
239
240   ULONG channelID;
241   ULONG requestID;
242   ULONG opcode;
243   ULONG extraDataLength;
244   UCHAR* data;
245   
246   ULONG kaTimeStamp;
247   ULONG logStringLen;
248
249   while(1)
250   {
251     log->log("Client", Log::DEBUG, "Waiting");
252     
253     if (!tcp.readData((UCHAR*)&channelID, sizeof(ULONG)))
254     {
255       // If this read fails then the client just hasn't sent anything.
256       // If any of the lower reads fail, then break, the connection is probably dead
257
258       // check connection is ok
259 //      if (tcp.isConnected()) continue;
260       
261       log->log("Client", Log::DEBUG, "Disconnection detected");
262       break;
263     }     
264     
265     channelID = ntohl(channelID);
266     if (channelID == 1)
267     {
268       if (!tcp.readData((UCHAR*)&requestID, sizeof(ULONG))) break;
269       requestID = ntohl(requestID);
270
271       if (!tcp.readData((UCHAR*)&opcode, sizeof(ULONG))) break;
272       opcode = ntohl(opcode);
273
274       if (!tcp.readData((UCHAR*)&extraDataLength, sizeof(ULONG))) break;
275       extraDataLength = ntohl(extraDataLength);
276       if (extraDataLength > 200000) // a random sanity limit
277       {
278         log->log("Client", Log::ERR, "ExtraDataLength > 200000!");
279         break;
280       }
281
282       if (extraDataLength)
283       {
284         data = (UCHAR*)malloc(extraDataLength);
285         if (!data)
286         {
287           log->log("Client", Log::ERR, "Extra data buffer malloc error");
288           break;
289         }
290         
291         if (!tcp.readData(data, extraDataLength))
292         {
293           log->log("Client", Log::ERR, "Could not read extradata");
294           free(data);
295           break;
296         }      
297       }
298       else
299       {
300         data = NULL;
301       }
302
303       log->log("Client", Log::DEBUG, "Received chan=%lu, ser=%lu, op=%lu, edl=%lu", channelID, requestID, opcode, extraDataLength);
304
305       if (!loggedIn && (opcode != 1))
306       {
307         log->log("Client", Log::ERR, "Not logged in and opcode != 1");
308         if (data) free(data);
309         break;
310       }
311
312       RequestPacket* req = new RequestPacket(requestID, opcode, data, extraDataLength);
313       rrproc.recvRequest(req);
314     }
315     else if (channelID == 3)
316     {
317       if (!tcp.readData((UCHAR*)&kaTimeStamp, sizeof(ULONG))) break;
318       kaTimeStamp = ntohl(kaTimeStamp);
319
320       log->log("Client", Log::DEBUG, "Received chan=%lu kats=%lu", channelID, kaTimeStamp);    
321
322       ULONG* p;
323       UCHAR buffer[8];
324       p = (ULONG*)&buffer[0]; *p = htonl(3); // KA CHANNEL
325       p = (ULONG*)&buffer[4]; *p = htonl(kaTimeStamp);
326       if (!tcp.sendPacket(buffer, 8))
327       {
328         log->log("Client", Log::ERR, "Could not send back KA reply");
329         break;
330       }      
331     }
332     else if (channelID == 4)
333     {
334       if (!tcp.readData((UCHAR*)&logStringLen, sizeof(ULONG))) break;
335       logStringLen = ntohl(logStringLen);
336
337       log->log("Client", Log::DEBUG, "Received chan=%lu loglen=%lu", channelID, logStringLen);    
338
339       UCHAR buffer[logStringLen + 1];
340       if (!tcp.readData((UCHAR*)&buffer, logStringLen)) break;
341       buffer[logStringLen] = '\0';
342
343 //      log->log("Client", Log::INFO, "Client said: '%s'", buffer);
344       if (netLogFile)
345       {
346         if (fputs((const char*)buffer, netLogFile) == EOF)
347         {
348           fclose(netLogFile);
349           netLogFile = NULL;
350         }
351         fflush(NULL);
352       }
353     }    
354     else
355     {
356       log->log("Client", Log::ERR, "Incoming channel number unknown");
357       break;
358     }
359   }
360 }
361
362 ULLONG VompClient::ntohll(ULLONG a)
363 {
364   return htonll(a);
365 }
366
367 ULLONG VompClient::htonll(ULLONG a)
368 {
369   #if BYTE_ORDER == BIG_ENDIAN
370     return a;
371   #else
372     ULLONG b = 0;
373
374     b = ((a << 56) & 0xFF00000000000000ULL)
375       | ((a << 40) & 0x00FF000000000000ULL)
376       | ((a << 24) & 0x0000FF0000000000ULL)
377       | ((a <<  8) & 0x000000FF00000000ULL)
378       | ((a >>  8) & 0x00000000FF000000ULL)
379       | ((a >> 24) & 0x0000000000FF0000ULL)
380       | ((a >> 40) & 0x000000000000FF00ULL)
381       | ((a >> 56) & 0x00000000000000FFULL) ;
382
383     return b;
384   #endif
385 }
386
387 #ifndef VOMPSTANDALONE
388
389 cChannel* VompClient::channelFromNumber(ULONG channelNumber)
390 {
391   cChannel* channel = NULL;
392
393   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
394   {
395     if (!channel->GroupSep())
396     {
397       log->log("Client", Log::DEBUG, "Looking for channel %lu::: number: %i name: '%s'", channelNumber, channel->Number(), channel->Name());
398
399       if (channel->Number() == (int)channelNumber)
400       {
401         int vpid = channel->Vpid();
402 #if VDRVERSNUM < 10300
403         int apid1 = channel->Apid1();
404 #else
405         int apid1 = channel->Apid(0);
406 #endif
407         log->log("Client", Log::DEBUG, "Found channel number %lu, vpid = %i, apid1 = %i", channelNumber, vpid, apid1);
408         return channel;
409       }
410     }
411   }
412
413   if (!channel)
414   {
415     log->log("Client", Log::DEBUG, "Channel not found");
416   }
417
418   return channel;
419 }
420
421 void VompClient::writeResumeData()
422 {
423   /*config.setValueLong("ResumeData",
424                           (char*)recplayer->getCurrentRecording()->FileName(),
425                           recplayer->frameNumberFromPosition(recplayer->getLastPosition()) );*/
426
427   /* write to vdr resume file */
428   int resume = recplayer->frameNumberFromPosition(recplayer->getLastPosition());
429   char* ResumeIdC = config.getValueString("General", "ResumeId");
430   int ResumeId;
431   if (ResumeIdC)
432     ResumeId = atoi(ResumeIdC);
433   else
434     ResumeId = 0;
435
436   while (ResumeIDLock)
437           cCondWait::SleepMs(100);
438   ResumeIDLock = true;
439   int OldSetupResumeID = Setup.ResumeID;
440   Setup.ResumeID = ResumeId;                            //UGLY: quickly change resumeid
441 #if VDRVERSNUM < 10703
442   cResumeFile ResumeFile((char*)recplayer->getCurrentRecording()->FileName());  //get corresponding resume file
443 #else
444   cResumeFile ResumeFile((char*)recplayer->getCurrentRecording()->FileName(),(char*)recplayer->getCurrentRecording()->IsPesRecording());        //get corresponding resume file
445 #endif
446   Setup.ResumeID = OldSetupResumeID;                    //and restore it back
447   ResumeIDLock = false;
448   ResumeFile.Save(resume);
449   //isyslog("VOMPDEBUG: Saving resume = %i, ResumeId = %i",resume, ResumeId);
450 }
451
452 #endif
453