/*
 *  Copyright (C) 2004-2010, Eric Lund
 *  http://www.mvpmc.org/
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * proginfo.c - functions to manage MythTV program info.  This is
 *              information kept by MythTV to describe recordings and
 *              also to describe programs in the program guide.  The
 *              functions here allocate and fill out program
 *              information and lists of program information.  They
 *              also retrieve and manipulate recordings and program
 *              material based on program information.
 */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <inttypes.h>
#include <cmyth_local.h>

/*
 * cmyth_proginfo_destroy()
 *
 * Scope: PRIVATE (static)
 *
 * Description
 *
 * Destroy the program info structure pointed to by 'p' and release
 * its storage.  This should only be called by
 * ref_release(). All others should use
 * ref_release() to release references to a program info
 * structure.
 *
 * Return Value:
 *
 * None.
 */
static void
cmyth_proginfo_destroy(cmyth_proginfo_t p)
{
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s {\n", __FUNCTION__);
	if (!p) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!a\n", __FUNCTION__);
		return;
	}
	if (p->proginfo_title) {
		ref_release(p->proginfo_title);
	}
	if (p->proginfo_subtitle) {
		ref_release(p->proginfo_subtitle);
	}
	if (p->proginfo_description) {
		ref_release(p->proginfo_description);
	}
	if (p->proginfo_category) {
		ref_release(p->proginfo_category);
	}
	if (p->proginfo_chanstr) {
		ref_release(p->proginfo_chanstr);
	}
	if (p->proginfo_chansign) {
		ref_release(p->proginfo_chansign);
	}
	if (p->proginfo_channame) {
		ref_release(p->proginfo_channame);
	}
	if (p->proginfo_chanicon) {
		ref_release(p->proginfo_chanicon);
	}
	if (p->proginfo_url) {
		ref_release(p->proginfo_url);
	}
	if (p->proginfo_unknown_0) {
		ref_release(p->proginfo_unknown_0);
	}
	if (p->proginfo_hostname) {
		ref_release(p->proginfo_hostname);
	}
	if (p->proginfo_rec_profile) {
		ref_release(p->proginfo_rec_profile);
	}
	if (p->proginfo_recgroup) {
		ref_release(p->proginfo_recgroup);
	}
	if (p->proginfo_chancommfree) {
		ref_release(p->proginfo_chancommfree);
	}
	if (p->proginfo_chan_output_filters) {
		ref_release(p->proginfo_chan_output_filters);
	}
	if (p->proginfo_seriesid) {
		ref_release(p->proginfo_seriesid);
	}
	if (p->proginfo_programid) {
		ref_release(p->proginfo_programid);
	}
	if (p->proginfo_inetref) {
		ref_release(p->proginfo_inetref);
	}
	if (p->proginfo_stars) {
		ref_release(p->proginfo_stars);
	}
	if (p->proginfo_pathname) {
		ref_release(p->proginfo_pathname);
	}
	if (p->proginfo_host) {
		ref_release(p->proginfo_host);
	}
	if (p->proginfo_playgroup) {
		ref_release(p->proginfo_playgroup);
	}
	if (p->proginfo_lastmodified) {
		ref_release(p->proginfo_lastmodified);
	}
	if (p->proginfo_start_ts) {
		ref_release(p->proginfo_start_ts);
	}
	if (p->proginfo_end_ts) {
		ref_release(p->proginfo_end_ts);
	}
	if (p->proginfo_rec_start_ts) {
		ref_release(p->proginfo_rec_start_ts);
	}
	if (p->proginfo_rec_end_ts) {
		ref_release(p->proginfo_rec_end_ts);
	}
	if (p->proginfo_originalairdate) {
		ref_release(p->proginfo_originalairdate);
	}
	if (p->proginfo_storagegroup) {
		ref_release(p->proginfo_storagegroup);
	}
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s }\n", __FUNCTION__);
}

/*
 * cmyth_proginfo_create()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Create a programinfo structure to be used to hold program
 * information and return a pointer to the structure.  The structure
 * is initialized to default values.
 *
 * Return Value:
 *
 * Success: A non-NULL cmyth_proginfo_t (this type is a pointer)
 *
 * Failure: A NULL cmyth_proginfo_t
 */
cmyth_proginfo_t
cmyth_proginfo_create(void)
{
	cmyth_proginfo_t ret = ref_alloc(sizeof(*ret));

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s {\n", __FUNCTION__);
	if (!ret) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!\n", __FUNCTION__);
		return NULL;
	}
	ref_set_destroy(ret, (ref_destroy_t)cmyth_proginfo_destroy);

	ret->proginfo_start_ts = cmyth_timestamp_create();
	if (!ret->proginfo_start_ts) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!!\n", __FUNCTION__);
		goto err;
	}
	ret->proginfo_end_ts = cmyth_timestamp_create();
	if (!ret->proginfo_end_ts) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!!!\n", __FUNCTION__);
		goto err;
	}
	ret->proginfo_rec_start_ts = cmyth_timestamp_create();
	if (!ret->proginfo_rec_start_ts) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!!!!\n", __FUNCTION__);
		goto err;
	}
	ret->proginfo_rec_end_ts = cmyth_timestamp_create();
	if (!ret->proginfo_rec_end_ts) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s } !!!!!\n", __FUNCTION__);
		goto err;
	}
	ret->proginfo_lastmodified = cmyth_timestamp_create();
	if (!ret->proginfo_lastmodified) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s } !!!!!!\n", __FUNCTION__);
		goto err;
	}
	ret->proginfo_originalairdate = cmyth_timestamp_create();
	if (!ret->proginfo_originalairdate) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s } !!!!!!!\n", __FUNCTION__);
		goto err;
	}
	ret->proginfo_title = NULL;
	ret->proginfo_subtitle = NULL;
	ret->proginfo_description = NULL;
	ret->proginfo_season = 0;
	ret->proginfo_episode = 0;
	ret->proginfo_category = NULL;
	ret->proginfo_chanId = 0;
	ret->proginfo_chanstr = NULL;
	ret->proginfo_chansign = NULL;
	ret->proginfo_channame = NULL;
	ret->proginfo_chanicon = NULL;
	ret->proginfo_url = NULL;
	ret->proginfo_pathname = NULL;
	ret->proginfo_host = NULL;
	ret->proginfo_port = 0;
	ret->proginfo_Length = 0;
	ret->proginfo_conflicting = 0;
	ret->proginfo_unknown_0 = NULL;
	ret->proginfo_recording = 0;
	ret->proginfo_override = 0;
	ret->proginfo_hostname = NULL;
	ret->proginfo_source_id = 0;
	ret->proginfo_card_id = 0;
	ret->proginfo_input_id = 0;
	ret->proginfo_rec_priority = 0;
	ret->proginfo_rec_status = 0;
	ret->proginfo_record_id = 0;
	ret->proginfo_rec_type = 0;
	ret->proginfo_rec_dupin = 0;
	ret->proginfo_rec_dupmethod = 0;
	ret->proginfo_repeat = 0;
	ret->proginfo_program_flags = 0;
	ret->proginfo_rec_profile = NULL;
	ret->proginfo_recgroup = NULL;
	ret->proginfo_chancommfree = NULL;
	ret->proginfo_chan_output_filters = NULL;
	ret->proginfo_seriesid = NULL;
	ret->proginfo_programid = NULL;
	ret->proginfo_inetref = NULL;
	ret->proginfo_stars = NULL;
	ret->proginfo_version = 12;
	ret->proginfo_hasairdate = 0;
	ret->proginfo_playgroup = NULL;
	ret->proginfo_storagegroup = NULL;
	ret->proginfo_recpriority_2 = 0;
	ret->proginfo_parentid = 0;
	ret->proginfo_audioproperties = 0;
	ret->proginfo_videoproperties = 0;
	ret->proginfo_subtitletype = 0;
	ret->proginfo_year = 0;
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s }\n", __FUNCTION__);
	return ret;

    err:
	ref_release(ret);
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s } !++\n", __FUNCTION__);
       	return NULL;
}

/*
 * cmyth_proginfo_create()
 *
 * Scope: PRIVATE (static)
 *
 * Description
 *
 * Duplicate a program information structure into a new one.  The sub-fields
 * get held, not actually copied.
 *
 * Return Value:
 *
 * Success: A non-NULL cmyth_proginfo_t (this type is a pointer)
 *
 * Failure: A NULL cmyth_proginfo_t
 */
static cmyth_proginfo_t
cmyth_proginfo_dup(cmyth_proginfo_t p)
{
	cmyth_proginfo_t ret = cmyth_proginfo_create();

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s {\n", __FUNCTION__);
	if (!ret) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!\n", __FUNCTION__);
		return NULL;
	}
	ref_set_destroy(ret, (ref_destroy_t)cmyth_proginfo_destroy);

	ret->proginfo_start_ts = ref_hold(p->proginfo_start_ts);
	ret->proginfo_end_ts = ref_hold(p->proginfo_end_ts);
	ret->proginfo_rec_start_ts = ref_hold(p->proginfo_rec_start_ts);
	ret->proginfo_rec_end_ts = ref_hold(p->proginfo_rec_end_ts);
	ret->proginfo_lastmodified = ref_hold(p->proginfo_lastmodified);
	ret->proginfo_originalairdate = ref_hold(p->proginfo_originalairdate);
	ret->proginfo_title = ref_hold(p->proginfo_title);
	ret->proginfo_subtitle = ref_hold(p->proginfo_subtitle);
	ret->proginfo_description = ref_hold(p->proginfo_description);
	ret->proginfo_season = p->proginfo_season;
	ret->proginfo_episode = p->proginfo_episode;
	ret->proginfo_category = ref_hold(p->proginfo_category);
	ret->proginfo_chanId = p->proginfo_chanId;
	ret->proginfo_chanstr = ref_hold(p->proginfo_chanstr);
	ret->proginfo_chansign = ref_hold(p->proginfo_chansign);
	ret->proginfo_channame = ref_hold(p->proginfo_channame);
	ret->proginfo_chanicon = ref_hold(p->proginfo_chanicon);
	ret->proginfo_url = ref_hold(p->proginfo_url);
	ret->proginfo_pathname = ref_hold(p->proginfo_pathname);
	ret->proginfo_host = ref_hold(p->proginfo_host);
	ret->proginfo_port = p->proginfo_port;
	ret->proginfo_Length = p->proginfo_Length;
	ret->proginfo_conflicting = p->proginfo_conflicting;
	ret->proginfo_unknown_0 = ref_hold(p->proginfo_unknown_0);
	ret->proginfo_recording = p->proginfo_recording;
	ret->proginfo_override = p->proginfo_override;
	ret->proginfo_hostname = ref_hold(p->proginfo_hostname);
	ret->proginfo_source_id = p->proginfo_source_id;
	ret->proginfo_card_id = p->proginfo_card_id;
	ret->proginfo_input_id = p->proginfo_input_id;
	ret->proginfo_rec_priority = p->proginfo_rec_priority;
	ret->proginfo_rec_status = p->proginfo_rec_status;
	ret->proginfo_record_id = p->proginfo_record_id;
	ret->proginfo_rec_type = p->proginfo_rec_type;
	ret->proginfo_rec_dupin = p->proginfo_rec_dupin;
	ret->proginfo_rec_dupmethod = p->proginfo_rec_dupmethod;
	ret->proginfo_repeat = p->proginfo_repeat;
	ret->proginfo_program_flags = p->proginfo_program_flags;
	ret->proginfo_rec_profile = ref_hold(p->proginfo_rec_profile);
	ret->proginfo_recgroup = ref_hold(p->proginfo_recgroup);
	ret->proginfo_chancommfree = ref_hold(p->proginfo_chancommfree);
	ret->proginfo_chan_output_filters = ref_hold(p->proginfo_chan_output_filters);
	ret->proginfo_seriesid = ref_hold(p->proginfo_seriesid);
	ret->proginfo_programid = ref_hold(p->proginfo_programid);
	ret->proginfo_inetref = ref_hold(p->proginfo_inetref);
	ret->proginfo_stars = ref_hold(p->proginfo_stars);
	ret->proginfo_version = p->proginfo_version;
	ret->proginfo_hasairdate = p->proginfo_hasairdate;
	ret->proginfo_playgroup = ref_hold(p->proginfo_playgroup);
	ret->proginfo_storagegroup = ref_hold(p->proginfo_storagegroup);
	ret->proginfo_recpriority_2 = p->proginfo_recpriority_2;
	ret->proginfo_parentid = p->proginfo_parentid;
	ret->proginfo_audioproperties = p->proginfo_audioproperties;
	ret->proginfo_videoproperties = p->proginfo_videoproperties;
	ret->proginfo_subtitletype = p->proginfo_subtitletype;
	ret->proginfo_year = p->proginfo_year;
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s }\n", __FUNCTION__);
	return ret;
}

char *
cmyth_proginfo_string(cmyth_conn_t control, cmyth_proginfo_t prog)
{
	char *buf;
	unsigned int len = ((2 * CMYTH_INT64_LEN) +
			    (6 * CMYTH_TIMESTAMP_LEN) +
			    (16 * CMYTH_INT32_LEN));
	char start_ts[CMYTH_TIMESTAMP_LEN + 1];
	char end_ts[CMYTH_TIMESTAMP_LEN + 1];
	char rec_start_ts[CMYTH_TIMESTAMP_LEN + 1];
	char rec_end_ts[CMYTH_TIMESTAMP_LEN + 1];
	char originalairdate[CMYTH_TIMESTAMP_LEN + 1];
	char lastmodified[CMYTH_TIMESTAMP_LEN + 1];

	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no program info\n",
			  __FUNCTION__);
		return NULL;
	}
#define S(a) ((a) == NULL ? "" : (a))

	len += strlen(S(prog->proginfo_title));
	len += strlen(S(prog->proginfo_subtitle));
	len += strlen(S(prog->proginfo_description));
	len += strlen(S(prog->proginfo_category));
	len += strlen(S(prog->proginfo_chanstr));
	len += strlen(S(prog->proginfo_chansign));
	len += strlen(S(prog->proginfo_channame));
	len += strlen(S(prog->proginfo_url));
	len += strlen(S(prog->proginfo_hostname));
	len += strlen(S(prog->proginfo_playgroup));
	len += strlen(S(prog->proginfo_seriesid));
	len += strlen(S(prog->proginfo_programid));
	len += strlen(S(prog->proginfo_inetref));
	len += strlen(S(prog->proginfo_storagegroup));

	buf = malloc(len + 1 + 2048);
	if (!buf) {
		return NULL;
	}
	*buf = '\0';

	if(control->conn_version < 14)
	{
		cmyth_timestamp_to_string(start_ts, prog->proginfo_start_ts);
		cmyth_timestamp_to_string(end_ts, prog->proginfo_end_ts);
		cmyth_timestamp_to_string(rec_start_ts, prog->proginfo_rec_start_ts);
		cmyth_timestamp_to_string(rec_end_ts, prog->proginfo_rec_end_ts);
		cmyth_timestamp_to_string(originalairdate, prog->proginfo_originalairdate);
		cmyth_timestamp_to_string(lastmodified, prog->proginfo_lastmodified);
	}
	else
	{
		cmyth_datetime_to_string(start_ts, prog->proginfo_start_ts);
		cmyth_datetime_to_string(end_ts, prog->proginfo_end_ts);
		cmyth_datetime_to_string(rec_start_ts, prog->proginfo_rec_start_ts);
		cmyth_datetime_to_string(rec_end_ts, prog->proginfo_rec_end_ts);
		cmyth_datetime_to_string(originalairdate, prog->proginfo_originalairdate);
		cmyth_datetime_to_string(lastmodified, prog->proginfo_lastmodified);
	}

	if(control->conn_version > 32) {
		cmyth_timestamp_to_isostring(originalairdate, prog->proginfo_originalairdate);
	}

	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_title));
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_subtitle));
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_description));
	if (control->conn_version >= 67) {
		sprintf(buf + strlen(buf), "%"PRIu16"[]:[]", prog->proginfo_season);
		sprintf(buf + strlen(buf), "%"PRIu16"[]:[]", prog->proginfo_episode);
	}
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_category));
	sprintf(buf + strlen(buf), "%"PRIu32"[]:[]", prog->proginfo_chanId);
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_chanstr));
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_chansign));
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_channame));
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_url));
	if (control->conn_version >= 57) {
		sprintf(buf + strlen(buf), "%"PRId64"[]:[]", prog->proginfo_Length);
	} else {
		sprintf(buf + strlen(buf), "%"PRId32"[]:[]", (int32_t)(prog->proginfo_Length >> 32));
		sprintf(buf + strlen(buf), "%"PRId32"[]:[]", (int32_t)(prog->proginfo_Length & 0xffffffff));
	}
	sprintf(buf + strlen(buf), "%s[]:[]",  start_ts);
	sprintf(buf + strlen(buf), "%s[]:[]",  end_ts);
	if (control->conn_version < 57) {
		sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_unknown_0)); // "duplicate"
		sprintf(buf + strlen(buf), "%ld[]:[]", 0L); // "shareable"
	}
	sprintf(buf + strlen(buf), "%ld[]:[]", 0L); // "findid"
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_hostname));
	sprintf(buf + strlen(buf), "%"PRIu32"[]:[]", prog->proginfo_source_id);
	sprintf(buf + strlen(buf), "%"PRIu32"[]:[]", prog->proginfo_card_id);
	sprintf(buf + strlen(buf), "%"PRIu32"[]:[]", prog->proginfo_input_id);
	sprintf(buf + strlen(buf), "%"PRId8"[]:[]", prog->proginfo_rec_priority);
	sprintf(buf + strlen(buf), "%"PRId8"[]:[]", prog->proginfo_rec_status);
	sprintf(buf + strlen(buf), "%"PRIu32"[]:[]", prog->proginfo_record_id);
	sprintf(buf + strlen(buf), "%"PRIu8"[]:[]", prog->proginfo_rec_type);
	sprintf(buf + strlen(buf), "%"PRIu8"[]:[]", prog->proginfo_rec_dupin);
	sprintf(buf + strlen(buf), "%"PRIu8"[]:[]", prog->proginfo_rec_dupmethod);
	sprintf(buf + strlen(buf), "%s[]:[]",  rec_start_ts);
	sprintf(buf + strlen(buf), "%s[]:[]",  rec_end_ts);
	if (control->conn_version < 57) {
		sprintf(buf + strlen(buf), "%"PRIu8"[]:[]", prog->proginfo_repeat);
	}
	sprintf(buf + strlen(buf), "%"PRIu32"[]:[]", prog->proginfo_program_flags);
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_recgroup));
	if (control->conn_version < 57) {
		sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_chancommfree));
	}
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_chan_output_filters));
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_seriesid));
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_programid));
	if (control->conn_version >= 67) {
		sprintf(buf + strlen(buf), "%s[]:[]", S(prog->proginfo_inetref));
	}
	sprintf(buf + strlen(buf), "%s[]:[]",  lastmodified);
	sprintf(buf + strlen(buf), "%s[]:[]",  S(prog->proginfo_stars));
	sprintf(buf + strlen(buf), "%s[]:[]",  originalairdate);
	if (control->conn_version >= 15 && control->conn_version < 57) {
		sprintf(buf + strlen(buf), "%"PRIu8"[]:[]", prog->proginfo_hasairdate);
	}
	if (control->conn_version >= 18) {
		sprintf(buf + strlen(buf), "%s[]:[]", S(prog->proginfo_playgroup));
	}
	if (control->conn_version >= 25) {
		sprintf(buf + strlen(buf), "%"PRId8"[]:[]", prog->proginfo_recpriority_2);
	}
	if (control->conn_version >= 31) {
		sprintf(buf + strlen(buf), "%"PRIu32"[]:[]", prog->proginfo_parentid);
	}
	if (control->conn_version >= 32) {
		sprintf(buf + strlen(buf), "%s[]:[]", S(prog->proginfo_storagegroup));
	}
	if (control->conn_version >= 35) {
		sprintf(buf + strlen(buf), "%"PRIu16"[]:[]", prog->proginfo_audioproperties);
		sprintf(buf + strlen(buf), "%"PRIu16"[]:[]", prog->proginfo_videoproperties);
		sprintf(buf + strlen(buf), "%"PRIu16"[]:[]", prog->proginfo_subtitletype);
	}
	if (control->conn_version >= 43) {
		sprintf(buf + strlen(buf), "%"PRIu16"[]:[]", prog->proginfo_year);
	}
#undef S

	return buf;
}

/*
 * cmyth_proginfo_check_recording()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Make a request on the control connection 'control' to check the
 * existence of the program 'prog' on the MythTV back end.
 *
 * Return Value:
 *
 * Success: 1 - if the recording exists, 0 - if it does not
 *
 * Failure: -(ERRNO)
 */
int
cmyth_proginfo_check_recording(cmyth_conn_t control, cmyth_proginfo_t prog)
{
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s\n", __FUNCTION__);
	return -ENOSYS;
}

/*
 * cmyth_proginfo_delete_recording()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Make a request on the control connection 'control' to delete the
 * program 'prog' from the MythTV back end.
 *
 * Return Value:
 *
 * Success: 0
 *
 * Failure: -(ERRNO)
 */
int
cmyth_proginfo_delete_recording(cmyth_conn_t control, cmyth_proginfo_t prog)
{
	int32_t c = 0;
	int err = 0;
	int count = 0;
	int r = 0;
	int ret = 0;
	char *buf;
	char *proginfo;

	if (!control) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no connection\n",
			  __FUNCTION__);
		return -EINVAL;
	}
	if (control->conn_version < 12)
	{
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: delete not supported with protocol ver %d\n",
			  __FUNCTION__, control->conn_version);
		return -EINVAL;
	}

	proginfo = cmyth_proginfo_string(control, prog);
	if (proginfo == NULL) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: program_info failed.\n",
			  __FUNCTION__);
		return -EINVAL;
	}

	buf = malloc(strlen(proginfo) + 23 + 1);
	if (!buf) {
		free(proginfo);
		return -ENOMEM;
	}
	sprintf(buf, "DELETE_RECORDING 0[]:[]%s", proginfo);
	free(proginfo);

	pthread_mutex_lock(&control->conn_mutex);

	if ((err = cmyth_send_message(control, buf)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message() failed (%d)\n",
			  __FUNCTION__, err);
		ret = err;
		goto out;
	}

	count = cmyth_rcv_length(control);
	if ((r = cmyth_rcv_int32(control, &err, &c, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, r);
		ret = err;
		goto out;
	}

	out:
	pthread_mutex_unlock(&control->conn_mutex);
	free(buf);

	return ret;
}

/*
 * cmyth_proginfo_forget_recording()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Make a request on the control connection 'control' to tell the
 * MythTV back end to forget the program 'prog'.
 *
 * Return Value:
 *
 * Success: 0
 *
 * Failure: -(ERRNO)
 */
int
cmyth_proginfo_forget_recording(cmyth_conn_t control, cmyth_proginfo_t prog)
{
	int32_t c = 0;
	int err = 0;
	int count = 0;
	int r = 0;
	int ret = 0;
	char *buf;
	char *proginfo;

	if (!control) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no connection\n",
			  __FUNCTION__);
		return -EINVAL;
	}
	if (control->conn_version < 12)
	{
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: forget not supported with protocol ver %d\n",
			  __FUNCTION__, control->conn_version);
		return -EINVAL;
	}

	proginfo = cmyth_proginfo_string(control, prog);
	if (proginfo == NULL) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: program_info failed.\n",
			  __FUNCTION__);
		return -EINVAL;
	}

	buf = malloc(strlen(proginfo) + 23 + 1);
	if (!buf) {
		free(proginfo);
		return -ENOMEM;
	}
	sprintf(buf, "FORGET_RECORDING 0[]:[]%s", proginfo);
	free(proginfo);

	pthread_mutex_lock(&control->conn_mutex);

	if ((err = cmyth_send_message(control, buf)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message() failed (%d)\n",
			  __FUNCTION__, err);
		ret = err;
		goto out;
	}

	count = cmyth_rcv_length(control);
	if ((r = cmyth_rcv_int32(control, &err, &c, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, r);
		ret = err;
		goto out;
	}

	out:
	pthread_mutex_unlock(&control->conn_mutex);
	free(buf);

	return ret;
}

/*
 * cmyth_proginfo_stop_recording()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Make a request on the control connection 'control' to ask the
 * MythTV back end to stop recording the program described in 'prog'.
 *
 * Return Value:
 *
 * Success: 0
 *
 * Failure: -(ERRNO)
 */
int
cmyth_proginfo_stop_recording(cmyth_conn_t control, cmyth_proginfo_t prog)
{
	int32_t c = 0;
	int err = 0;
	int count = 0;
	int r = 0;
	int ret = 0;
	char *buf;
	char *proginfo;

	if (!control) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no connection\n",
			  __FUNCTION__);
		return -EINVAL;
	}
	if (control->conn_version < 12)
	{
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: stop not supported with protocol ver %d\n",
			  __FUNCTION__, control->conn_version);
		return -EINVAL;
	}

	proginfo = cmyth_proginfo_string(control, prog);
	if (proginfo == NULL) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: program_info failed.\n",
			  __FUNCTION__);
		return -EINVAL;
	}

	buf = malloc(strlen(proginfo) + 21 + 1);
	if (!buf) {
		free(proginfo);
		return -ENOMEM;
	}
	sprintf(buf, "STOP_RECORDING 0[]:[]%s", proginfo);
	free(proginfo);

	pthread_mutex_lock(&control->conn_mutex);

	if ((err = cmyth_send_message(control, buf)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message() failed (%d)\n",
			  __FUNCTION__, err);
		ret = err;
		goto out;
	}

	count = cmyth_rcv_length(control);
	if ((r = cmyth_rcv_int32(control, &err, &c, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, r);
		ret = err;
		goto out;
	}

	out:
	pthread_mutex_unlock(&control->conn_mutex);
	free(buf);

	return ret;
}

/*
 * cmyth_proginfo_get_recorder_num()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Make a request on the control connection 'control' to obtain the
 * recorder number for the program 'prog' and fill out 'rnum' with the
 * information.
 *
 * Return Value:
 *
 * Success: 0
 *
 * Failure: -(ERRNO)
 */
int
cmyth_proginfo_get_recorder_num(cmyth_conn_t control,
				cmyth_rec_num_t rnum,
				cmyth_proginfo_t prog)
{
	return -ENOSYS;
}

/*
 * cmyth_proginfo_chan_id()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Obtain the channel identifier from a program information structure.
 *
 * Return Value:
 *
 * Success: A positive integer channel identifier (these are formed from
 *          the recorder number and the channel number).
 *
 * Failure: 0
 */
uint32_t
cmyth_proginfo_chan_id(cmyth_proginfo_t prog)
{
	if (!prog) {
		return 0;
	}
	return prog->proginfo_chanId;
}

/*
 * cmyth_proginfo_title()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_title' field of a program info structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_title(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_title);
}

/*
 * cmyth_proginfo_subtitle()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_subtitle' field of a program info structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_subtitle(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_subtitle);
}

/*
 * cmyth_proginfo_description()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_description' field of a program info structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_description(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_description);
}

uint16_t
cmyth_proginfo_season(cmyth_proginfo_t prog)
{
	if (!prog) {
		return 0;
	}
	return prog->proginfo_season;
}

uint16_t
cmyth_proginfo_episode(cmyth_proginfo_t prog)
{
	if (!prog) {
		return 0;
	}
	return prog->proginfo_episode;
}

/*
 * cmyth_proginfo_category()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_category' field of a program info structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_category(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_category);
}

char *
cmyth_proginfo_seriesid(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL series ID\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_seriesid);
}

char *
cmyth_proginfo_programid(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program ID\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_programid);
}

char *
cmyth_proginfo_inetref(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL inetref\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_inetref);
}

char *
cmyth_proginfo_stars(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL stars\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_stars);
}

char *
cmyth_proginfo_playgroup(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL playgroup\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_playgroup);
}

cmyth_timestamp_t
cmyth_proginfo_originalairdate(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL original air date\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_originalairdate);
}

/*
 * cmyth_proginfo_chanstr()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_chanstr' field of a program info structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_chanstr(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_chanstr);
}

/*
 * cmyth_proginfo_chansign()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_chansign' field of a program info
 * structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_chansign(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_chansign);
}

/*
 * cmyth_proginfo_channame()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_channame' field of a program info
 * structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_channame(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_channame);
}

/*
 * cmyth_proginfo_channame()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_pathname' field of a program info
 * structure.
 *
 * The returned string is a pointer to the string within the program
 * info structure, so it should not be modified by the caller.  The
 * return value is a 'char *' for this reason.
 *
 * Return Value:
 *
 * Success: A pointer to a 'char *' pointing to the field.
 *
 * Failure: NULL
 */
char *
cmyth_proginfo_pathname(cmyth_proginfo_t prog)
{
	if (!prog) {
		return NULL;
	}
	return ref_hold(prog->proginfo_pathname);
}

/*
 * cmyth_proginfo_length()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_Length' field of a program info
 * structure.
 *
 * Return Value:
 *
 * Success: long long file length
 *
 * Failure: 0
 */
int64_t
cmyth_proginfo_length(cmyth_proginfo_t prog)
{
	if (!prog) {
		return 0;
	}
	return prog->proginfo_Length;
}

/*
 * cmyth_proginfo_length_sec()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the program length in seconds.
 *
 * Return Value:
 *
 * Success: int file length
 *
 * Failure: 0
 */
uint32_t
cmyth_proginfo_length_sec(cmyth_proginfo_t prog)
{
	int seconds;

	if (!prog) {
		return 0;
	}

	seconds = cmyth_timestamp_diff(prog->proginfo_start_ts,
				       prog->proginfo_end_ts);
	if (seconds < 0) {
		return 0;
	}
	return (uint32_t)seconds;
}
/*
 * cmyth_proginfo_start()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'start' timestamp from a program info structure.
 * This indicates a programmes start time.
 *
 * The returned timestamp is returned held.  It should be released
 * when no longer needed using ref_release().
 *
 * Return Value:
 *
 * Success: A non-NULL cmyth_timestamp_t
 *
 * Failure: NULL
 */
cmyth_timestamp_t
cmyth_proginfo_start(cmyth_proginfo_t prog)
{
	if (!prog) {
		return NULL;
	}
	return ref_hold(prog->proginfo_start_ts);
}


/*
 * cmyth_proginfo_rec_end()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'end' timestamp from a program info structure.
 * This tells when a recording started.
 *
 * The returned timestamp is returned held.  It should be released
 * when no longer needed using ref_release().
 *
 * Return Value:
 *
 * Success: A non-NULL cmyth_timestamp_t
 *
 * Failure: NULL
 */
cmyth_timestamp_t
cmyth_proginfo_end(cmyth_proginfo_t prog)
{
	if (!prog) {
		return NULL;
	}
	return ref_hold(prog->proginfo_end_ts);
}

/*
 * cmyth_proginfo_rec_start()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'rec_start' timestamp from a program info structure.
 * This tells when a recording started.
 *
 * The returned timestamp is returned held.  It should be released
 * when no longer needed using ref_release().
 *
 * Return Value:
 *
 * Success: A non-NULL cmyth_timestamp_t
 *
 * Failure: NULL
 */
cmyth_timestamp_t
cmyth_proginfo_rec_start(cmyth_proginfo_t prog)
{
	if (!prog) {
		return NULL;
	}
	return ref_hold(prog->proginfo_rec_start_ts);
}


/*
 * cmyth_proginfo_rec_end()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'rec_end' timestamp from a program info structure.
 * This tells when a recording started.
 *
 * The returned timestamp is returned held.  It should be released
 * when no longer needed using ref_release().
 *
 * Return Value:
 *
 * Success: A non-NULL cmyth_timestamp_t
 *
 * Failure: NULL
 */
cmyth_timestamp_t
cmyth_proginfo_rec_end(cmyth_proginfo_t prog)
{
	if (!prog) {
		return NULL;
	}
	return ref_hold(prog->proginfo_rec_end_ts);
}

/*
 * cmyth_proginfo_rec_status()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the recording status from a program info structure.
 * Recording status has the following possible values:
 *
 *	RS_DELETED
 *	RS_STOPPED
 *	RS_RECORDED
 *	RS_RECORDING
 *	RS_WILL_RECORD
 *	RS_DONT_RECORD
 *	RS_PREVIOUS_RECORDING
 *	RS_CURRENT_RECORDING
 *	RS_EARLIER_RECORDING
 *	RS_TOO_MANY_RECORDINGS
 *	RS_CANCELLED
 *	RS_CONFLICT
 *	RS_LATER_SHOWING
 *	RS_REPEAT
 *	RS_LOW_DISKSPACE
 *	RS_TUNER_BUSY
 *
 * Return Value:
 *
 * Success: A recording status
 *
 * Failure: 0 (an invalid status)
 */
cmyth_proginfo_rec_status_t
cmyth_proginfo_rec_status(cmyth_proginfo_t prog)
{
	if (!prog) {
		return 0;
	}
	return prog->proginfo_rec_status;
}

/*
 * cmyth_proginfo_flags()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the flags mask from a program info structure.
 *
 * Return Value:
 *
 * Success: The program flag mask.
 *
 * Failure: 0 (NULL mask)
 */
uint32_t
cmyth_proginfo_flags(cmyth_proginfo_t prog)
{
	if (!prog) {
		return 0;
	}
	return prog->proginfo_program_flags;
}

/*
 * cmyth_proginfo_year()
 *
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Retrieves the 'proginfo_year' field of a program info
 * structure.
 *
 * Return Value:
 *
 * Success: the production year for the program
 *
 * Failure: 0
 */
uint16_t
cmyth_proginfo_year(cmyth_proginfo_t prog)
{
	if (!prog) {
		return 0;
	}
	return prog->proginfo_year;
}

/*
 * cmyth_proginfo_fill()
 *
 * Scope: PRIVATE (static)
 *
 * Description
 *
 * Fill out a (possibly incomplete) program info.  Incomplete program
 * info comes from program listings.  Since this modifies the contents of
 * the supplied program info, it must never be called with a program info
 * that has more than one reference).
 *
 * Return Value:
 *
 * Success: 0
 *
 * Failure: a negative error code.
 */
static int
cmyth_proginfo_fill(cmyth_conn_t control, cmyth_proginfo_t prog)
{
	int err = 0;
	int count = 0;
	int ret = 0;
	char *buf;
	char *proginfo;
	int64_t length = 0;

	if (!control) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no connection\n",
			  __FUNCTION__);
		return -EINVAL;
	}
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no program info\n",
			  __FUNCTION__);
		return -EINVAL;
	}

	proginfo = cmyth_proginfo_string(control, prog);
	if (proginfo == NULL) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: program_info failed.\n",
			  __FUNCTION__);
		return -EINVAL;
	}

	buf = malloc(strlen(proginfo) + 34 + 1);
	if (!buf) {
		free(proginfo);
		return -ENOMEM;
	}
	sprintf(buf, "FILL_PROGRAM_INFO cmyth[]:[]0[]:[]%s", proginfo);
	free(proginfo);

	pthread_mutex_lock(&control->conn_mutex);

	length = prog->proginfo_Length;

	if ((err = cmyth_send_message(control, buf)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message() failed (%d)\n",
			  __FUNCTION__, err);
		ret = err;
		goto out;
	}

	count = cmyth_rcv_length(control);
	if (count < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, count);
		ret = count;
		goto out;
	}
	if (cmyth_rcv_proginfo(control, &err, prog, count) != count) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_proginfo() < count\n", __FUNCTION__);
		ret = err;
		goto out;
	}

	/*
	 * Myth seems to cache the program length, rather than call stat()
	 * every time it needs to know.  Using FILL_PROGRAM_INFO has worked
	 * to force mythbackend to call stat() and return the correct length.
	 *
	 * However, some users are reporting that FILL_PROGRAM_INFO is
	 * returning 0 for the program length.  In that case, the original
	 * number is still probably wrong, but it's better than 0.
	 */
	if (prog->proginfo_Length == 0) {
		prog->proginfo_Length = length;
	}

	out:
	pthread_mutex_unlock(&control->conn_mutex);
	free(buf);

	return ret;
}

/*
 * cmyth_proginfo_get_detail()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Completely fill out a program info based on a supplied (possibly
 * incomplete) program info.  The supplied program info will be duplicated and
 * a pointer to the duplicate will be returned.
 *
 * NOTE: The original program info is released before the return.  If the
 *       caller wishes to retain access to the original it must already be
 *       held before the call.  This permits the called to replace a
 *       program info directly with the return from this function.
 *
 * Return Value:
 *
 * Success: A held, Non-NULL program_info
 *
 * Failure: NULL
 */
cmyth_proginfo_t
cmyth_proginfo_get_detail(cmyth_conn_t control, cmyth_proginfo_t p)
{
	cmyth_proginfo_t ret = cmyth_proginfo_dup(p);
	ref_release(p);

	if (ret == NULL) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_proginfo_dup() failed\n",
			  __FUNCTION__);
		return NULL;
	}
	if (cmyth_proginfo_fill(control, ret) < 0) {
		ref_release(ret);
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_proginfo_fill() failed\n",
			  __FUNCTION__);
		return NULL;
	}
	return ret;
}

/*
 * cmyth_proginfo_compare()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Compare two program info's and indicate whether they are the same program.
 *
 * Return Value:
 *
 * Same: 0
 *
 * Different: -1
 */
int
cmyth_proginfo_compare(cmyth_proginfo_t a, cmyth_proginfo_t b)
{
	if (a == b)
		return 0;

	if ((a == NULL) || (b == NULL))
		return -1;

#define STRCMP(a, b) ( (a && b && (strcmp(a,b) == 0)) ? 0 : \
		       ((a == NULL) && (b == NULL) ? 0 : -1) )

	if (STRCMP(a->proginfo_title, b->proginfo_title) != 0)
		return -1;
	if (STRCMP(a->proginfo_subtitle, b->proginfo_subtitle) != 0)
		return -1;
	if (STRCMP(a->proginfo_description, b->proginfo_description) != 0)
		return -1;
	if (STRCMP(a->proginfo_chanstr, b->proginfo_chanstr) != 0)
		return -1;

	if (a->proginfo_url && b->proginfo_url) {
          char* aa = strrchr(a->proginfo_url, '/');
          char* bb = strrchr(b->proginfo_url, '/');
          if (strcmp(aa ? aa+1 : a->proginfo_url, bb ? bb+1 : b->proginfo_url) != 0)
		return -1;
	} else if(!a->proginfo_url != !b->proginfo_url)
		return -1;

	if (cmyth_timestamp_compare(a->proginfo_start_ts,
				    b->proginfo_start_ts) != 0)
		return -1;
	if (cmyth_timestamp_compare(a->proginfo_end_ts,
				    b->proginfo_end_ts) != 0)
		return -1;

	return 0;
}

/*
 * cmyth_proginfo_host()
 *
 * Scope: PUBLIC
 *
 * Description
 *
 * Get the host name of the recorder serving the program 'prog'.
 *
 * Return Value:
 *
 * Success: A held, non-NULL string pointer
 *
 * Failure: NULL
 */
char*
cmyth_proginfo_host(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: no program info\n", __FUNCTION__);
		return NULL;
	}

	return ref_hold(prog->proginfo_host);
}

uint16_t
cmyth_proginfo_port(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: no program info\n", __FUNCTION__);
		return 0;
	}

	return prog->proginfo_port;
}

uint32_t
cmyth_proginfo_card_id(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: no program info\n", __FUNCTION__);
		return 0;
	}

	return prog->proginfo_card_id;
}

uint32_t
cmyth_proginfo_recordid(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: no program info\n", __FUNCTION__);
		return 0;
	}

	return prog->proginfo_record_id;
}

int8_t
cmyth_proginfo_priority(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: no program info\n", __FUNCTION__);
		return 0;
	}

	return prog->proginfo_rec_priority;
}

char *
cmyth_proginfo_recgroup(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_recgroup);
}

char *
cmyth_proginfo_chanicon(cmyth_proginfo_t prog)
{
	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL program information\n",
			  __FUNCTION__);
		return NULL;
	}
	return ref_hold(prog->proginfo_chanicon);
}

int
cmyth_get_delete_list(cmyth_conn_t conn, char * msg, cmyth_proglist_t prog)
{
        int err=0;
        int count;
        int prog_count=0;

        if (!conn) {
                cmyth_dbg(CMYTH_DBG_ERROR, "%s: no connection\n", __FUNCTION__);
                return -1;
        }
        pthread_mutex_lock(&conn->conn_mutex);
        if ((err = cmyth_send_message(conn, msg)) < 0) {
                fprintf (stderr, "ERROR %d \n",err);
                cmyth_dbg(CMYTH_DBG_ERROR,
                        "%s: cmyth_send_message() failed (%d)\n",__FUNCTION__,err);
                return err;
        }
        count = cmyth_rcv_length(conn);
        cmyth_rcv_proglist(conn, &err, prog, count);
        prog_count=cmyth_proglist_get_count(prog);
        pthread_mutex_unlock(&conn->conn_mutex);
        return prog_count;
}

cmyth_proginfo_t
cmyth_proginfo_get_from_basename(cmyth_conn_t control, const char* basename)
{
	int err = 0;
	int count, i;
	char msg[4096];
	char *base;
	cmyth_proginfo_t prog = NULL;
	cmyth_proglist_t list = NULL;

	if (!control) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no connection\n",
			  __FUNCTION__);
		return NULL;
	}

	/*
	 * mythbackend doesn't support spaces in basenames
	 * when doing QUERY_RECORDING.  If there are spaces, fallback
	 * to enumerating all recordings
	 */
	if(control->conn_version >= 32 && strchr(basename, ' ') == NULL) {
		pthread_mutex_lock(&control->conn_mutex);

		snprintf(msg, sizeof(msg), "QUERY_RECORDING BASENAME %s",
			 basename);

		if ((err=cmyth_send_message(control, msg)) < 0) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_send_message() failed (%d)\n",
			  	__FUNCTION__, err);
			goto out;
		}

		count = cmyth_rcv_length(control);
		if (count < 0) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_rcv_length() failed (%d)\n",
				  __FUNCTION__, count);
			goto out;
		}

		i = cmyth_rcv_string(control, &err, msg, sizeof(msg), count);
		if (err) {
			cmyth_dbg(CMYTH_DBG_ERROR, "%s: cmyth_rcv_string() failed\n",
				  __FUNCTION__);
			goto out;
		}
		count -= i;

		if (strcmp(msg, "OK") != 0) {
			cmyth_dbg(CMYTH_DBG_ERROR, "%s: didn't recieve OK as response\n",
				  __FUNCTION__);
			goto out;
		}

		prog = cmyth_proginfo_create();
		if (cmyth_rcv_proginfo(control, &err, prog, count) != count) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_rcv_proginfo() < count\n", __FUNCTION__);
			goto out;
		}

		pthread_mutex_unlock(&control->conn_mutex);
		return prog;
		out:
		pthread_mutex_unlock(&control->conn_mutex);
		if(prog)
			ref_release(prog);
		return NULL;

	} else {

		list = cmyth_proglist_get_all_recorded(control);
		if (!list) {
			cmyth_dbg(CMYTH_DBG_ERROR, "%s: no program list\n",
				  __FUNCTION__);
		}

		count = cmyth_proglist_get_count(list);
		for (i = 0;i < count; i++) {
			prog = cmyth_proglist_get_item(list, i);
			if (!prog) {
				cmyth_dbg(CMYTH_DBG_DEBUG, "%s: no program info\n",
					  __FUNCTION__);
				continue;
			}
			base = strrchr(prog->proginfo_pathname, '/');
			if (!base || strcmp(base+1, basename) !=0) {
				ref_release(prog);
				prog = NULL;
				continue;
			}
			break;
		}
		ref_release(list);
		return prog;
	}

}

cmyth_proginfo_t
cmyth_proginfo_get_from_timeslot(cmyth_conn_t control, uint32_t chanid, const cmyth_timestamp_t recstartts)
{
	int err = 0;
	int count, i;
	char msg[4096];
	cmyth_proginfo_t prog = NULL;
	cmyth_proglist_t list = NULL;
	char time[15];

	if (!control) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no connection\n",
			  __FUNCTION__);
		return NULL;
	}

	if ((err = cmyth_timestamp_to_numstring(time, recstartts)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: cmyth_timestamp_to_numstring() failed (%d)\n",
			  __FUNCTION__, err);
		return NULL;
	}

	if(control->conn_version >= 32) {
		pthread_mutex_lock(&control->conn_mutex);

		snprintf(msg, sizeof(msg), "QUERY_RECORDING TIMESLOT %"PRIu32" %s",
			chanid, time);

		if ((err=cmyth_send_message(control, msg)) < 0) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_send_message() failed (%d)\n",
				  __FUNCTION__, err);
			goto out;
		}

		count = cmyth_rcv_length(control);
		if (count < 0) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_rcv_length() failed (%d)\n",
				  __FUNCTION__, count);
			goto out;
		}

		i = cmyth_rcv_string(control, &err, msg, sizeof(msg), count);
		if (err) {
			cmyth_dbg(CMYTH_DBG_ERROR, "%s: cmyth_rcv_string() failed\n",
				  __FUNCTION__);
			goto out;
		}
		count -= i;

		if (strcmp(msg, "OK") != 0) {
			cmyth_dbg(CMYTH_DBG_ERROR, "%s: didn't recieve OK as response\n",
				  __FUNCTION__);
			goto out;
		}

		prog = cmyth_proginfo_create();
		if (cmyth_rcv_proginfo(control, &err, prog, count) != count) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_rcv_proginfo() < count\n", __FUNCTION__);
			goto out;
		}

		pthread_mutex_unlock(&control->conn_mutex);
		return prog;
		out:
		pthread_mutex_unlock(&control->conn_mutex);
		if(prog)
			ref_release(prog);
		return NULL;

	} else {

		list = cmyth_proglist_get_all_recorded(control);
		if (!list) {
			cmyth_dbg(CMYTH_DBG_ERROR, "%s: no program list\n",
				  __FUNCTION__);
		}

		count = cmyth_proglist_get_count(list);
		for (i = 0;i < count; i++) {
			prog = cmyth_proglist_get_item(list, i);
			if (!prog) {
				cmyth_dbg(CMYTH_DBG_DEBUG, "%s: no program info\n",
					  __FUNCTION__);
				continue;
			}
			if (cmyth_timestamp_compare(prog->proginfo_rec_start_ts, recstartts) != 0 ||
					prog->proginfo_chanId != chanid) {
				ref_release(prog);
				prog = NULL;
				continue;
			}
			break;
		}
		ref_release(list);
		return prog;
	}

}
