Exemple #1
0
Fichier : file.c Projet : tsp/cmyth
/*
 * cmyth_file_destroy(cmyth_file_t file)
 * 
 * Scope: PRIVATE (static)
 *
 * Description
 *
 * Tear down and release storage associated with a file connection.
 * This should only be called by ref_release().  All others
 * should call ref_release() to release a file connection.
 *
 * Return Value:
 *
 * None.
 */
static void
cmyth_file_destroy(cmyth_file_t file)
{
	int err, count;
	int r;
	long c;
	char msg[256];

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s {\n", __FUNCTION__);
	if (!file) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!\n", __FUNCTION__);
		return;
	}
	if (file->file_control) {
		pthread_mutex_lock(&mutex);

		/*
		 * Try to shut down the file transfer.  Can't do much
		 * if it fails other than log it.
		 */
		snprintf(msg, sizeof(msg),
			 "QUERY_FILETRANSFER %ld[]:[]DONE", file->file_id);

		if ((err = cmyth_send_message(file->file_control, msg)) < 0) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_send_message() failed (%d)\n",
				  __FUNCTION__, err);
			goto fail;
		}

		if ((count = cmyth_rcv_length(file->file_control)) < 0) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_rcv_length() failed (%d)\n",
				  __FUNCTION__, count);
			err = count;
			goto fail;
		}
		if ((r = cmyth_rcv_long(file->file_control,
					&err, &c, count)) < 0) {
			cmyth_dbg(CMYTH_DBG_ERROR,
				  "%s: cmyth_rcv_long() failed (%d)\n",
				  __FUNCTION__, r);
			goto fail;
		}
	    fail:
		ref_release(file->file_control);
		pthread_mutex_unlock(&mutex);
	}
	if (file->closed_callback) {
	    (file->closed_callback)(file);
	}
	if (file->file_data) {
		ref_release(file->file_data);
	}

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s }\n", __FUNCTION__);
}
Exemple #2
0
Fichier : file.c Projet : tsp/cmyth
/*
 * cmyth_file_request_block(cmyth_file_t file, unsigned long len)
 * 
 * Scope: PUBLIC
 *
 * Description
 *
 * Request a file data block of a certain size, and return when the
 * block has been transfered.
 *
 * Return Value:
 *
 * Sucess: number of bytes transfered
 *
 * Failure: an int containing -errno
 */
int
cmyth_file_request_block(cmyth_file_t file, unsigned long len)
{
	int err, count;
	int r;
	long c, ret;
	char msg[256];

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

	pthread_mutex_lock(&mutex);

#ifdef LIBCMYTH_READ_SINGLE_THREAD
	if(len > (unsigned int)file->file_control->conn_tcp_rcvbuf)
		len = (unsigned int)file->file_control->conn_tcp_rcvbuf;
#endif

	snprintf(msg, sizeof(msg),
		 "QUERY_FILETRANSFER %ld[]:[]REQUEST_BLOCK[]:[]%ld",
		 file->file_id, len);

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

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

	file->file_pos += c;
	ret = c;

    out:
	pthread_mutex_unlock(&mutex);

	return ret;
}
Exemple #3
0
/*
 * cmyth_ringbuf_request_block(cmyth_ringbuf_t file, unsigned long len)
 * 
 * Scope: PUBLIC
 *
 * Description
 *
 * Request a file data block of a certain size, and return when the
 * block has been transfered.
 *
 * Return Value:
 *
 * Sucess: number of bytes transfered
 *
 * Failure: an int containing -errno
 */
int
cmyth_ringbuf_request_block(cmyth_recorder_t rec, unsigned long len)
{
	int err, count;
	int r;
	long c, ret;
	char msg[256];

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

	pthread_mutex_lock(&mutex);

#ifdef LIBCMYTH_READ_SINGLE_THREAD
	if(len > (unsigned int)rec->rec_conn->conn_tcp_rcvbuf)
		len = (unsigned int)rec->rec_conn->conn_tcp_rcvbuf;
#endif

	snprintf(msg, sizeof(msg),
		 "QUERY_RECORDER %u[]:[]REQUEST_BLOCK_RINGBUF[]:[]%ld",
		 rec->rec_id, len);

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

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

	rec->rec_ring->file_pos += c;
	ret = c;

    out:
	pthread_mutex_unlock(&mutex);

	return ret;
}
Exemple #4
0
int
cmyth_conn_get_free_recorder_count(cmyth_conn_t conn)
{
	char msg[256];
	int count, err;
	long c, r;
	int ret;

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

	pthread_mutex_lock(&mutex);

	snprintf(msg, sizeof(msg), "GET_FREE_RECORDER_COUNT");
	if ((err = cmyth_send_message(conn, msg)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message() failed (%d)\n",
			  __FUNCTION__, err);
		ret = err;
		goto err;
	}

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

	ret = c;

    err:
	pthread_mutex_unlock(&mutex);

	return ret;
}
Exemple #5
0
/*
 * cmyth_recorder_is_recording(cmyth_recorder_t rec)
 * 
 * Scope: PUBLIC
 *
 * Description
 *
 * Determine whether recorder 'rec' is currently recording.  Return
 * the true / false answer.
 *
 * Return Value:
 *
 * Success: 0 if the recorder is idle, 1 if the recorder is recording.
 *
 * Failure: -(ERRNO)
 */
int
cmyth_recorder_is_recording(cmyth_recorder_t rec)
{
	int err, count;
	int r;
	long c, ret;
	char msg[256];

	if (!rec) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: no recorder connection\n",
			  __FUNCTION__);
		return -EINVAL;
	}

	pthread_mutex_lock(&mutex);

	snprintf(msg, sizeof(msg), "QUERY_RECORDER %u[]:[]IS_RECORDING",
		 rec->rec_id);

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

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

	ret = c;

    out:
	pthread_mutex_unlock(&mutex);

	return ret;
}
Exemple #6
0
int cmyth_rcv_commbreaklist(cmyth_conn_t conn, int *err, 
			cmyth_commbreaklist_t breaklist, int count)
{
	int consumed;
	int total = 0;
	long rows;
	char *failed = NULL;
	cmyth_commbreak_t commbreak;
	unsigned short type;
	int i;
	int j;

	if (count <= 0) {
		*err = EINVAL;
		return 0;
	}

	/*
	 * Get number of rows
	 */
	consumed = cmyth_rcv_long(conn, err, &rows, count);
	count -= consumed;
	total += consumed;
	if (*err) {
		failed = "cmyth_rcv_long";
		goto fail;
	}

	if (rows < 0) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s: no commercial breaks found.\n",
			__FUNCTION__);
		return 0;
	} else {
	/*
		 * Don't check for an uneven row count. mythcommflag can mark the start of the last 
		 * commercial break, but then not mark the end before it reaches the end of the file.
		 * For this case the last commercial break is ignored.
	 */
		breaklist->commbreak_count = rows / 2;
	}

	breaklist->commbreak_list = malloc(breaklist->commbreak_count * 
					sizeof(cmyth_commbreak_t));
	if (!breaklist->commbreak_list) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: malloc() failed for list\n",
			__FUNCTION__);
		*err = ENOMEM;
		return consumed;
	}
	memset(breaklist->commbreak_list, 0, breaklist->commbreak_count * sizeof(cmyth_commbreak_t));

	for (i = 0; i < breaklist->commbreak_count; i++) {
		commbreak = cmyth_commbreak_create();

		for (j = 0; j < 2; j++) {
			consumed = cmyth_rcv_ushort(conn, err, &type, count);
			count -= consumed;
			total += consumed;
			if (*err) {
				failed = "cmyth_rcv_ushort";
				goto fail;
			}
			/*
			 * Do a little sanity-checking.
			 */
			if (j == 0 && type != CMYTH_COMMBREAK_START) {
				cmyth_dbg(CMYTH_DBG_ERROR,
					"%s: type was not CMYTH_COMMBREAK_START\n",
					__FUNCTION__);
				return 0;
			} else if (j == 1 && type != CMYTH_COMMBREAK_END) {
				cmyth_dbg(CMYTH_DBG_ERROR,
					"%s: type was not CMYTH_COMMBREAK_END\n",
					__FUNCTION__);
				return 0;
			}

			/*
			 * Only marks are returned, not the offsets. Marks are encoded in long_long.
			 */
			if (j == 0) {
				consumed = cmyth_rcv_long_long(conn, err, &commbreak->start_mark, count);
			} else {
				consumed = cmyth_rcv_long_long(conn, err, &commbreak->end_mark, count);
			}	

			count -= consumed;
			total += consumed;
			if (*err) {
				failed = "cmyth_rcv_long";
				goto fail;
			}

		}

		breaklist->commbreak_list[i] = commbreak;
	}

	return total;

    fail:
	cmyth_dbg(CMYTH_DBG_ERROR, "%s: %s() failed (%d)\n",
		__FUNCTION__, failed, *err);
	return total;
}
Exemple #7
0
int cmyth_rcv_commbreaklist(cmyth_conn_t conn, int *err, 
			cmyth_commbreaklist_t breaklist, int count)
{
	int consumed;
	int total = 0;
	long rows;
	int64_t mark;
	long long start = -1;
	char *failed = NULL;
	cmyth_commbreak_t commbreak;
	unsigned short type;
	unsigned short start_type;
	int i;

	if (count <= 0) {
		*err = EINVAL;
		return 0;
	}

	/*
	 * Get number of rows
	 */
	consumed = cmyth_rcv_long(conn, err, &rows, count);
	count -= consumed;
	total += consumed;
	if (*err) {
		failed = "cmyth_rcv_long";
		goto fail;
	}

	if (rows < 0) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s: no commercial breaks found.\n",
			__FUNCTION__);
		return 0;
	}

	for (i = 0; i < rows; i++) {
		consumed = cmyth_rcv_ushort(conn, err, &type, count);
		count -= consumed;
		total += consumed;
		if (*err) {
			failed = "cmyth_rcv_ushort";
			goto fail;
		}

		consumed = cmyth_rcv_int64(conn, err, &mark, count);
		count -= consumed;
		total += consumed;
		if (*err) {
			failed = "cmyth_rcv_long long";
			goto fail;
		}
		if (type == CMYTH_COMMBREAK_START || type == CMYTH_CUTLIST_START) {
			start = mark;
			start_type = type;
		} else if (type == CMYTH_COMMBREAK_END || type == CMYTH_CUTLIST_END) {
			if (start >= 0 &&
			    ((type == CMYTH_COMMBREAK_END && start_type == CMYTH_COMMBREAK_START)
			     || (type == CMYTH_CUTLIST_END && start_type == CMYTH_CUTLIST_START)))
			{
				commbreak = cmyth_commbreak_create();
				commbreak->start_mark = start;
				commbreak->end_mark = mark;
				start = -1;
				breaklist->commbreak_list = realloc(breaklist->commbreak_list,
					(++breaklist->commbreak_count) * sizeof(cmyth_commbreak_t));
				breaklist->commbreak_list[breaklist->commbreak_count - 1] = commbreak;
			} else {
				cmyth_dbg(CMYTH_DBG_WARN,
					"%s: ignoring 'end' marker without a 'start' marker at %lld\n",
					__FUNCTION__, type, mark);
			}
		} else {
				cmyth_dbg(CMYTH_DBG_WARN,
					"%s: type (%d) is not a COMMBREAK or CUTLIST\n",
					__FUNCTION__, type);
		}
	}

	/*
	 * If the last entry is a start marker then it doesn't have an associated end marker. In this
	 * case we choose to simply ignore it. Another option is to put in a really large fake end marker
	 * but that may cause strange seek behaviour in a client application.
	 */

	return total;

	fail:
	cmyth_dbg(CMYTH_DBG_ERROR, "%s: %s() failed (%d)\n",
		__FUNCTION__, failed, *err);
	return total;
}
Exemple #8
0
/*
 * cmyth_conn_get_recorder_from_num(cmyth_conn_t control,
 *                                  cmyth_recorder_num_t num,
 *                                  cmyth_recorder_t rec)
 * 
 * Scope: PUBLIC
 *
 * Description
 *
 * Obtain a recorder from a connection by its recorder number.  The
 * recorder structure created by this describes how to set up a data
 * connection and play media streamed from a particular back-end recorder.
 *
 * This fills out the recorder structure specified by 'rec'.
 *
 * Return Value:
 *
 * Success: 0 for not complete, 1 for complete
 *
 * Failure: -(errno)
 */
cmyth_recorder_t
cmyth_conn_get_recorder_from_num(cmyth_conn_t conn, int id)
{
	int err, count;
	int r;
	long port;
	char msg[256];
	char reply[256];
	cmyth_recorder_t rec = NULL;

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

	pthread_mutex_lock(&mutex);

	if ((rec=cmyth_recorder_create()) == NULL)
		goto fail;

	snprintf(msg, sizeof(msg), "GET_RECORDER_FROM_NUM[]:[]%d", id);

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

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

	if ((r=cmyth_rcv_string(conn, &err,
				reply, sizeof(reply)-1, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_string() failed (%d)\n",
			  __FUNCTION__, r);
		goto fail;
	}
	count -= r;

	if ((r=cmyth_rcv_long(conn, &err, &port, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_long() failed (%d)\n",
			  __FUNCTION__, r);
		goto fail;
	}

	if (port == -1)
		goto fail;

	rec->rec_id = id;
	rec->rec_server = ref_strdup(reply);
	rec->rec_port = port;

	if (cmyth_conn_connect_recorder(rec, conn->conn_buflen,
					conn->conn_tcp_rcvbuf) < 0)
		goto fail;

	pthread_mutex_unlock(&mutex);

	return rec;

    fail:
	if (rec)
		ref_release(rec);

	pthread_mutex_unlock(&mutex);

	return NULL;
}
Exemple #9
0
/*
 * cmyth_conn_connect_path(char* path, cmyth_conn_t control,
 *                         unsigned buflen, int tcp_rcvbuf)
 *
 * Scope: PUBLIC
 *
 * Description:
 *
 * Create a file structure containing a data connection for use
 * transfering a file within the MythTV protocol.  Return a pointer to
 * the newly created file structure.  The connection in the file
 * structure is returned held as is the file structure itself.  The
 * connection will be released when the file structure is released.
 * The file structure can be released using ref_release().
 *
 * Return Value:
 *
 * Success: Non-NULL cmyth_file_t (this is a pointer type)
 *
 * Failure: NULL cmyth_file_t
 */
cmyth_file_t
cmyth_conn_connect_path(char* path, cmyth_conn_t control,
			unsigned buflen, int tcp_rcvbuf)
{
	cmyth_conn_t conn = NULL;
	char *announcement = NULL;
	char reply[16];
	char host[256];
	int err = 0;
	int count = 0;
	int r, port;
	int ann_size = sizeof("ANN FileTransfer []:[][]:[]");
	struct sockaddr_in addr;
        socklen_t addr_size = sizeof(addr);
	cmyth_file_t ret = NULL;

	if (getpeername(control->conn_fd, (struct sockaddr*)&addr, &addr_size)<0) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: getpeername() failed\n",
			  __FUNCTION__);
		goto shut;
	}

	inet_ntop(addr.sin_family, &addr.sin_addr, host, sizeof(host));
	port = ntohs(addr.sin_port);

	ret = cmyth_file_create(control);
	if (!ret) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: cmyth_file_create() failed\n",
			  __FUNCTION__);
		goto shut;
	}

	cmyth_dbg(CMYTH_DBG_PROTO, "%s: connecting data connection\n",
		  __FUNCTION__);
	conn = cmyth_connect(host, port, buflen, tcp_rcvbuf);
	cmyth_dbg(CMYTH_DBG_PROTO,
		  "%s: done connecting data connection, conn = %p\n",
		  __FUNCTION__, conn);
	if (!conn) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_connect(%s, %d, %d) failed\n",
			  __FUNCTION__, host, port, buflen);
		goto shut;
	}
	ann_size += strlen(path) + strlen(my_hostname);
	announcement = malloc(ann_size);
	if (!announcement) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: malloc(%d) failed for announcement\n",
			  __FUNCTION__, ann_size);
		goto shut;
	}
	if (control->conn_version >= 44) {
		sprintf(announcement, "ANN FileTransfer %s[]:[]%s[]:[]",
			  my_hostname, path);
	}
	else {
		sprintf(announcement, "ANN FileTransfer %s[]:[]%s",
			  my_hostname, path);
	}
	if (cmyth_send_message(conn, announcement) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message('%s') failed\n",
			  __FUNCTION__, announcement);
		goto shut;
	}
	ret->file_data = ref_hold(conn);
	count = cmyth_rcv_length(conn);
	if (count < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, count);
		goto shut;
	}
	reply[sizeof(reply) - 1] = '\0';
	r = cmyth_rcv_string(conn, &err, reply, sizeof(reply) - 1, count); 
	if (err != 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_string() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	if (strcmp(reply, "OK") != 0) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: reply ('%s') is not 'OK'\n",
			  __FUNCTION__, reply);
		goto shut;
	}
	count -= r;
	r = cmyth_rcv_long(conn, &err, &ret->file_id, count);
	if (err) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: (id) cmyth_rcv_long() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	count -= r;
	r = cmyth_rcv_u_long_long(conn, &err, &ret->file_length, count);
	if (err) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: (length) cmyth_rcv_longlong() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	count -= r;
	free(announcement);
	ref_release(conn);
	return ret;

    shut:
	if (announcement) {
		free(announcement);
	}
	ref_release(ret);
	ref_release(conn);
	return NULL;
}
Exemple #10
0
/*
 * cmyth_conn_connect_file(char *server, unsigned short port, unsigned buflen
 *                         cmyth_proginfo_t prog)
 *
 * Scope: PUBLIC
 *
 * Description:
 *
 * Create a file structure containing a data connection for use
 * transfering a file within the MythTV protocol.  Return a pointer to
 * the newly created file structure.  The connection in the file
 * structure is returned held as is the file structure itself.  The
 * connection will be released when the file structure is released.
 * The file structure can be released using ref_release().
 *
 * Return Value:
 *
 * Success: Non-NULL cmyth_file_t (this is a pointer type)
 *
 * Failure: NULL cmyth_file_t
 */
cmyth_file_t
cmyth_conn_connect_file(cmyth_proginfo_t prog,  cmyth_conn_t control,
			unsigned buflen, int tcp_rcvbuf)
{
	cmyth_conn_t conn = NULL;
	char *announcement = NULL;
	char *myth_host = NULL;
	char reply[16];
	int err = 0;
	int count = 0;
	int r;
	int ann_size = sizeof("ANN FileTransfer []:[][]:[]");
	cmyth_file_t ret = NULL;

	if (!prog) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: prog is NULL\n", __FUNCTION__);
		goto shut;
	}
	if (!prog->proginfo_host) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: prog host is NULL\n",
			  __FUNCTION__);
		goto shut;
	}
	if (!prog->proginfo_pathname) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: prog has no pathname in it\n",
			  __FUNCTION__);
		goto shut;
	}
	ret = cmyth_file_create(control);
	if (!ret) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: cmyth_file_create() failed\n",
			  __FUNCTION__);
		goto shut;
	}
	cmyth_dbg(CMYTH_DBG_PROTO, "%s: connecting data connection\n",
		  __FUNCTION__);
	if (control->conn_version >= 17) {
		myth_host = cmyth_conn_get_setting(control, prog->proginfo_host,
		                                   "BackendServerIP");
	}
	if (!myth_host) {
		cmyth_dbg(CMYTH_DBG_PROTO,
		          "%s: BackendServerIP setting not found. Using proginfo_host: %s\n",
		          __FUNCTION__, prog->proginfo_host);
		myth_host = ref_alloc(strlen(prog->proginfo_host) + 1);
		strcpy(myth_host, prog->proginfo_host);
	}
	conn = cmyth_connect(myth_host, prog->proginfo_port,
			     buflen, tcp_rcvbuf);
	cmyth_dbg(CMYTH_DBG_PROTO,
		  "%s: done connecting data connection, conn = %d\n",
		  __FUNCTION__, conn);
	if (!conn) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_connect(%s, %d, %d) failed\n",
			  __FUNCTION__,
			  myth_host, prog->proginfo_port, buflen);
		goto shut;
	}
	ann_size += strlen(prog->proginfo_pathname) + strlen(my_hostname);
	announcement = malloc(ann_size);
	if (!announcement) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: malloc(%d) failed for announcement\n",
			  __FUNCTION__, ann_size);
		goto shut;
	}
	if (control->conn_version >= 44) {
		sprintf(announcement, "ANN FileTransfer %s[]:[]%s[]:[]",
			  my_hostname, prog->proginfo_pathname);
	}
	else {
		sprintf(announcement, "ANN FileTransfer %s[]:[]%s",
			  my_hostname, prog->proginfo_pathname);
	}

	if (cmyth_send_message(conn, announcement) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message('%s') failed\n",
			  __FUNCTION__, announcement);
		goto shut;
	}
	ret->file_data = ref_hold(conn);
	count = cmyth_rcv_length(conn);
	if (count < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, count);
		goto shut;
	}
	reply[sizeof(reply) - 1] = '\0';
	r = cmyth_rcv_string(conn, &err, reply, sizeof(reply) - 1, count); 
	if (err != 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_string() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	if (strcmp(reply, "OK") != 0) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: reply ('%s') is not 'OK'\n",
			  __FUNCTION__, reply);
		goto shut;
	}
	count -= r;
	r = cmyth_rcv_long(conn, &err, &ret->file_id, count);
	if (err) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: (id) cmyth_rcv_long() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	count -= r;
	r = cmyth_rcv_u_long_long(conn, &err, &ret->file_length, count);
	if (err) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: (length) cmyth_rcv_longlong() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	count -= r;
	free(announcement);
	ref_release(conn);
	ref_release(myth_host);
	return ret;

    shut:
	if (announcement) {
		free(announcement);
	}
	ref_release(ret);
	ref_release(conn);
	ref_release(myth_host);
	return NULL;
}
Exemple #11
0
static int
proginfo_command(cmyth_conn_t control, cmyth_proginfo_t prog, char *cmd,
		 long *result)
{
	long c = 0;
	char *buf;
	unsigned int len = ((2 * CMYTH_LONGLONG_LEN) + 
			    (6 * CMYTH_TIMESTAMP_LEN) +
			    (16 * CMYTH_LONG_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];
	int err = 0;
	int count = 0;
	long r = 0;
	int ret = 0;

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

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

	buf = alloca(len + 1+2048);
	if (!buf) {
		return -ENOMEM;
	}

	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);
	}

	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;
	}
	sprintf(buf, "%s 0[]:[]", cmd);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_title);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_subtitle);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_description);
	if (control->conn_version >= 67) {
		sprintf(buf + strlen(buf), "%u[]:[]", prog->proginfo_season);
		sprintf(buf + strlen(buf), "%u[]:[]", prog->proginfo_episode);
	}
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_category);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_chanId);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_chanstr);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_chansign);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_channame);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_url);
	if (control->conn_version >= 57) {
		sprintf(buf + strlen(buf), "%"PRId64"[]:[]", prog->proginfo_Length);
	} else {
		sprintf(buf + strlen(buf), "%d[]:[]", (int32_t)(prog->proginfo_Length >> 32));
		sprintf(buf + strlen(buf), "%d[]:[]", (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[]:[]", prog->proginfo_unknown_0); // "duplicate"
		sprintf(buf + strlen(buf), "%ld[]:[]", 0L); // "shareable"
	}
	sprintf(buf + strlen(buf), "%ld[]:[]", 0L); // "findid"
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_hostname);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_source_id);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_card_id);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_input_id);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_rec_priority);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_rec_status);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_record_id);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_rec_type);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_rec_dups);
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_unknown_1); // "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), "%ld[]:[]", prog->proginfo_repeat);
	}
	sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_program_flags);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_recgroup);
	if (control->conn_version < 57) {
		sprintf(buf + strlen(buf), "%s[]:[]", prog->proginfo_chancommfree);
	}
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_chan_output_filters);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_seriesid);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_programid);
	if (control->conn_version >= 67) {
		sprintf(buf + strlen(buf), "%s[]:[]", prog->proginfo_inetref);
	}
	sprintf(buf + strlen(buf), "%s[]:[]",  lastmodified);
	sprintf(buf + strlen(buf), "%s[]:[]",  prog->proginfo_stars);
	sprintf(buf + strlen(buf), "%s[]:[]",  originalairdate);
	if (control->conn_version >= 15 && control->conn_version < 57) {
		sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_hasairdate);
	}
	if (control->conn_version >= 18) {
		sprintf(buf + strlen(buf), "%s[]:[]", prog->proginfo_playgroup);
	}
	if (control->conn_version >= 25) {
		sprintf(buf + strlen(buf), "%s[]:[]", prog->proginfo_recpriority_2);
	}
	if (control->conn_version >= 31) {
		sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_parentid);
	}
	if (control->conn_version >= 32) {
		sprintf(buf + strlen(buf), "%s[]:[]", prog->proginfo_storagegroup);
	}
	if (control->conn_version >= 35) {
		sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_audioproperties);
		sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_videoproperties);
		sprintf(buf + strlen(buf), "%ld[]:[]", prog->proginfo_subtitletype);
	}
	if (control->conn_version >= 43) {
		sprintf(buf + strlen(buf), "%d[]:[]", prog->proginfo_year);
	}

	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_long(control, &err, &c, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, r);
		ret = err;
		goto out;
	}

	if (result) {
		*result = c;
	}

    out:
	pthread_mutex_unlock(&control->conn_mutex);

	return ret;
}
Exemple #12
0
/*
 * cmyth_conn_connect_path(char* path, cmyth_conn_t control,
 *                         unsigned buflen, int tcp_rcvbuf)
 *
 * Scope: PUBLIC
 *
 * Description:
 *
 * Create a file structure containing a data connection for use
 * transfering a file within the MythTV protocol.  Return a pointer to
 * the newly created file structure.  The connection in the file
 * structure is returned held as is the file structure itself.  The
 * connection will be released when the file structure is released.
 * The file structure can be released using ref_release().
 *
 * Return Value:
 *
 * Success: Non-NULL cmyth_file_t (this is a pointer type)
 *
 * Failure: NULL cmyth_file_t
 */
cmyth_file_t
cmyth_conn_connect_path(char* path, cmyth_conn_t control,
			unsigned buflen, int tcp_rcvbuf, char* storage_group)
{
	cmyth_conn_t conn = NULL;
	char *announcement = NULL;
	char reply[16];
	char host[256];
	int err = 0;
	int count = 0;
	int r, port;
	int ann_size = sizeof("ANN FileTransfer  0[]:[][]:[]");
	struct sockaddr_in addr;
        socklen_t addr_size = sizeof(addr);
	cmyth_file_t ret = NULL;

	if (getpeername(control->conn_fd, (struct sockaddr*)&addr, &addr_size)<0) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: getpeername() failed\n",
			  __FUNCTION__);
		goto shut;
	}

	inet_ntop(addr.sin_family, &addr.sin_addr, host, sizeof(host));
	port = ntohs(addr.sin_port);

	ret = cmyth_file_create(control);
	if (!ret) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: cmyth_file_create() failed\n",
			  __FUNCTION__);
		goto shut;
	}

	cmyth_dbg(CMYTH_DBG_PROTO, "%s: connecting data connection\n",
		  __FUNCTION__);
	conn = cmyth_connect(host, port, buflen, tcp_rcvbuf);
	cmyth_dbg(CMYTH_DBG_PROTO,
		  "%s: done connecting data connection, conn = %p\n",
		  __FUNCTION__, conn);
	if (!conn) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_connect(%s, %d, %d) failed\n",
			  __FUNCTION__, host, port, buflen);
		goto shut;
	}
	/*
	 * Explicitly set the conn version to the control version as cmyth_connect() doesn't and some of
	 * the cmyth_rcv_* functions expect it to be the same as the protocol version used by mythbackend.
	 */
	conn->conn_version = control->conn_version;

	ann_size += strlen(path) + strlen(my_hostname) + strlen(storage_group) + 6;
	announcement = malloc(ann_size);
	if (!announcement) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: malloc(%d) failed for announcement\n",
			  __FUNCTION__, ann_size);
		goto shut;
	}
	if (control->conn_version >= 44) { /*TSP: from version 44 according to the source code*/
		if (strlen(storage_group) > 1) {
			sprintf(announcement, "ANN FileTransfer %s 0 0 0[]:[]%s[]:[]%s",
				  my_hostname, path, storage_group);
		} else {
			sprintf(announcement, "ANN FileTransfer %s 0[]:[]%s[]:[]",  // write = false
				  my_hostname, path);
		}
	} else {
		sprintf(announcement, "ANN FileTransfer %s[]:[]%s",
			  my_hostname, path);
	}
	if (cmyth_send_message(conn, announcement) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_send_message('%s') failed\n",
			  __FUNCTION__, announcement);
		goto shut;
	}
	ret->file_data = ref_hold(conn);
	count = cmyth_rcv_length(conn);
	if (count < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_length() failed (%d)\n",
			  __FUNCTION__, count);
		goto shut;
	}
	reply[sizeof(reply) - 1] = '\0';
	r = cmyth_rcv_string(conn, &err, reply, sizeof(reply) - 1, count); 
	if (err != 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: cmyth_rcv_string() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	if (strcmp(reply, "OK") != 0) {
		cmyth_dbg(CMYTH_DBG_ERROR, "%s: reply ('%s') is not 'OK'\n",
			  __FUNCTION__, reply);
		goto shut;
	}
	count -= r;
	r = cmyth_rcv_long(conn, &err, &ret->file_id, count);
	if (err) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: (id) cmyth_rcv_long() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	count -= r;
	r = cmyth_rcv_uint64(conn, &err, &ret->file_length, count);
	if (err) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			  "%s: (length) cmyth_rcv_uint64() failed (%d)\n",
			  __FUNCTION__, err);
		goto shut;
	}
	count -= r;
	free(announcement);
	ref_release(conn);
	return ret;

    shut:
	if (announcement) {
		free(announcement);
	}
	ref_release(ret);
	ref_release(conn);
	return NULL;
}
Exemple #13
0
static int
delete_command(cmyth_conn_t control, cmyth_proginfo_t prog, char *cmd)
{
    long c = 0;
    char *buf;
    unsigned int len = ((2 * CMYTH_LONGLONG_LEN) +
                        (6 * CMYTH_TIMESTAMP_LEN) +
                        (14 * CMYTH_LONG_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];
    int err;
    int count;
    long r;
    int ret;

    if (!prog) {
        cmyth_dbg(CMYTH_DBG_ERROR, "%s: no program info\n",
                  __FUNCTION__);
        return -EINVAL;
    }
#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_recpriority_2));
    len += strlen(S(prog->proginfo_storagegroup));

    buf = alloca(len + 1+2048);
    if (!buf) {
        return -ENOMEM;
    }

    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);
    }

    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;
    } else {
        sprintf(buf,
                "%s 0[]:[]"
                "%s[]:[]%s[]:[]%s[]:[]%s[]:[]%ld[]:[]"
                "%s[]:[]%s[]:[]%s[]:[]%s[]:[]%ld[]:[]"
                "%ld[]:[]%s[]:[]%s[]:[]%s[]:[]%ld[]:[]"
                "%ld[]:[]%s[]:[]%ld[]:[]%ld[]:[]%ld[]:[]"
                "%s[]:[]%ld[]:[]%ld[]:[]%ld[]:[]%ld[]:[]"
                "%ld[]:[]%s[]:[]%s[]:[]%ld[]:[]%ld[]:[]"
                "%s[]:[]%s[]:[]%s[]:[]%s[]:[]"
                "%s[]:[]%s[]:[]%s[]:[]%s[]:[]",
                cmd,
                S(prog->proginfo_title),
                S(prog->proginfo_subtitle),
                S(prog->proginfo_description),
                S(prog->proginfo_category),
                prog->proginfo_chanId,
                S(prog->proginfo_chanstr),
                S(prog->proginfo_chansign),
                S(prog->proginfo_chanicon),
                S(prog->proginfo_url),
                (unsigned long)(prog->proginfo_Length >> 32),
                (unsigned long)(prog->proginfo_Length & 0xffffffff),
                start_ts,
                end_ts,
                S(prog->proginfo_unknown_0),
                prog->proginfo_recording,
                prog->proginfo_override,
                S(prog->proginfo_hostname),
                prog->proginfo_source_id,
                prog->proginfo_card_id,
                prog->proginfo_input_id,
                S(prog->proginfo_rec_priority),
                prog->proginfo_rec_status,
                prog->proginfo_record_id,
                prog->proginfo_rec_type,
                prog->proginfo_rec_dups,
                prog->proginfo_unknown_1,
                rec_start_ts,
                rec_end_ts,
                prog->proginfo_repeat,
                prog->proginfo_program_flags,
                S(prog->proginfo_recgroup),
                S(prog->proginfo_chancommfree),
                S(prog->proginfo_chan_output_filters),
                S(prog->proginfo_seriesid),
                S(prog->proginfo_programid),
                lastmodified,
                S(prog->proginfo_stars),
                originalairdate);
        if (control->conn_version >= 15) {
            sprintf(buf + strlen(buf), "%ld[]:[]",
                    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), "%s[]:[]",
                    S(prog->proginfo_recpriority_2));
        }
        if (control->conn_version >= 31) {
            sprintf(buf + strlen(buf), "%ld[]:[]",
                    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), "%ld[]:[]%ld[]:[]%ld[]:[]",
                    prog->proginfo_audioproperties,
                    prog->proginfo_videoproperties,
                    prog->proginfo_subtitletype);
        }
        if (control->conn_version >= 41) {
            sprintf(buf + strlen(buf), "%s[]:[]",
                    S(prog->proginfo_prodyear));
        }
    }
#undef S

    pthread_mutex_lock(&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_long(control, &err, &c, count)) < 0) {
        cmyth_dbg(CMYTH_DBG_ERROR,
                  "%s: cmyth_rcv_length() failed (%d)\n",
                  __FUNCTION__, r);
        ret = err;
        goto out;
    }

    /*
     * XXX: for some reason, this seems to return an error, even though
     *      it succeeds...
     */

    ret = 0;

out:
    pthread_mutex_unlock(&mutex);

    return ret;
}