Logo Search packages:      
Sourcecode: dcgui version File versions  Download package

bt2dc_gui2.c

/* dc_gui2 - a GTK+2 GUI for DCTC
 * Copyright (C) 2003 Eric Prevoteau
 *
 * bt2dc_gui2.c: Copyright (C) Eric Prevoteau <www@a2pb.gotdns.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
/*
$Id: bt2dc_gui2.c,v 1.1 2004/01/17 08:07:01 ericprev Exp $
*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <glib.h>

#ifdef WITH_PYTHON
#include <Python.h>

#include "bt2dc_gui2_io.h"
#include "misc.h"

GString *local_map_path=NULL;       /* $HOME/.dc_gui2/bt2dc_gui2/bt_xfer.%d (contains process description (mmap)) */
GString *lock_file_path=NULL;       /* $HOME/.dc_gui2/bt2dc_gui2/bt_xfer.%d.lock (if lockable, no process exists) */
int lock_fd=-1;
GString *done_file_path=NULL;       /* $HOME/.dc_gui2/bt2dc_gui2/bt_done */
BT_DL_STRUCT *local_dl=NULL;
char *download_directory=NULL;
char *done_directory=NULL;

#ifdef DEBUG_MODE
#define PRINT_MSG(x...)       printf(x)
#else
#define PRINT_MSG(x...)
#endif
/**********************************************************************/
/* check the access on the given directory (creating it if necessary) */
/**********************************************************************/
static void create_dir(const char *dir)
{
      struct stat st;

      if(stat(dir,&st))
      {
            if(mkdir(dir,0777))
            {
                  fprintf(stderr,"mkdir %s: %s\n",dir,strerror(errno));
                  exit(1);
            }
      }
      else
      {
            if(access(dir,R_OK|W_OK|X_OK))
            {
                  fprintf(stderr,"access to %s: %s\n",dir,strerror(errno));
                  exit(1);
            }
      }
}

/*********************************************/
/* create the unix socket for display client */
/*********************************************/
/* output: 0=ok, !=0=error */
/***************************/
static int init_status_file(const char *dl_dir,const char *url)
{
      char *path;
      int fd;
      BT_DL_STRUCT nw;
      
      local_map_path=g_string_new(NULL);
      path=getenv("HOME");
      g_string_sprintf(local_map_path,"%s/.dc_gui2",(path!=NULL)?path:".");

      create_dir(local_map_path->str);
      g_string_append(local_map_path,"/bt2dc_gui2");
      create_dir(local_map_path->str);

      /* create the .done file */
      done_file_path=g_string_new(local_map_path->str);
      g_string_append(done_file_path,"/bt.done");

      g_string_sprintfa(local_map_path,"/bt_xfer.%d",getpid());

      lock_file_path=g_string_new(local_map_path->str);
      g_string_append(lock_file_path,".lock");

      lock_fd=open(lock_file_path->str,O_RDWR|O_CREAT|O_TRUNC,0777);
      if(lock_fd==-1)
      {
            fprintf(stderr,"fail to create '%s': %s\n",lock_file_path->str,strerror(errno));
            return 1;
      }
      if(lockf(lock_fd,F_LOCK,1)!=0)
      {
            fprintf(stderr,"fail to lock '%s': %s\n",lock_file_path->str,strerror(errno));
            return 1;
      }

      fd=open(local_map_path->str,O_RDWR|O_CREAT|O_TRUNC,0777);
      if(fd==-1)
      {
            fprintf(stderr,"fail to create '%s': %s\n",local_map_path->str,strerror(errno));
            return 1;
      }

      nw.last_updated_time=0;
      nw.done=FALSE;
      memset(nw.file,'\0',sizeof(nw.file));
      nw.filesize=0;
      nw.percentDone=0;
      memset(nw.status,'\0',sizeof(nw.status));
      memset(nw.downloadTo,'\0',sizeof(nw.downloadTo));
      nw.downRate=0;
      nw.upRate=0;
      memset(nw.error_string,'\0',sizeof(nw.error_string));
      nw.client_is_running=TRUE;
      strncpy_max(nw.download_dir,dl_dir,sizeof(nw.download_dir));
      strncpy_max(nw.original_url,url,sizeof(nw.original_url));
      
      if(write(fd,&nw,sizeof(nw))!=sizeof(nw))
      {
            fprintf(stderr,"fail to write into '%s': %s\n",local_map_path->str,strerror(errno));
            close(fd);
            unlink(local_map_path->str);
            return 2;
      }

      local_dl=mmap(NULL,sizeof(BT_DL_STRUCT),PROT_READ|PROT_WRITE,MAP_SHARED, fd,0);
      if(local_dl==MAP_FAILED)
      {
            fprintf(stderr,"fail to map file '%s': %s\n",local_map_path->str,strerror(errno));
            close(fd);
            unlink(local_map_path->str);
            return 2;
      }
      close(fd);
      
      return 0;
}

/*********************************************/
/* add an error message to the status string */
/*********************************************/
static void add_error_message(const char *str)
{
      GString *gs;

      /* prepend the new error message to the error string. Messages are separated by '|' */
      gs=g_string_new(local_dl->error_string);
      g_string_prepend_c(gs,'|');
      g_string_prepend(gs,str);
      strncpy_max(local_dl->error_string,gs->str,sizeof(local_dl->error_string));
      g_string_free(gs,TRUE);
}

/**********************************************************************/
/* move the given fine to a different place (copying it if requiered) */
/**********************************************************************/
/* output: TRUE=success, FALSE=error */
/*************************************/
static gboolean move_file(const char *source_fpath, const char *dest_fpath)
{
      gboolean no_error=TRUE;

      if(rename(source_fpath,dest_fpath)!=0)
      {
            {
                  gchar *msg;

                  msg=g_strconcat("Fail to rename ",source_fpath," into ",dest_fpath,": ",strerror(errno),". Trying copy mode.",NULL);
                  add_error_message(msg);
                  g_free(msg);
            }

            {
                  int sfd,dfd;

                  dfd=creat(dest_fpath,0666);
                  if(dfd==-1)
                  {
                        fprintf(stderr,"Fail to create '%s': %s\n",dest_fpath,strerror(errno));
                        no_error=FALSE;
                  }
                  else
                  {
                        sfd=open(source_fpath,O_RDONLY);
                        if(sfd==-1)
                        {
                              fprintf(stderr,"Fail to open '%s': %s\n",source_fpath,strerror(errno));
                              no_error=FALSE;
                        }
                        else
                        {
                              while(1)
                              {
                                    char buf[8192];
                                    int ln,oln;

                                    ln=read(sfd,buf,sizeof(buf));
                                    if(ln<0)
                                    {
                                          fprintf(stderr,"Fail to read '%s': %s\n",source_fpath,strerror(errno));
                                          no_error=FALSE;
                                          break;
                                    }
                                    if(ln==0)
                                          break;            /* end of source file */
                                    oln=write(dfd,buf,ln);
                                    if(ln!=oln)
                                    {
                                          fprintf(stderr,"Fail to write '%s': %s\n",dest_fpath,strerror(errno));
                                          no_error=FALSE;
                                          break;
                                    }
                              }
                              
                              close(sfd);
                              if(no_error)                  /* on successful copy, we delete the original else we delete the copy */
                                    unlink(source_fpath);
                              else
                                    unlink(dest_fpath);
                        }
                        close(dfd);
                  }
            }
      }
      return no_error;
}

/*************************************************************************/
/* at end of download, move the downloaded file into the done/ directory */
/* and update the log file.                                              */
/*************************************************************************/
/* output: TRUE=success, FALSE=error */
/*************************************/
static gboolean move_file_to_done_dir(void)
{
      gchar *filename;
      gchar *dest_path;
      gboolean result;

      filename=g_path_get_basename(local_dl->file);
      
      /* update log file */
      {
            FILE *f;

            f=fopen(done_file_path->str,"ab");
            if(f!=NULL)
            {
                  fprintf(f,"(multi-source)|BitTorrent:%llu|%s|%llu\n",(unsigned long long)time(NULL),filename,local_dl->filesize);
                  fclose(f);
            }
      }

      dest_path=g_strconcat(done_directory,"/",filename,NULL);
      result=move_file(local_dl->file,dest_path);

      g_free(dest_path);
      g_free(filename);
      return result;
}

/*********************************/
/* at end, perform some cleaning */
/*********************************/
static void exit_status_file(void)
{
      if((local_dl->done==TRUE)&&(local_dl->percentDone==100.0))
      {
            gboolean result;
            fprintf(stderr,"File successfully downloaded, updating log file and moving into done/ directory\n");
            result=move_file_to_done_dir();

            local_dl->client_is_running=FALSE;
            munmap(local_dl,sizeof(BT_DL_STRUCT));

            /* on successful 'file move', we delete the mapped file, else, we keep it to allow correct termination */
            if(result==TRUE)
            {
                  unlink(local_map_path->str);
                  unlink(lock_file_path->str);
            }
      }
      else
      {
            /* download not finished, mark the entry as inactive */
            local_dl->client_is_running=FALSE;
            munmap(local_dl,sizeof(BT_DL_STRUCT));
      }
      g_string_free(local_map_path,TRUE);
      local_map_path=NULL;
      g_string_free(lock_file_path,TRUE);
      lock_file_path=NULL;
}

/************************************************************/
/* functions used by the bittorrent download core in python */
/************************************************************/

/**************************************/
/* python prototype: failed(finished) */
/**************************************/
static PyObject *bt2dc_finished(PyObject *self, PyObject *args)
{
      printf("success.\n");
      local_dl->done=TRUE;
      local_dl->done_time=time(NULL);
      local_dl->percentDone=100.0;
      strncpy_max(local_dl->status,"Download Succeeded!",sizeof(local_dl->status));
      local_dl->downRate=0.0;
      local_dl->last_updated_time=time(NULL);

      Py_INCREF(Py_None);
      return Py_None;
}

/**********************************/
/* python prototype: failed(self) */
/**********************************/
static PyObject *bt2dc_failed(PyObject *self, PyObject *args)
{
      printf("failed.\n");
      local_dl->done=TRUE;
      local_dl->percentDone=0.0;
      strncpy_max(local_dl->status,"Download Failed!",sizeof(local_dl->status));
      local_dl->downRate=0.0;
      local_dl->last_updated_time=time(NULL);

      Py_INCREF(Py_None);
      return Py_None;
}

/*******************************************/
/* python prototype: error(self, errormsg) */
/*******************************************/
static PyObject *bt2dc_error(PyObject *self, PyObject *args)
{
      char *str;
      PyObject *arg_errormsg=PyTuple_GetItem(args, 0);

      printf("error.\n");
      str=PyString_AS_STRING(arg_errormsg);
      if(str)
      {
            add_error_message(str);
      }

      Py_INCREF(Py_None);
      return Py_None;
}

static void fmt_time(GString *activity, long long n)
{
#ifdef __lldiv_t_defined
      lldiv_t hms;
      lldir_t hm;

      if(n==-1)
      {
            g_string_assign(activity,"download not progressing (file not being uploaded by others?)");
            return;
      }
      if(n==0)
      {
            g_string_assign(activity,"download complete!");
            return;
      }
      
      hms=lldiv(n,60);  /* hms.rem = seconds */
      hm=lldiv(hms.quot,60);  /* hm.rem= minutes, hm.quot= hour */
      if(hm.quot > 1000000)
            g_string_assign(activity,"N/A");
      else
      {
            if(hm.quot!=0)
                  g_string_sprintf(activity,"finishing in %lld:%02lld'%02lld\"",hm.quot,hm.rem,hms.rem);
            else if(hm.rem!=0)
                  g_string_sprintf(activity,"finishing in %02lld'%02lld\"",hm.rem,hms.rem);
            else
                  g_string_sprintf(activity,"finishing in %02lld\"",hms.rem);
      }
#else
      long long h,m,s;

      if(n==-1)
      {
            g_string_assign(activity,"download not progressing (file not being uploaded by others?)");
            return;
      }
      if(n==0)
      {
            g_string_assign(activity,"download complete!");
            return;
      }
      
      s=n%60;
      n/=60;
      m=n%60;
      h=n/60;

      if(h > 1000000)
            g_string_assign(activity,"N/A");
      else
      {
            if(h!=0)
                  g_string_sprintf(activity,"finishing in %lld:%02lld'%02lld\"",h,m,s);
            else if(m!=0)
                  g_string_sprintf(activity,"finishing in %02lld'%02lld\"",m,s);
            else
                  g_string_sprintf(activity,"finishing in %02lld\"",s);
      }
#endif
}

/*************************************************************************************************************************/
/* python prototype: display(self, fractionDone = None, timeEst = None, downRate = None, upRate = None, activity = None) */
/*************************************************************************************************************************/
static PyObject *bt2dc_display(PyObject *self, PyObject *args)
{
      double arg_fractionDone=-1.0;
      long int arg_timeEst=-1;
      double arg_downRate=-1.0;
      double arg_upRate=-1.0;
      char *arg_activity=NULL;
      GString *activity;

      PyObject *arg_dict=PyTuple_GetItem(args, 0);

      /* arg_dict is a dictionnary, we must extract all required keyword */
      PyObject *tmp_obj;

      PRINT_MSG("display1a\n");
      tmp_obj=PyDict_GetItemString(arg_dict,"fractionDone");
      if(tmp_obj!=NULL)
      {
            PRINT_MSG("display1a1\n");
            arg_fractionDone=PyFloat_AsDouble(tmp_obj);
      }

      PRINT_MSG("display1b\n");
      tmp_obj=PyDict_GetItemString(arg_dict,"timeEst");
      if(tmp_obj!=NULL)
      {
            PRINT_MSG("display1b1\n");
            arg_timeEst=PyFloat_AsDouble(tmp_obj);
      }

      PRINT_MSG("display1c\n");
      tmp_obj=PyDict_GetItemString(arg_dict,"downRate");
      if(tmp_obj!=NULL)
      {
            PRINT_MSG("display1c1\n");
            arg_downRate=PyFloat_AsDouble(tmp_obj);
      }

      PRINT_MSG("display1d\n");
      tmp_obj=PyDict_GetItemString(arg_dict,"upRate");
      if(tmp_obj!=NULL)
      {
            PRINT_MSG("display1d1\n");
            arg_upRate=PyFloat_AsDouble(tmp_obj);
      }

      PRINT_MSG("display1e\n");
      tmp_obj=PyDict_GetItemString(arg_dict,"activity");
      if(tmp_obj!=NULL)
      {
            PRINT_MSG("display1e1\n");
            arg_activity=PyString_AS_STRING(tmp_obj);
      }
      
      PRINT_MSG("display2\n");

#define PYOBJECT_IS_NOT_NONE(po)          ((po)!=NULL)

      /* compute the status string */
      activity=g_string_new("");
      if(PYOBJECT_IS_NOT_NONE(arg_activity)&&(local_dl->done!=TRUE))
      {
            g_string_assign(activity,arg_activity);
      }
      else if(arg_timeEst!=-1)
      {
            fmt_time(activity,arg_timeEst);
      }

      /* append the %age only if it exists */
      if(!(arg_fractionDone<0.0))
      {
            g_string_sprintfa(activity," (%.1f%%)",arg_fractionDone*100.0);
      }
      strncpy(local_dl->status,activity->str,sizeof(local_dl->status));
      g_string_free(activity,TRUE);

      if(!(arg_downRate<0.0))
            local_dl->downRate=arg_downRate;

      if(!(arg_upRate<0.0))
            local_dl->upRate=arg_upRate;
      
      local_dl->last_updated_time=time(NULL);
      Py_INCREF(Py_None);
      return Py_None;
}

/******************************************************************/
/* python prototype: chooseFile(self, default, size, saveas, dir) */
/******************************************************************/
static PyObject *bt2dc_choosefile(PyObject *self, PyObject *args)
{
      char *dflt;
      char *str;
      char resolved_path[PATH_MAX];

      PyObject *arg_default=PyTuple_GetItem(args, 0);
      PyObject *arg_size=PyTuple_GetItem(args,1);
      PyObject *arg_saveas=PyTuple_GetItem(args,2);

      printf("choose.\n");
      str=PyString_AS_STRING(arg_default);
      if(str)
      {
            strncpy_max(local_dl->file,str,sizeof(local_dl->file));
      }
      else
      {
            local_dl->file[0]='\0';
      }

      local_dl->filesize=PyLong_AsUnsignedLongLong(arg_size);

      dflt=PyString_AS_STRING(arg_saveas);
      if((dflt==NULL)||(strlen(dflt)==0))
      {
            dflt=local_dl->file;
      }
      realpath(dflt,resolved_path);
      strncpy_max(local_dl->downloadTo,resolved_path,sizeof(local_dl->downloadTo));

      local_dl->last_updated_time=time(NULL);

#if 0
      Py_DECREF(arg_default);
      Py_DECREF(arg_size);
      Py_DECREF(arg_saveas);
#endif

      return PyString_FromString(local_dl->downloadTo);
}

/*****************************************/
/* python prototype: newpath(self, path) */
/*****************************************/
static PyObject *bt2dc_newpath(PyObject *self, PyObject *args)
{
      char *str;
      PyObject *arg_path=PyTuple_GetItem(args, 0);

      printf("newpath.\n");
      str=PyString_AS_STRING(arg_path);
      if(str==NULL)
            local_dl->downloadTo[0]='\0';
      else
            strncpy_max(local_dl->downloadTo,str,sizeof(local_dl->downloadTo));
      local_dl->last_updated_time=time(NULL);
      Py_INCREF(Py_None);
      return Py_None;
}

static PyMethodDef bt2dc_methods[]={
            {     "finished", bt2dc_finished, METH_VARARGS, "notify successful end of download"},
            {  "failed",   bt2dc_failed,   METH_VARARGS, "notify incorrect end of download"},
            {  "error",       bt2dc_error,    METH_VARARGS, "notify new error message"},
            {  "display",  (PyCFunction)bt2dc_display,  METH_VARARGS|METH_KEYWORDS, "display update"},
            {  "choosefile", bt2dc_choosefile, METH_VARARGS, "choose file"},
            {  "newpath",  bt2dc_newpath,  METH_VARARGS, "new path"},
            {  NULL,       NULL,           0,            NULL}
      };

static PyObject *get_function_from_module(PyObject *module, char *function_name)
{
      PyObject *pDict, *pFunc;

      pDict = PyModule_GetDict( module );
      pFunc=PyDict_GetItemString( pDict, function_name);
      if( pFunc && PyCallable_Check( pFunc ) )
      {
            return pFunc;
      }
      else
      {
            PyErr_Print();
            fprintf(stderr,"Cannot find function \"%s\" in module\n",function_name);
            exit(1);
      }
}

static void create_directory(char *dir)
{
      GString str;

      str.str=dir;
      str.len=strlen(dir);
      if(recursive_mkdir(&str)!=0)
      {
            fprintf(stderr,"Unable to create directory '%s'\n",dir);
            exit(1);
      }
}

static void detach_from_tty(void)
{
      int a;

      a=open("/dev/null",O_WRONLY);
      if(a==-1)
      {
            fprintf(stderr,"unable to open /dev/null and to close tty\n");
            return;
      }

      dup2(a,0);
      dup2(a,1);
      dup2(a,2);
      close(a);
}

int main(int argc, char **argv)
{
      PyObject *pName, *pModule, *pFunc;
      PyObject *pName_threading, *pModule_threading, *pFunc_threading;
      PyObject *mymodule;
      PyObject *bt2dc_finished_func, *bt2dc_failed_func, *bt2dc_error_func, *bt2dc_display_func, *bt2dc_choosefile_func, *bt2dc_newpath_func;
      pid_t old_bt_xfer_pid=0;

      if((argc!=4)&&(argc!=5))
      {
            fprintf(stderr,"Usage: %s download_directory done_directory url.torrent [earlier_bt_xfer_pid_to_delete]\n",argv[0]);
            exit(1);
      }

      download_directory=strdup(argv[1]);
      done_directory=strdup(argv[2]);
      if(argc==5)
            old_bt_xfer_pid=strtoul(argv[4],NULL,10); /* pid of a previous bittorrent downloading the same file (resume mode) */

      create_directory(download_directory);
      create_directory(done_directory);
      if(chdir(download_directory))
      {
            fprintf(stderr,"Fail to move to download directory\n");
            exit(1);
      }

#ifdef BITTORRENT_MODULE_PATH
      setenv("PYTHONPATH",BITTORRENT_MODULE_PATH,TRUE);
#endif
      Py_Initialize();
      if(!Py_IsInitialized())
      {
            fprintf(stderr,"Fail to initialize python interpreter\n");
            exit(1);
      }

      mymodule=Py_InitModule("bt2dc",bt2dc_methods);
      bt2dc_finished_func=get_function_from_module(mymodule,"finished");
      bt2dc_failed_func=get_function_from_module(mymodule,"failed");
      bt2dc_error_func=get_function_from_module(mymodule,"error");
      bt2dc_display_func=get_function_from_module(mymodule,"display");
      bt2dc_choosefile_func=get_function_from_module(mymodule,"choosefile");
      bt2dc_newpath_func=get_function_from_module(mymodule,"newpath");


      /* load the BitTorrent.download module and get the 'download' function object */
      pName = PyString_FromString("BitTorrent.download");
      pName_threading = PyString_FromString("threading");
      pModule = PyImport_Import( pName );
      if(pModule!=NULL)
      {
            pModule_threading= PyImport_Import(pName_threading);
            if(pModule_threading!=NULL)
            {
                  pFunc=get_function_from_module(pModule,"download");
                  pFunc_threading=get_function_from_module(pModule,"Event");

            //PyRun_SimpleString("from BitTorrent.download import download");
            //PyRun_SimpleString("from threading import Event");
            //PyRun_SimpleString("from sys import argv, version, stdout");
                  if(init_status_file(argv[1],argv[3])==0)
                  {
                        PyObject *event;

                        if(old_bt_xfer_pid!=0)
                        {
                              /* if the bittorrent was already download by another dead client, discard this dead client xfer files */
                              /* (except the downloaded file data) to avoid to have 2 lock on the same xfer */
                              GString *temp_path;
                              char *path;

                              temp_path=g_string_new("");
                        path=getenv("HOME");
                        g_string_sprintf(temp_path,"%s/.dc_gui2/bt2dc_gui2/bt_xfer.%d",(path!=NULL)?path:".",old_bt_xfer_pid);
                              unlink(temp_path->str);
                              g_string_append(temp_path,".lock");
                              unlink(temp_path->str);
                              g_string_free(temp_path,TRUE);
                        }
                        event=PyObject_CallFunction(pFunc_threading,NULL);
                        printf("ready\n");
                        detach_from_tty();
                        PyObject_CallFunction(pFunc, "(ss)OOOOOiO", 
                                                            "--url",argv[3],              /* "(ss)" */
                                                            bt2dc_choosefile_func,  /* "O" */
                                                            bt2dc_display_func,           /* "O" */
                                                            bt2dc_finished_func,    /* "O" */
                                                            bt2dc_error_func,             /* "O" */
                                                            event,                                    /* "O" */
                                                            (int)80,                            /* "i" */
                                                            bt2dc_newpath_func);          /* "O" */
                        Py_DECREF(event);
      
                        printf("exiting\n");
                        exit_status_file();
                        Py_DECREF(pModule);
                        kill(getpid(),SIGTERM);       /* self kill to prevent some undead remaining sub-process */
                  }
            }
            else
            {
                  PyErr_Print();
                  fprintf(stderr,"Cannot find module \"threading\"\n");
            }
            Py_DECREF(pModule);
      }
      else
      {
            PyErr_Print();
            fprintf(stderr,"Cannot find module \"BitTorrent.download\"\n");
      }
      Py_DECREF(pName_threading);
      Py_DECREF(pName);
      
      Py_Finalize();
      exit(0);
}

#else
int main(int argc, char **argv)
{
      fprintf(stderr,"Python library was not available during compilation. Recompile after installing them.\n");
      exit(1);
}
#endif

Generated by  Doxygen 1.6.0   Back to index