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