//**************************************************************************
//*                     This file is part of the                           *
//*                    Mpxplay-MMC - video player.                         *
//*                The source code of Mpxplay-MMC is                       *
//*        (C) copyright 1998-2018 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: digital TV (DVB) handling by BDA driver

//#define MPXPLAY_USE_DEBUGF
#define MPXPLAY_DEBUG_OUTPUT     stdout
#define MPXPLAY_DEBUGOUT_OPTIONS stdout
#define MPXPLAY_DEBUGOUT_READ    NULL
#define MPXPLAY_DEBUGOUT_ERROR   stderr

#include "mpxplay.h"

#if defined(MPXPLAY_GUI_QT)

#include "diskdriv.h"
#include "dtv_drv.h"
#include "tcpcomon.h"
#include "display\display.h"

typedef struct dtv_protocol_list_s
{
	char *prot_name;
	int prot_number;
}dtv_protocol_list_s;

typedef struct dtvdrive_info_s
{
	unsigned int drivenum;
	long connect_id_num;
	dvb_device_t *dtv_device;
	char *filename_store;       // for options parse
}dtvdrive_info_s;

typedef struct dtvfile_info_s
{
	mpxp_uint32_t flags;
	unsigned int openmode;
	dtvdrive_info_s *dtvdi;
	dvb_device_t *dtv_device;
	char *filename_store;
	const char *content_type_fileext;   // dtv content-type to file-extension (TS)
	mpxp_int32_t (*control_cb)(void *mdds,mpxp_uint32_t funcnum,void *argp1,void *argp2);
	void *cb_data;
	mpxp_int64_t filesize, filepos;
	char content_artist[MAX_STRLEN];    // provider name (like CNN)
	char content_title[MAX_STRLEN];     // current program title
}dtvfile_info_s;

static struct dtv_protocol_list_s dtv_protocol_lists[] =
{
	{"atsc:",   BDADEVTYPE_ATSC},
	{"cqam:",   BDADEVTYPE_CQAM},
	{"dvb-c:",  BDADEVTYPE_DVB_C},
//	{"dvb-c2:", BDADEVTYPE_DVB_C2},
	{"dvb-s:",  BDADEVTYPE_DVB_S},
//	{"dvb-s2:", BDADEVTYPE_DVB_S2},
	{"dvb-t:",  BDADEVTYPE_DVB_T},
//	{"dvb-t2:", BDADEVTYPE_DVB_T2},
	{"isdb_s:", BDADEVTYPE_DVB_S}, // ISDB_S is controlled by DVB_S
	{NULL, 0}
};

static const char *demuxer_file_extension = "TS";
static void *drvdtv_mutexhnd_open = NULL;

//----------------------------------------------------------------------------------------

enum { DRVDTV_OPTIONTYPE_STRING = 0, DRVDTV_OPTIONTYPE_INTEGER, DRVDTV_OPTIONTYPE_CHAR};

typedef struct dtv_option_list_s
{
	const char *option_name;
	unsigned int option_type;
	unsigned int cfg_pos;
	mpxp_int32_t (*option_proxy)(char *value_str, int value_num);
}dtv_option_list_s;

static const struct dtv_option_list_s dtv_options[] =
{
	{"dvb-adapter",           DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,adapter_num), NULL},
	{"dvb-frequency",         DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,frequency), NULL},
	{"dvb-inversion",         DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,inversion), NULL},
	{"dvb-network-name",      DRVDTV_OPTIONTYPE_STRING,  offsetof(dvb_device_t,network_name), NULL},

	{"dvb-program-id",        DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,program_number_id), NULL}, // TODO: demultiplexer

	// ATSC
	{"dvb-physical-channel",  DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,physical_channel), NULL},
	{"dvb-major-channel",     DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,major_channel), NULL},
	{"dvb-minor-channel",     DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,minor_channel), NULL},

	// DVB-S, ISDB_S
	{"dvb-polarization",      DRVDTV_OPTIONTYPE_CHAR,    offsetof(dvb_device_t,lnb_polarization), NULL},
	{"dvb-srate",             DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,srate), NULL},
	{"dvb-lnb-low",           DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,lowf), NULL},
	{"dvb-lnb-high",          DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,highf), NULL},
	{"dvb-lnb-switch",        DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,switchf), NULL},
	{"dvb-network-id",        DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,network_id), NULL},
	{"dvb-azimuth",           DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,azimuth), NULL},
	{"dvb-elevation",         DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,elevation), NULL},
	{"dvb-longitude",         DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,longitude), NULL},
	{"dvb-range",             DRVDTV_OPTIONTYPE_STRING,  offsetof(dvb_device_t,range), NULL},

	// DVB-T
	{"dvb-bandwidth",         DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,bandwidth), NULL},
	{"dvb-transmission",      DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,transmission), NULL},
	{"dvb-hierarchy",         DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,hierarchy), NULL},
	{"dvb-modulation",        DRVDTV_OPTIONTYPE_STRING,  offsetof(dvb_device_t,mod), NULL},

//	{"dvb-fec",               DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,fec), NULL},    // TODO: not correct
//	{"dvb-code-rate-hp",      DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,fec_hp), NULL}, // TODO: not correct
//	{"dvb-code-rate-lp",      DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,fec_lp), NULL}, // TODO: not correct
//	{"dvb-guard",             DRVDTV_OPTIONTYPE_INTEGER, offsetof(dvb_device_t,guard), NULL},  // TODO: not correct
	{NULL,0,0,NULL}
};

static mpxp_bool_t dtvdrive_parse_options(char *pathname, char **opt_store, dvb_device_t *dev_cfg)
{
	struct dtv_protocol_list_s *prot = &dtv_protocol_lists[0];
	char *cmdline_dvb_options_str;

	mpxplay_debugf(MPXPLAY_DEBUGOUT_OPTIONS, "dtvdrive_parse_options BEGIN %s", pathname);

	*opt_store = cmdline_dvb_options_str = (char *)pds_malloc(pds_strlen(pathname) + 1);
	if(!cmdline_dvb_options_str)
		return FALSE;
	pds_strcpy(cmdline_dvb_options_str, pathname);

	do{
		int prot_name_len = pds_strlen(prot->prot_name);
		if(strnicmp(prot->prot_name, cmdline_dvb_options_str, prot_name_len) == 0)
		{
			cmdline_dvb_options_str += prot_name_len;
			break;
		}
		prot++;
	}while(prot->prot_name);

	if(cmdline_dvb_options_str <= *opt_store)
	{
		mpxplay_debugf(MPXPLAY_DEBUGOUT_ERROR, "dtvdrive_parse_options: no valid protocol found!");
		return FALSE;
    }

	while((cmdline_dvb_options_str[0] == '/') || (cmdline_dvb_options_str[0] == ':'))
		cmdline_dvb_options_str++;

	while(cmdline_dvb_options_str && cmdline_dvb_options_str[0])
	{
		char *next = pds_strchr(cmdline_dvb_options_str, ':'), *value_str;
		mpxp_bool_t found = FALSE;
		if(next)
			*next++ =0;
		value_str = pds_strchr(cmdline_dvb_options_str,'=');
		//mpxplay_debugf(MPXPLAY_DEBUGOUT_OPTIONS, "optstr: %s val: %s", cmdline_dvb_options_str, value_str);
		if(value_str)
		{
			const struct dtv_option_list_s *opts = &dtv_options[0];
			*value_str++ = 0;
			pds_strcutspc(cmdline_dvb_options_str);
			pds_strcutspc(value_str);
			do{
				if( (pds_stricmp(cmdline_dvb_options_str, (char *)opts->option_name) == 0)
				 || (pds_stricmp(cmdline_dvb_options_str, (char *)&opts->option_name[sizeof("dvb-")-1]) == 0) // !!!
				){
					void *cfgval_ptr = (void *)dev_cfg + opts->cfg_pos;
					switch(opts->option_type)
					{
						case DRVDTV_OPTIONTYPE_STRING:  *((char **)cfgval_ptr) = value_str;
							break;
						case DRVDTV_OPTIONTYPE_INTEGER:
						{
							mpxp_int32_t value_num = pds_atol(value_str);
							*((mpxp_int32_t *)cfgval_ptr) = (opts->option_proxy)? opts->option_proxy(value_str, value_num) : value_num;
							break;
						}
						case DRVDTV_OPTIONTYPE_CHAR:  *((char *)cfgval_ptr) = value_str[0];
							break;
					}
					found = TRUE;
					mpxplay_debugf(MPXPLAY_DEBUGOUT_OPTIONS, "optstr: \"%s\" value: %s", cmdline_dvb_options_str, value_str);
					break;
				}
				opts++;
			}while(opts->option_name);
		}
		if(!found)
		{
			char sout[256];
			snprintf(sout, sizeof(sout), "DRVDTV: unknown/invalid option: \"%s\"", cmdline_dvb_options_str);
			display_timed_message(sout);
		}

		cmdline_dvb_options_str = next;
	}

	if(!dev_cfg->frequency)
	{
		mpxplay_debugf(MPXPLAY_DEBUGOUT_ERROR, "frequency is not parsed ZERO! %s", pathname);
	}

	mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "dtvdrive_parse_options END %s", pathname);

	return TRUE;
}

static struct dtv_protocol_list_s *dtvdrive_search_protocol(char *pathname)
{
	struct dtv_protocol_list_s *prot = &dtv_protocol_lists[0];

	while(prot->prot_name)
	{
		if(strnicmp(prot->prot_name, pathname, pds_strlen(prot->prot_name)) == 0)
			return prot;
		prot++;
	}

	return NULL;
}

//------------------------------------------------------------------------------------------

static unsigned int dtvdrive_drive_check(char *pathname)
{
	struct dtv_protocol_list_s *prot = dtvdrive_search_protocol(pathname);

	if(prot)
		return prot->prot_number;

	return 0;
}

static long dtvdrive_drive_config(void *drive_data,unsigned long funcnum,void *argp1,void *argp2)
{
	struct dtvfile_info_s *dtvfi;

	switch(funcnum){
		case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CANBESTREAM:
			return TRUE;
		case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CHKBUFBLOCKBYTES:
			return 262144;
		case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_PREBUFFERSIZE:
			return 32 * 1048576;                               // ??? (should be time based, not byte)
		case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_PREREADBUFBYTES:  //
			return 1 * 1048576;                                // ??? (should be time based, not byte)
	}

	if(!drive_data)
		return MPXPLAY_DISKDRIV_CFGERROR_INVALID_DRIVE;
	dtvfi = drive_data;

	switch(funcnum){
		// dtvfi
		case MPXPLAY_DISKFILE_CFGFUNCNUM_CMD_TERMINATE:
			funcbit_enable(dtvfi->flags, MPXPLAY_DRVDTV_FLAG_TERMINATE);
			if(dtvfi->dtv_device)
			{
				funcbit_enable(dtvfi->dtv_device->flags, MPXPLAY_DRVDTV_FLAG_TERMINATE);
				funcbit_disable(dtvfi->dtv_device->flags, MPXPLAY_DRVDTV_FLAG_READWAIT);
			}
			return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
		case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_ISNONSEEKABLE:
			return ((dtvfi->flags&DRVFTP_FTPFILE_FLAG_NONSEEKABLE)? 1:0);
		case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_ISLIVESTREAM:
			return ((dtvfi->flags&DRVFTP_FTPFILE_FLAG_LIVESTREAM)? 1:0);
		case MPXPLAY_DISKFILE_CFGFUNCNUM_SET_READWAIT:
			if(!argp1)
				return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
			if(!dtvfi->dtv_device)
				return MPXPLAY_DISKDRIV_CFGERROR_INVALID_DRIVE;
			if(*((unsigned long *)argp1))
				funcbit_enable(dtvfi->dtv_device->flags, MPXPLAY_DRVDTV_FLAG_READWAIT);
			else
				funcbit_disable(dtvfi->dtv_device->flags, MPXPLAY_DRVDTV_FLAG_READWAIT);
			return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
		case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENTTYPEEXT:
			if(!argp1)
				return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
			*((char **)argp1) = (char *)dtvfi->content_type_fileext;
			return 1;
		case MPXPLAY_DISKDRIV_CFGFUNCNUM_SET_CALLBACKPSIFN:
			if(!argp1)
				return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
			dtvfi->control_cb = argp1;
			dtvfi->cb_data = argp2;  // can be NULL
			//httpdrive_filter_parse_metadata(htfi); // !!! hack // TODO
			return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
		case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENT_ARTIST:
			if(!argp1 || !argp2)
				return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
			pds_strncpy((char *)argp1,dtvfi->content_artist,*((unsigned long *)argp2));
			((char *)argp1)[*((unsigned long *)argp2)] = 0;
			return 1;
		case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENT_TITLE:
			if(!argp1 || !argp2)
				return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
			pds_strncpy((char *)argp1,dtvfi->content_title,*((unsigned long *)argp2));
			((char *)argp1)[*((unsigned long *)argp2)] = 0;
			return 1;
 }

 return MPXPLAY_DISKDRIV_CFGERROR_UNSUPPFUNC;
}

#if 0

static void dtvdrive_drive_unmount(void *drivehand_data)
{
	struct dtvdrive_info_s *dtis = (struct dtvdrive_info_s *)drivehand_data;
	if(dtis)
	{
		dvb_close(dtis->dtv_device);
		if(dtis->filename_store)
			pds_free(dtis->filename_store);
		pds_free(dtis);
	}
}

static void *dtvdrive_drive_mount(char *pathname)
{
	struct dtvdrive_info_s *dtis;
	unsigned int prot_number;

	prot_number = dtvdrive_drive_check(pathname);
	if(!prot_number)
		return NULL;

	dtis=(struct dtvdrive_info_s *)pds_calloc(1,sizeof(*dtis));
	if(!dtis)
		return dtis;

	if(drvdtv_connectid_num >= 0x7fffffff)
		funcbit_smp_int32_put(drvdtv_connectid_num,0);

	funcbit_smp_int32_increment(drvdtv_connectid_num);
	dtis->connect_id_num = drvdtv_connectid_num;

	dtis->dtv_device = dvb_open (NULL);
	if(!dtis->dtv_device)
		goto err_out_mount;
	dtis->dtv_device->protocol_number = prot_number;

	dtvdrive_parse_options(pathname, &dtis->filename_store, dtis->dtv_device);

	return dtis;

err_out_mount:
	dtvdrive_drive_unmount((void *)dtis);
	return NULL;
}

static unsigned int dtvdrive_findfirst(void *drivehand_data,char *pathname,unsigned int attrib,struct pds_find_t *ffblk)
{
	return 0;//pds_findfirst(pathname,attrib,ffblk);
}

static unsigned int dtvdrive_findnext(void *drivehand_data,struct pds_find_t *ffblk)
{
	return 0;//pds_findnext(ffblk);
}

static void dtvdrive_findclose(void *drivehand_data,struct pds_find_t *ffblk)
{
	//pds_findclose(ffblk);
}

static char *dtvdrive_getcwd(void *drivehand_data,char *buf,unsigned int buflen)
{
	struct dtvdrive_info_s *dtis = (struct dtvdrive_info_s *)drivehand_data;
	//pds_getdcwd(hdis->drivenum+1,buf);
	*buf = 0;
	return buf;
}

static int dtvdrive_chdir(void *drivehand_data,char *path)
{
	return -1;//pds_chdir(path);
}

static int dtvdrive_mkdir(void *drivehand_data,char *path)
{
 return -1;//pds_mkdir(path);
}

static int dtvdrive_rmdir(void *drivehand_data,char *path)
{
 return -1;//pds_rmdir(path);
}

static int dtvdrive_rename(void *drivehand_data,char *oldfilename,char *newfilename)
{
 return -1;//pds_rename(oldfilename,newfilename);
}

static int dtvdrive_unlink(void *drivehand_data,char *filename)
{
 return -1;//pds_unlink(filename);
}

#endif // 0

//---------------------------------------------------------------------

static void dtvdrive_file_close(void *filehand_data);

static unsigned int dtvdrive_file_check(void *drivehand_data,char *filename)
{
	return dtvdrive_drive_check(filename);
}

static void *dtvdrive_file_open(void *drivehand_data,char *filename,unsigned long openmode,int *err_code)
{
	struct dtvfile_info_s *dtvfi = NULL;
	unsigned int prot_number;
	int retVal = MPXPLAY_ERROR_DISKDRIV_ERROR;
	char sout[256];

	if(openmode&(O_RDWR|O_WRONLY|O_CREAT))
		goto err_out_openr;

	if(!drvdtv_mutexhnd_open)
		pds_threads_mutex_new(&drvdtv_mutexhnd_open);
	PDS_THREADS_MUTEX_LOCK(&drvdtv_mutexhnd_open, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);

	prot_number = dtvdrive_drive_check(filename);
	if(!prot_number)
	{
		retVal = MPXPLAY_ERROR_DISKDRIV_PROTOCOL;
		goto err_out_openr;
	}

	dtvfi = pds_calloc(1,sizeof(*dtvfi));
	if(!dtvfi)
		goto err_out_openr;

	dtvfi->filesize = MPXPLAY_DISKDRIV_FILESIZE_UNKNOWN;
	dtvfi->openmode = openmode;
	dtvfi->dtvdi = (dtvdrive_info_s *)drivehand_data;

	dtvfi->dtv_device = dtv_bda_device_alloc();
	if(!dtvfi->dtv_device)
		goto err_out_openr;

	mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "dtvdrive_file_open fh:%8.8X om:%8.8X %s", (unsigned int)dtvfi, openmode, filename);

	dtvfi->dtv_device->protocol_number_id = prot_number;
	funcbit_enable(dtvfi->flags,DRVFTP_FTPFILE_FLAG_NONSEEKABLE);
	funcbit_enable(dtvfi->flags,DRVFTP_FTPFILE_FLAG_LIVESTREAM);
	dtvfi->content_type_fileext = demuxer_file_extension;

	if(dtvdrive_parse_options(filename, &dtvfi->filename_store, dtvfi->dtv_device) == FALSE) // new error code for this?
	{
		retVal = MPXPLAY_ERROR_DISKDRIV_PROTOCOL;
		goto err_out_openr;
	}

	if(!funcbit_test(openmode, MPXPLAY_INFILE_OPENMODE_LOAD_PLAY) && funcbit_test(openmode, MPXPLAY_INFILE_OPENMODE_CHECK))
	{
		retVal = MPXPLAY_ERROR_DISKDRIV_OPENMODE;
		goto err_out_openr;
	} // else open for play or simple copy

	mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "----------------- LOAD_PLAY fh:%8.8X dv:%8.8X %s", (unsigned int)dtvfi, (unsigned int)dtvfi->dtv_device, filename);

	if(funcbit_test(openmode, MPXPLAY_INFILE_OPENMODE_LOAD_PLAY))
	{
		funcbit_enable(dtvfi->dtv_device->flags, MPXPLAY_DRVDTV_FLAG_OPENMODE_PLAY);
		snprintf(sout, sizeof(sout), "DTV: opening %d Mhz", dtvfi->dtv_device->frequency / 1000000);
		display_timed_message(sout);
		dtvfi->dtv_device->timeout_open_showsignalstrength = pds_gettimem();
	}

	if(dtv_bda_resource_assign(dtvfi->dtv_device) != MPXPLAY_ERROR_DISKDRIV_OK)
		goto err_out_openr;

	snprintf(dtvfi->content_artist, sizeof(dtvfi->content_artist), "DTV-%dMhz-%6d", dtvfi->dtv_device->frequency / 1000000, pds_gettime());

	mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "----------------- LOAD_PLAY END fh:%8.8X dv:%8.8X %s", (unsigned int)dtvfi, (unsigned int)dtvfi->dtv_device, filename);

	if(err_code)
		*err_code = MPXPLAY_ERROR_DISKDRIV_OK;

	PDS_THREADS_MUTEX_UNLOCK(&drvdtv_mutexhnd_open);

	return dtvfi;

err_out_openr:
	mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "ERR_out_openr fh:%8.8X di:%8.8X %s", (unsigned int)dtvfi, (unsigned int)dtvfi->dtv_device, filename);
	if(dtvfi && dtvfi->dtv_device && (retVal != MPXPLAY_ERROR_DISKDRIV_OPENMODE))
	{
		snprintf(sout, sizeof(sout), "DTV: init %d Mhz failed!", dtvfi->dtv_device->frequency / 1000000);
		display_timed_message(sout);
	}
	PDS_THREADS_MUTEX_UNLOCK(&drvdtv_mutexhnd_open);
	dtvdrive_file_close(dtvfi);
	if(err_code)
		*err_code = retVal;
	return NULL;
}

static void dtvdrive_file_close(void *filehand_data)
{
	struct dtvfile_info_s *dtvfi = (struct dtvfile_info_s *)filehand_data;
	PDS_THREADS_MUTEX_LOCK(&drvdtv_mutexhnd_open, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);
	mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "dtvdrive_file_close fh:%8.8X di:%8.8X bda:%8.8X",
			filehand_data, (unsigned int)dtvfi->dtv_device, ((dtvfi->dtv_device)? (unsigned int)dtvfi->dtv_device->bdagraph_module : 0));
	if(dtvfi)
	{
		dtv_bda_resource_remove(dtvfi->dtv_device);
		dtv_bda_device_free(dtvfi->dtv_device);
		if(dtvfi->filename_store)
			pds_free(dtvfi->filename_store);
		pds_free(dtvfi);
	}
	PDS_THREADS_MUTEX_UNLOCK(&drvdtv_mutexhnd_open);
}

static long dtvdrive_file_read(void *filehand_data,char *ptr,unsigned int num)
{
	struct dtvfile_info_s *dtvfi = (struct dtvfile_info_s *)filehand_data;
	long bytes = -1;
	char sout[256];

	if(!dtvfi)
		return MPXPLAY_ERROR_DISKDRIV_ERROR;
	if(funcbit_test(dtvfi->flags, MPXPLAY_DRVDTV_FLAG_TERMINATE))
		return MPXPLAY_ERROR_DISKDRIV_CONNECT_TERMINATED;

	if(PDS_THREADS_MUTEX_LOCK(&drvdtv_mutexhnd_open, 0) == MPXPLAY_ERROR_OK)
	{
//		mpxplay_debugf(MPXPLAY_DEBUGOUT_READ, "dtvdrive_file_read fh:%8.8X n:%d", (unsigned int)dtvfi, num);
		if(dtvfi && dtvfi->dtv_device && ptr && num)
		{
			dvb_device_t *d = dtvfi->dtv_device;
			bytes = dtv_bda_device_read(d, ptr, num);
			if(bytes > 0)
			{
				dtvfi->filepos += bytes;
			}
			else
			{
				//mpxplay_debugf(MPXPLAY_DEBUGOUT_ERROR, "dtvdrive_file_read dtv_bda_device_read fh:%8.8X b:%d", (unsigned int)dtvfi, bytes);
			}
			if(d->timeout_open_showsignalstrength)
			{
				mpxp_uint64_t currtime_ms = pds_gettimem();
				if(currtime_ms >= (d->timeout_open_showsignalstrength + MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_OPENSHOWSIGSTR_60))
				{
					d->timeout_open_showsignalstrength = 0;
				}
				else
				{
					int signal_strength = dtv_bda_device_get_signal_strength(d);
					if((signal_strength <= 60) || (currtime_ms < (d->timeout_open_showsignalstrength + MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_OPENSHOWSIGSTR_100)))
					{
						snprintf(sout, sizeof(sout), "DTV: opening %d Mhz, signal: %d%%", d->frequency / 1000000, signal_strength);
						display_timed_message(sout);
					}
				}
			}
		}
		PDS_THREADS_MUTEX_UNLOCK(&drvdtv_mutexhnd_open);
	}
	return bytes;
}

static mpxp_filesize_t dtvdrive_file_seek(void *filehand_data,mpxp_filesize_t pos,int fromwhere)
{
	struct dtvfile_info_s *dtvfi = (struct dtvfile_info_s *)filehand_data;
	return dtvfi->filepos;
}

static mpxp_filesize_t dtvdrive_file_tell(void *filehand_data)
{
	struct dtvfile_info_s *dtvfi = (struct dtvfile_info_s *)filehand_data;
	return dtvfi->filepos;
}

static mpxp_filesize_t dtvdrive_file_length(void *filehand_data)
{
	struct dtvfile_info_s *dtvfi = (struct dtvfile_info_s *)filehand_data;
	return dtvfi->filesize;
}

static int dtvdrive_file_eof(void *filehand_data)
{
	return 0;
}

//------------------------------------------------------------------------

struct mpxplay_drivehand_func_s DTVDRIVE_drivehand_funcs={
 "DTVDRIVE",
 MPXPLAY_DRIVEHANDFUNC_INFOBIT_SLOWACCESS,// | MPXPLAY_DRIVEHANDFUNC_INFOBIT_DISCONNECT,
 &dtvdrive_drive_config,
 &dtvdrive_drive_check,
 NULL,//&dtvdrive_drive_mount,
 NULL,//&dtvdrive_drive_unmount,
 NULL,//&dtvdrive_findfirst,
 NULL,//&dtvdrive_findnext,
 NULL,//&dtvdrive_findclose,
 NULL,//&dtvdrive_getcwd,
 NULL,//&dtvdrive_chdir,
 NULL,//&dtvdrive_mkdir,
 NULL,//&dtvdrive_rmdir,
 NULL,//&dtvdrive_rename,
 NULL,//&dtvdrive_unlink,
 NULL, // r15
 NULL, // r16
 NULL, // r17
 NULL, // r18
 NULL, // r19
 NULL, // r20

 &dtvdrive_file_check,
 &dtvdrive_file_open,
 &dtvdrive_file_close,
 &dtvdrive_file_read,
 NULL, // file_write
#if 1
 &dtvdrive_file_seek,
 &dtvdrive_file_tell,
 &dtvdrive_file_length,
 &dtvdrive_file_eof,
#else
 NULL,
 NULL,
 NULL,
 NULL,
#endif
 NULL, // file_chsize
 NULL  // r31
};

#endif // MPXPLAY_GUI_QT
