network.c
author nathan
Sat, 29 Dec 2007 14:49:09 +0100
branchtrunk
changeset 2 4c1f7b705009
parent 0 474a1293c3c0
child 22 93aaf15c145a
permissions -rw-r--r--
release 0.10.1
nathan@0
     1
/*
nathan@0
     2
 * MP3/MPlayer plugin to VDR (C++)
nathan@0
     3
 *
nathan@0
     4
 * (C) 2001-2006 Stefan Huelswitt <s.huelswitt@gmx.de>
nathan@0
     5
 *
nathan@0
     6
 * This code is free software; you can redistribute it and/or
nathan@0
     7
 * modify it under the terms of the GNU General Public License
nathan@0
     8
 * as published by the Free Software Foundation; either version 2
nathan@0
     9
 * of the License, or (at your option) any later version.
nathan@0
    10
 *
nathan@0
    11
 * This code is distributed in the hope that it will be useful,
nathan@0
    12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
nathan@0
    13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
nathan@0
    14
 * GNU General Public License for more details.
nathan@0
    15
 *
nathan@0
    16
 * You should have received a copy of the GNU General Public License
nathan@0
    17
 * along with this program; if not, write to the Free Software
nathan@0
    18
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
nathan@0
    19
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
nathan@0
    20
 */
nathan@0
    21
nathan@0
    22
#include <ctype.h>
nathan@0
    23
#include <stdlib.h>
nathan@0
    24
#include <stdio.h>
nathan@0
    25
#include <stdarg.h>
nathan@0
    26
#include <sys/types.h>
nathan@0
    27
#include <unistd.h>
nathan@0
    28
#include <string.h>
nathan@0
    29
nathan@0
    30
#include <fcntl.h>
nathan@0
    31
#include <sys/socket.h>
nathan@0
    32
#include <netinet/in.h>
nathan@0
    33
#include <arpa/inet.h>
nathan@0
    34
#include <netdb.h>
nathan@0
    35
nathan@0
    36
#include <vdr/tools.h>
nathan@0
    37
nathan@0
    38
#include "common.h"
nathan@0
    39
#include "setup-mp3.h"
nathan@0
    40
#include "network.h"
nathan@0
    41
nathan@0
    42
#define CON_TIMEOUT      30*1000   // default timeout (ms) for connect operation
nathan@0
    43
#define RW_TIMEOUT       30*1000   // default timeout (ms) for read/write operations
nathan@0
    44
#define BUFFERSIZE       128*1024  // default ringbuffer size (bytes) for async read
nathan@0
    45
nathan@0
    46
#define NETDOWN_TIMEOUT  30        // timeout (s) for shutting down network
nathan@0
    47
nathan@0
    48
const char *netscript=0;
nathan@0
    49
nathan@0
    50
#if APIVERSNUM == 10131
nathan@0
    51
#error Using this plugin with vdr 1.1.31 is not recommended (may cause high cpu load during streaming)
nathan@0
    52
#endif
nathan@0
    53
nathan@0
    54
// -----------------------------------------------------------------------------
nathan@0
    55
nathan@0
    56
int RunCommand(const char *cmd, const char *State, const char *Name=0)
nathan@0
    57
{
nathan@0
    58
  int res=-1;
nathan@0
    59
  if(cmd) {
nathan@0
    60
    char *tmp=0;
nathan@0
    61
    if(Name)
nathan@0
    62
#if APIVERSNUM < 10318
nathan@0
    63
      asprintf(&tmp,"%s %s \"%s\"",cmd,State,strescape(Name,"\"$"));
nathan@0
    64
#else
nathan@0
    65
      asprintf(&tmp,"%s %s \"%s\"",cmd,State,*strescape(Name,"\"$"));
nathan@0
    66
#endif
nathan@0
    67
    else asprintf(&tmp,"%s %s",cmd,State);
nathan@0
    68
nathan@0
    69
    d(printf("run: executing '%s'\n",tmp))
nathan@0
    70
    res=SystemExec(tmp);
nathan@0
    71
    free(tmp);
nathan@0
    72
    }
nathan@0
    73
  return res;
nathan@0
    74
}
nathan@0
    75
nathan@0
    76
// -- cNetScript ---------------------------------------------------------------
nathan@0
    77
nathan@0
    78
class cNetScript : public cThread {
nathan@0
    79
private:
nathan@0
    80
  int count;
nathan@0
    81
  bool pending;
nathan@0
    82
protected:
nathan@0
    83
  virtual void Action(void);
nathan@0
    84
public:
nathan@0
    85
  cNetScript(void);
nathan@0
    86
  ~cNetScript();
nathan@0
    87
  void Up(void);
nathan@0
    88
  void Down(void);
nathan@0
    89
  };
nathan@0
    90
nathan@0
    91
cNetScript ns;
nathan@0
    92
nathan@0
    93
cNetScript::cNetScript(void)
nathan@0
    94
{
nathan@0
    95
  count=0; pending=false;
nathan@0
    96
}
nathan@0
    97
nathan@0
    98
cNetScript::~cNetScript()
nathan@0
    99
{
nathan@0
   100
  if(pending) Cancel(0);
nathan@0
   101
}
nathan@0
   102
nathan@0
   103
void cNetScript::Up(void)
nathan@0
   104
{
nathan@0
   105
  Lock();
nathan@0
   106
  if(netscript) {
nathan@0
   107
    if(pending) { Cancel(0); pending=false; }
nathan@0
   108
    RunCommand(netscript,"up");
nathan@0
   109
    count++;
nathan@0
   110
    }
nathan@0
   111
  Unlock();
nathan@0
   112
}
nathan@0
   113
nathan@0
   114
void cNetScript::Down(void)
nathan@0
   115
{
nathan@0
   116
  Lock();
nathan@0
   117
  if(netscript) {
nathan@0
   118
    if(--count==0) { Start(); pending=true; }
nathan@0
   119
    }
nathan@0
   120
  Unlock();
nathan@0
   121
}
nathan@0
   122
nathan@0
   123
void cNetScript::Action(void)
nathan@0
   124
{
nathan@0
   125
  d(printf("net: netscript down delay\n"))
nathan@0
   126
  sleep(NETDOWN_TIMEOUT);
nathan@0
   127
  Lock();
nathan@0
   128
  RunCommand(netscript,"down");
nathan@0
   129
  Unlock();
nathan@0
   130
}
nathan@0
   131
nathan@0
   132
// -- cNetConnect --------------------------------------------------------------
nathan@0
   133
nathan@0
   134
class cNetConnect : public cThread {
nathan@0
   135
private:
nathan@0
   136
  int fd;
nathan@0
   137
  const char *hostname;
nathan@0
   138
  int port;
nathan@0
   139
  cMutex conMutex;
nathan@0
   140
  cCondVar conCond;
nathan@0
   141
  int result;
nathan@0
   142
protected:
nathan@0
   143
  virtual void Action(void);
nathan@0
   144
  void Done(int res);
nathan@0
   145
public:
nathan@0
   146
  cNetConnect(int Fd, const char *Hostname, int Port);
nathan@0
   147
  ~cNetConnect();
nathan@0
   148
  int Wait(int timeoutMs);
nathan@0
   149
  };
nathan@0
   150
nathan@0
   151
cNetConnect::cNetConnect(int Fd, const char *Hostname, int Port)
nathan@0
   152
{
nathan@0
   153
  fd=Fd;
nathan@0
   154
  hostname=Hostname;
nathan@0
   155
  port=Port;
nathan@0
   156
  result=0;
nathan@0
   157
  Start();
nathan@0
   158
}
nathan@0
   159
nathan@0
   160
cNetConnect::~cNetConnect()
nathan@0
   161
{
nathan@0
   162
  Cancel(1);
nathan@0
   163
}
nathan@0
   164
nathan@0
   165
int cNetConnect::Wait(int timeoutMs)
nathan@0
   166
{
nathan@0
   167
  conMutex.Lock();
nathan@0
   168
  if(!result) conCond.TimedWait(conMutex,timeoutMs);
nathan@0
   169
  conMutex.Unlock();
nathan@0
   170
  return result;
nathan@0
   171
}
nathan@0
   172
nathan@0
   173
void cNetConnect::Done(int res)
nathan@0
   174
{
nathan@0
   175
  conMutex.Lock();
nathan@0
   176
  result=res;
nathan@0
   177
  conCond.Broadcast();
nathan@0
   178
  conMutex.Unlock();
nathan@0
   179
}
nathan@0
   180
nathan@0
   181
void cNetConnect::Action(void)
nathan@0
   182
{
nathan@0
   183
  d(printf("net: name lookup %s\n",hostname))
nathan@0
   184
  struct hostent *hp=gethostbyname(hostname);
nathan@0
   185
  if(hp) {
nathan@0
   186
    struct sockaddr_in sin;
nathan@0
   187
    sin.sin_port=htons(port);
nathan@0
   188
    sin.sin_family=AF_INET;
nathan@0
   189
    memcpy((char *)&sin.sin_addr,hp->h_addr,hp->h_length);
nathan@0
   190
    d(printf("net: connecting to %s:%d\n",hostname,port))
nathan@0
   191
    if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))==0) {
nathan@0
   192
      d(printf("net: connected\n"))
nathan@0
   193
      Done(1);
nathan@0
   194
      }
nathan@0
   195
    else { esyslog("connect() failed: %s",strerror(errno)); Done(-1); }
nathan@0
   196
    }
nathan@0
   197
  else { esyslog("Unknown host '%s'",hostname); Done(-1); }
nathan@0
   198
}
nathan@0
   199
nathan@0
   200
// -- cNet ---------------------------------------------------------------------
nathan@0
   201
nathan@0
   202
cNet::cNet(int size, int ConTimeoutMs, int RwTimeoutMs)
nathan@0
   203
:cRingBufferLinear(size>0?size:BUFFERSIZE,1,false)
nathan@0
   204
{
nathan@0
   205
  fd=-1; deferedErrno=0; count=0;
nathan@0
   206
  connected=netup=false;
nathan@0
   207
  rwTimeout =RwTimeoutMs  ? RwTimeoutMs :RW_TIMEOUT;
nathan@0
   208
  conTimeout=ConTimeoutMs ? ConTimeoutMs:CON_TIMEOUT;
nathan@0
   209
#if APIVERSNUM >= 10132
nathan@0
   210
  SetTimeouts(50,50);
nathan@0
   211
#endif
nathan@0
   212
}
nathan@0
   213
nathan@0
   214
cNet::~cNet()
nathan@0
   215
{
nathan@0
   216
  Disconnect();
nathan@0
   217
}
nathan@0
   218
nathan@0
   219
void cNet::Close(void)
nathan@0
   220
{
nathan@0
   221
  if(connected) {
nathan@0
   222
    connected=false;
nathan@0
   223
    Cancel(2);
nathan@0
   224
    deferedErrno=0;
nathan@0
   225
    }
nathan@0
   226
  if(fd>=0) { close(fd); fd=-1; }
nathan@0
   227
  Clear(); count=0;
nathan@0
   228
}
nathan@0
   229
nathan@0
   230
void cNet::Disconnect(void)
nathan@0
   231
{
nathan@0
   232
  Close();
nathan@0
   233
  if(netup) { ns.Down(); netup=false; }
nathan@0
   234
}
nathan@0
   235
nathan@0
   236
bool cNet::Connect(const char *hostname, const int port)
nathan@0
   237
{
nathan@0
   238
  Close();
nathan@0
   239
  fd=socket(AF_INET,SOCK_STREAM,0);
nathan@0
   240
  if(fd>=0) {
nathan@0
   241
    ns.Up(); netup=true;
nathan@0
   242
    cNetConnect *con=new cNetConnect(fd,hostname,port);
nathan@0
   243
    int res=con->Wait(conTimeout);
nathan@0
   244
    delete con;
nathan@0
   245
    if(res>0) {
nathan@0
   246
      if(fcntl(fd,F_SETFL,O_NONBLOCK)>=0) {
nathan@0
   247
        deferedErrno=0; connected=true;
nathan@0
   248
        Start();
nathan@0
   249
        return(true);
nathan@0
   250
        }
nathan@0
   251
      else esyslog("fnctl() failed: %s",strerror(errno)); 
nathan@0
   252
      }
nathan@0
   253
    else if(res==0) esyslog("Connection timed out"); 
nathan@0
   254
    }
nathan@0
   255
  else esyslog("socket() failed: %s",strerror(errno)); 
nathan@0
   256
  Disconnect();
nathan@0
   257
  return false;
nathan@0
   258
}
nathan@0
   259
nathan@0
   260
void cNet::CopyFromBuff(unsigned char *dest, int n)
nathan@0
   261
{
nathan@0
   262
  memcpy(dest,lineBuff,n);
nathan@0
   263
  count-=n;
nathan@0
   264
  if(count>0) memmove(lineBuff,lineBuff+n,count);
nathan@0
   265
}
nathan@0
   266
nathan@0
   267
int cNet::Gets(char *dest, int len)
nathan@0
   268
{
nathan@0
   269
  len--; // let room for trailing zero
nathan@0
   270
  int c=0;
nathan@0
   271
  while(c<len) {
nathan@0
   272
    if(count<=0) {
nathan@0
   273
      int r=RingRead(lineBuff,sizeof(lineBuff));
nathan@0
   274
      if(r<0) {
nathan@0
   275
        if(c==0) return -1;
nathan@0
   276
        break;
nathan@0
   277
        }
nathan@0
   278
      count=r;
nathan@0
   279
      }
nathan@0
   280
    int n=0;
nathan@0
   281
    while(n<count && n+c<len) {
nathan@0
   282
      if(lineBuff[n]=='\n') len=0;
nathan@0
   283
      n++;
nathan@0
   284
      }
nathan@0
   285
    CopyFromBuff((unsigned char *)dest,n);
nathan@0
   286
    dest+=n; c+=n;
nathan@0
   287
    }
nathan@0
   288
  *dest=0;
nathan@0
   289
  return c;
nathan@0
   290
}
nathan@0
   291
nathan@0
   292
int cNet::Read(unsigned char *dest, int len)
nathan@0
   293
{
nathan@0
   294
  int c=0;
nathan@0
   295
  if(count>0) {
nathan@0
   296
    c=count; if(c>len) c=len;
nathan@0
   297
    CopyFromBuff(dest,c);
nathan@0
   298
    }
nathan@0
   299
  else {
nathan@0
   300
    c=RingRead(dest,len);
nathan@0
   301
    }
nathan@0
   302
  return c;
nathan@0
   303
}
nathan@0
   304
nathan@0
   305
int cNet::Write(unsigned char *dest, int len)
nathan@0
   306
{
nathan@0
   307
  int t=0, r;
nathan@0
   308
  cPoller poll(fd,true);
nathan@0
   309
  do {
nathan@0
   310
    if(poll.Poll(rwTimeout)) {
nathan@0
   311
      r=write(fd,dest,len);
nathan@0
   312
      if(r<0 && errno!=EAGAIN) {
nathan@0
   313
        esyslog("write() failed: %s",strerror(errno));
nathan@0
   314
        break;
nathan@0
   315
        }
nathan@0
   316
      dest+=r; len-=r; t+=r;
nathan@0
   317
      }
nathan@0
   318
    else { esyslog("Write timed out"); break; }
nathan@0
   319
    } while(len>0);
nathan@0
   320
  return t;
nathan@0
   321
}
nathan@0
   322
nathan@0
   323
int cNet::Puts(char *dest)
nathan@0
   324
{
nathan@0
   325
  return Write((unsigned char *)dest,strlen(dest));
nathan@0
   326
}
nathan@0
   327
nathan@0
   328
int cNet::RingRead(unsigned char *dest, int len)
nathan@0
   329
{
nathan@0
   330
  int r=0;
nathan@0
   331
  const uchar *rd;
nathan@0
   332
  for(;;) {
nathan@0
   333
    if(!Available() && deferedErrno) {
nathan@0
   334
      d(printf("net: ringbuffer empty, async read bailed out\n"))
nathan@0
   335
      return -1;
nathan@0
   336
      }
nathan@0
   337
    rd=Get(r);
nathan@0
   338
    if(rd && r>0) {
nathan@0
   339
      if(r>len) r=len;
nathan@0
   340
      memcpy(dest,rd,r);
nathan@0
   341
      Del(r);
nathan@0
   342
      return r;
nathan@0
   343
      }
nathan@0
   344
    }
nathan@0
   345
}
nathan@0
   346
nathan@0
   347
void cNet::Action(void)
nathan@0
   348
{
nathan@0
   349
  d(printf("net: async read started\n"))
nathan@0
   350
nathan@0
   351
  cPoller poll(fd,false);
nathan@0
   352
  while(connected) {
nathan@0
   353
    if(poll.Poll(rwTimeout)) {
nathan@0
   354
      unsigned char buff[8192];
nathan@0
   355
      int r=read(fd,buff,sizeof(buff));
nathan@0
   356
      if(r>0) {
nathan@0
   357
        int d=0;
nathan@0
   358
        do { d+=Put(buff+d,r-d); } while(d<r && connected);
nathan@0
   359
        }
nathan@0
   360
      else if(r<0 && errno!=EAGAIN) {
nathan@0
   361
        deferedErrno=errno;
nathan@0
   362
        esyslog("read() failed: %s",strerror(errno));
nathan@0
   363
        break;
nathan@0
   364
        }
nathan@0
   365
      else if(r==0) {
nathan@0
   366
        deferedErrno=-1;
nathan@0
   367
        d(printf("EOF from read()\n"))
nathan@0
   368
        break;
nathan@0
   369
        }
nathan@0
   370
      }
nathan@0
   371
    else {
nathan@0
   372
      deferedErrno=-1;
nathan@0
   373
      esyslog("Read timed out");
nathan@0
   374
      break;
nathan@0
   375
      }
nathan@0
   376
    }
nathan@0
   377
  EnableGet();
nathan@0
   378
  connected=false;
nathan@0
   379
}