/** \file
 *
 * Implement Camera object representing a camera attached to the system.
 *
 * \author Copyright 2000 Scott Fritzinger
 * \author Copyright 2001-2002 Lutz Mueller <lutz@users.sf.net>
 *
 * \note
 * 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 of the License, or (at your option) any later version.
 *
 * \note
 * 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.
 *
 * \note
 * 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

#include "config.h"
#include <gphoto2/gphoto2-camera.h>

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#include <ltdl.h>

#include <gphoto2/gphoto2-result.h>
#include <gphoto2/gphoto2-library.h>
#include <gphoto2/gphoto2-port-log.h>
#include <gphoto2/gphoto2-port-locking.h>

#include "libgphoto2/i18n.h"


#define CAMERA_UNUSED(c,ctx)						\
{									\
	(c)->pc->used--;						\
	if (!(c)->pc->used) {						\
		if ((c)->pc->exit_requested)				\
			gp_camera_exit ((c), (ctx));			\
		if (!(c)->pc->ref_count)				\
			gp_camera_free (c);				\
	}								\
}

#define CR(c,result,ctx)						\
{									\
	int r1 = (result);						\
									\
	if (r1 < 0) {							\
		/* libgphoto2_port doesn't have a GPContext */		\
		gp_context_error ((ctx), _("An error occurred "		\
			"in the io-library ('%s'): %s"),		\
			gp_port_result_as_string (r1),			\
			gp_port_get_error ((c) ? (c)->port : NULL));	\
		if (c)							\
			CAMERA_UNUSED (c,ctx);				\
		return (r1);						\
	}								\
}

/*
 * HAVE_MULTI
 * ----------
 *
 * The problem: Several different programs (gtkam, gphoto2, gimp) accessing
 * 		one camera.
 * The solutions:
 *  (1) gp_port_open before each operation, gp_port_close after. This has
 *      shown to not work with some drivers (digita/dc240) for serial ports,
 *      because the camera will notice that [1],
 *      reset itself and will therefore need to be reinitialized. If you want
 *      this behaviour, #define HAVE_MULTI.
 *  (2) Leave it up to the frontend to release the camera by calling
 *      gp_camera_exit after camera operations. This is what is implemented
 *      right now. The drawback is that re-initialization takes more time than
 *      just reopening the port. However, it works for all camera drivers.
 *
 * [1] Marr <marr@shianet.org> writes:
 *
 *     With the Digita-OS cameras at least, one of the RS-232 lines is tied
 *     to a 'Reset' signal on the camera.  I quote from the Digita 'Host
 *     Interface Specification' document:
 *
 *     "The Reset signal is a pulse on the Reset/Att line (which corresponds
 *     [sic] to pin 2 at the camera side) sent from the host computer to the
 *     camera.  This pulse must be at least 50us."
 */

#ifdef HAVE_MULTI
#define CHECK_OPEN(c,ctx)						\
{									\
	int r2;								\
									\
	if (strcmp ((c)->pc->a.model,"Directory Browse")) {		\
		r2 = gp_port_open ((c)->port);				\
		if (r2 < 0) {						\
			CAMERA_UNUSED (c,ctx);				\
			return (r2);					\
		}							\
	}								\
	if ((c)->functions->pre_func) {					\
		r2 = (c)->functions->pre_func (c,ctx);			\
		if (r2 < 0) {						\
			CAMERA_UNUSED (c,ctx);				\
			return (r2);					\
		}							\
	}								\
}
#else
#define CHECK_OPEN(c,ctx)						\
{                                                                       \
	if ((c)->functions->pre_func) {					\
		int r2 = (c)->functions->pre_func (c,ctx);		\
		if (r2 < 0) {                                           \
			CAMERA_UNUSED (c,ctx);				\
			return (r2);                                    \
		}                                                       \
	}                                                               \
}
#endif

#ifdef HAVE_MULTI
#define CHECK_CLOSE(c,ctx)					\
{								\
	if (strcmp ((c)->pc->a.model,"Directory Browse"))	\
		gp_port_close ((c)->port);			\
	if ((c)->functions->post_func) {			\
		int r3 = (c)->functions->post_func (c,ctx);	\
		if (r3 < 0) {					\
			CAMERA_UNUSED (c,ctx);			\
			return (r3);				\
		}						\
	}							\
}
#else
#define CHECK_CLOSE(c,ctx)					\
{								\
	if ((c)->functions->post_func) {			\
		int r3 = (c)->functions->post_func (c,ctx);	\
		if (r3 < 0) {					\
			CAMERA_UNUSED (c,ctx);			\
			return (r3);				\
		}						\
	}							\
}
#endif

#define CRS(c,res,ctx)							\
{									\
	int r4 = (res);							\
									\
	if (r4 < 0) {							\
		CAMERA_UNUSED (c,ctx);                              	\
		return (r4);						\
	}								\
}

#define CRSL(c,res,ctx,list)						\
{									\
	int r5 = (res);							\
									\
	if (r5 < 0) {							\
		CAMERA_UNUSED (c,ctx);                              	\
		gp_list_free (list);					\
		return (r5);						\
	}								\
}

#define CHECK_RESULT_OPEN_CLOSE(c,result,ctx)				\
{									\
	int r6;								\
									\
	CHECK_OPEN (c,ctx);						\
	r6 = (result);							\
	if (r6 < 0) {							\
		GP_LOG_E ("'%s' failed: %d", #result, r6);		\
		CHECK_CLOSE (c,ctx);					\
		CAMERA_UNUSED (c,ctx);                              	\
		return (r6);						\
	}								\
	CHECK_CLOSE (c,ctx);						\
}

#define CHECK_INIT(c,ctx)						\
{									\
	if ((c)->pc->used)						\
		return (GP_ERROR_CAMERA_BUSY);				\
	(c)->pc->used++;						\
	if (!(c)->pc->lh)						\
		CR((c), gp_camera_init (c, ctx), ctx);			\
}

struct _CameraPrivateCore {

	/* Some information about the port */
	unsigned int speed;

	/* The abilities of this camera */
	CameraAbilities a;

	/* Library handle */
	lt_dlhandle lh;

	char error[2048];

	unsigned int ref_count;
	unsigned char used;
	unsigned char exit_requested;

	int initialized;

	/* Timeout functions */
	CameraTimeoutStartFunc timeout_start_func;
	CameraTimeoutStopFunc  timeout_stop_func;
	void                  *timeout_data;
	unsigned int          *timeout_ids;
	unsigned int           timeout_ids_len;
};


/**
 * Close connection to camera.
 *
 * @param camera a #Camera object
 * @param context a #GPContext object
 * @return a gphoto2 error code.
 *
 * Closes a connection to the camera and therefore gives other application
 * the possibility to access the camera, too.
 *
 * It is recommended that you
 * call this function when you currently don't need the camera. The camera
 * will get reinitialized by gp_camera_init() automatically if you try to
 * access the camera again.
 *
 */
int
gp_camera_exit (Camera *camera, GPContext *context)
{
	int exit_result = GP_OK;
	C_PARAMS (camera);

	GP_LOG_D ("Exiting camera ('%s')...", camera->pc->a.model);

	/*
	 * We have to postpone this operation if the camera is currently
	 * in use. gp_camera_exit will be called again if the
	 * camera->pc->used will drop to zero.
	 */
	if (camera->pc->used) {
		camera->pc->exit_requested = 1;
		return (GP_OK);
	}

	/* Remove every timeout that is still pending */
	while (camera->pc->timeout_ids_len)
		gp_camera_stop_timeout (camera, camera->pc->timeout_ids[0]);
	free (camera->pc->timeout_ids);
	camera->pc->timeout_ids = NULL;

	if (camera->functions->exit) {
#ifdef HAVE_MULTI
		gp_port_open (camera->port);
#endif
		exit_result = camera->functions->exit (camera, context);
	}
	gp_port_close (camera->port);
	memset (camera->functions, 0, sizeof (CameraFunctions));

	if (camera->pc->lh) {
#if !defined(VALGRIND)
		gpi_libltdl_lock();
		lt_dlclose (camera->pc->lh);
		lt_dlexit ();
		gpi_libltdl_unlock();
#endif
		camera->pc->lh = NULL;
	}

	gp_filesystem_reset (camera->fs);

	return exit_result;
}


/**
 * Allocates the memory for a #Camera.
 *
 * @param camera the #Camera object to initialize.
 * @return a gphoto2 error code
 *
 */
int
gp_camera_new (Camera **camera)
{
	int result;

	C_PARAMS (camera);

	C_MEM (*camera = calloc (1, sizeof (Camera)));

	(*camera)->functions = calloc (1, sizeof (CameraFunctions));
	(*camera)->pc        = calloc (1, sizeof (CameraPrivateCore));
	if (!(*camera)->functions || !(*camera)->pc) {
		result = GP_ERROR_NO_MEMORY;
		goto error;
	}

	(*camera)->pc->ref_count = 1;

	/* Create the filesystem */
	result = gp_filesystem_new (&(*camera)->fs);
	if (result < GP_OK)
		goto error;

	/* Create the port */
	result = gp_port_new (&(*camera)->port);
	if (result < GP_OK)
		goto error;

	return(GP_OK);

error:
	gp_camera_free (*camera);
	return result;
}


/**
 * \brief Sets the camera abilities.
 *
 * @param camera a #Camera
 * @param abilities the #CameraAbilities to be set
 * @return a gphoto2 error code
 *
 * You need to call this function before calling #gp_camera_init the
 * first time unless you want gphoto2 to autodetect cameras and choose
 * the first detected one. By setting the \c abilities, you
 * tell gphoto2 what model the \c camera is and what camera driver should
 * be used for accessing the \c camera. You can get \c abilities by calling
 * #gp_abilities_list_get_abilities.
 *
 */
int
gp_camera_set_abilities (Camera *camera, CameraAbilities abilities)
{
	GP_LOG_D ("Setting abilities ('%s')...", abilities.model);

	C_PARAMS (camera);

	/*
	 * If the camera is currently initialized, terminate that connection.
	 * We don't care if we are successful or not.
	 */
	if (camera->pc->lh)
		gp_camera_exit (camera, NULL);

	memcpy (&camera->pc->a, &abilities, sizeof (CameraAbilities));

	return (GP_OK);
}


/**
 * Retrieve the \c abilities of the \c camera.
 *
 * @param camera a #Camera
 * @param abilities
 * @return a gphoto2 error code
 *
 */
int
gp_camera_get_abilities (Camera *camera, CameraAbilities *abilities)
{
	C_PARAMS (camera && abilities);

	memcpy (abilities, &camera->pc->a, sizeof (CameraAbilities));

	return (GP_OK);
}


int
gp_camera_get_port_info (Camera *camera, GPPortInfo *info)
{
	C_PARAMS (camera && info);

	CR (camera, gp_port_get_info (camera->port, info), NULL);

	return (GP_OK);
}


int
gp_camera_set_port_info (Camera *camera, GPPortInfo info)
{
	char	*name, *path;
	C_PARAMS (camera);

	/*
	 * If the camera is currently initialized, terminate that connection.
	 * We don't care if we are successful or not.
	 */
	if (camera->pc->lh)
		gp_camera_exit (camera, NULL);

	gp_port_info_get_name (info, &name);
	gp_port_info_get_path (info, &path);
	GP_LOG_D ("Setting port info for port '%s' at '%s'...", name, path);
	CR (camera, gp_port_set_info (camera->port, info), NULL);

	return (GP_OK);
}


/**
 * Set the camera speed.
 *
 * @param camera a #Camera
 * @param speed the speed
 * @return a gphoto2 error code
 *
 * This function is typically used prior first initialization
 * using #gp_camera_init for debugging purposes. Normally, a camera driver
 * will try to figure out the current speed of the camera and set the speed
 * to the optimal one automatically. Note that this function only works with
 * serial ports. In other words, you have to set the camera's port to a
 * serial one (using #gp_camera_set_port_path or #gp_camera_set_port_name)
 * prior calling this function.
 *
 */
int
gp_camera_set_port_speed (Camera *camera, int speed)
{
	GPPortSettings settings;

	C_PARAMS (camera);

	C_PARAMS_MSG (camera->port,
		      "You need to set a port prior trying to set the speed");
	C_PARAMS_MSG (camera->port->type == GP_PORT_SERIAL,
		      "You can specify a speed only with serial ports");

	/*
	 * If the camera is currently initialized, terminate that connection.
	 * We don't care if we are successful or not.
	 */
	if (camera->pc->lh)
		gp_camera_exit (camera, NULL);

	CR (camera, gp_port_get_settings (camera->port, &settings), NULL);
	settings.serial.speed = speed;
	CR (camera, gp_port_set_settings (camera->port, settings), NULL);
	camera->pc->speed = speed;

	return (GP_OK);
}


/**
 * Retrieve the current speed.
 *
 * @param camera a #Camera
 * @return The current speed or a gphoto2 error code
 *
 */
int
gp_camera_get_port_speed (Camera *camera)
{
	C_PARAMS (camera);

	return (camera->pc->speed);
}


/**
 * Increment the reference count of a \c camera.
 *
 * @param camera a #Camera
 * @return a gphoto2 error code
 *
 */
int
gp_camera_ref (Camera *camera)
{
	C_PARAMS (camera);

	camera->pc->ref_count += 1;

	return (GP_OK);
}


/**
 * Decrements the reference count of a #Camera.
 *
 * @param camera a #Camera
 * @return a gphoto2 error code
 *
 * If the reference count reaches %0, the \c camera will be freed
 * automatically.
 *
 */
int
gp_camera_unref (Camera *camera)
{
	C_PARAMS (camera);

	if (!camera->pc->ref_count) {
		GP_LOG_E ("gp_camera_unref on a camera with ref_count == 0 "
			"should not happen at all");
		return (GP_ERROR);
	}

	camera->pc->ref_count -= 1;

	if (!camera->pc->ref_count) {

		/* We cannot free a camera that is currently in use */
		if (!camera->pc->used)
			gp_camera_free (camera);
	}

	return (GP_OK);
}


/**
 * Free the \c camera.
 *
 * @param camera a #Camera
 * @return a gphoto2 error code
 *
 * \deprecated
 * This function should never be used. Please use #gp_camera_unref instead.
 *
 */
int
gp_camera_free (Camera *camera)
{
	C_PARAMS (camera);

	GP_LOG_D ("Freeing camera...");

	/*
	 * If the camera is currently initialized, close the connection.
	 * We don't care if we are successful or not.
	 */
	if (camera->port && camera->pc && camera->pc->lh)
		gp_camera_exit (camera, NULL);

	/* We don't care if anything goes wrong */
	if (camera->port) {
		gp_port_free (camera->port);
		camera->port = NULL;
	}

	if (camera->pc) {
		free (camera->pc->timeout_ids);
		free (camera->pc);
		camera->pc = NULL;
	}

	if (camera->fs) {
		gp_filesystem_free (camera->fs);
		camera->fs = NULL;
	}

	free (camera->functions);

	free (camera);

	return (GP_OK);
}

/**
 * Autodetect all detectable camera
 *
 * @param list a #CameraList that receives the autodetected cameras
 * @param context a #GPContext
 * @return the number of cameras detected (0 to n) or a gphoto2 error code (<0)
 *
 * This camera will autodetected all cameras that can be autodetected.
 * This will for instance detect all USB cameras.
 *
 *   CameraList *list;
 *   gp_list_new (&list);
 *   gp_camera_autodetect (list, context);
 *   ... done! ...
 */
int
gp_camera_autodetect (CameraList *list, GPContext *context)
{
	CameraAbilitiesList	*al = NULL;
	GPPortInfoList		*il = NULL;
	int			ret, i;
	CameraList		*xlist = NULL;

	ret = gp_list_new (&xlist);
	if (ret < GP_OK) goto out;
	if (!il) {
		/* Load all the port drivers we have... */
		ret = gp_port_info_list_new (&il);
		if (ret < GP_OK) goto out;
		ret = gp_port_info_list_load (il);
		if (ret < 0) goto out;
		ret = gp_port_info_list_count (il);
		if (ret < 0) goto out;
	}
	/* Load all the camera drivers we have... */
	ret = gp_abilities_list_new (&al);
	if (ret < GP_OK) goto out;
	ret = gp_abilities_list_load (al, context);
	if (ret < GP_OK) goto out;

	/* ... and autodetect the currently attached cameras. */
	ret = gp_abilities_list_detect (al, il, xlist, context);
	if (ret < GP_OK) goto out;

	/* Filter out the "usb:" entry */
	ret = gp_list_count (xlist);
	if (ret < GP_OK) goto out;
	for (i=0;i<ret;i++) {
		const char *name, *value;

		gp_list_get_name (xlist, i, &name);
		gp_list_get_value (xlist, i, &value);
		if (!strcmp ("usb:",value)) continue;
		gp_list_append (list, name, value);
	}
out:
	if (il) gp_port_info_list_free (il);
	if (al) gp_abilities_list_free (al);
	gp_list_free (xlist);
	if (ret < GP_OK)
		return ret;
	return gp_list_count(list);
}

/**
 * Initiate a connection to the \c camera.
 *
 * @param camera a #Camera
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * Before calling this function, the
 * \c camera should be set up using gp_camera_set_port_path() or
 * gp_camera_set_port_name() and gp_camera_set_abilities(). If that has been
 * omitted, gphoto2 tries to autodetect any cameras and chooses the first one
 * if any cameras are found. It is generally a good idea to call
 * gp_camera_exit() after transactions have been completed in order to give
 * other applications the chance to access the camera, too.
 *
 */
int
gp_camera_init (Camera *camera, GPContext *context)
{
	CameraAbilities a;
	const char *model, *port;
	CameraLibraryInitFunc init_func;
	int result;

	GP_LOG_D ("Initializing camera...");

	C_PARAMS (camera);
	/*
	 * Reset the exit_requested flag. If this flag is set,
	 * gp_camera_exit will be called as soon as the camera is no
	 * longer in use (used flag).
	 */
	camera->pc->exit_requested = 0;

	/*
	 * If the model hasn't been indicated, try to
	 * figure it out (USB only). Beware of "Directory Browse".
	 */
	if (strcasecmp (camera->pc->a.model, "Directory Browse") &&
	    !strcmp ("", camera->pc->a.model)) {
		CameraAbilitiesList *al;
		GPPortInfoList	*il;
		int		m, p;
		char		*ppath;
		GPPortType	ptype;
		GPPortInfo	info;
		CameraList	*list;

		result = gp_list_new (&list);
		if (result < GP_OK)
			return result;

		GP_LOG_D ("Neither port nor model set. Trying auto-detection...");

		/* Call auto-detect and choose the first camera */
		CRSL (camera, gp_abilities_list_new (&al), context, list);
		CRSL (camera, gp_abilities_list_load (al, context), context, list);
		CRSL (camera, gp_port_info_list_new (&il), context, list);
		CRSL (camera, gp_port_info_list_load (il), context, list);
		CRSL (camera, gp_abilities_list_detect (al, il, list, context), context, list);
		if (!gp_list_count (list)) {
			gp_abilities_list_free (al);
			gp_port_info_list_free (il);
			gp_context_error (context, _("Could not detect "
					     "any camera"));
			gp_list_free (list);
			return (GP_ERROR_MODEL_NOT_FOUND);
		}
		p = 0;
		CRSL (camera, gp_port_get_info (camera->port, &info), context, list);
		CRSL (camera, gp_port_info_get_path (info, &ppath), context, list);
		CRSL (camera, gp_port_info_get_type (info, &ptype), context, list);
		/* if the port was set before, then use that entry, but not if it is "usb:" */
		if ((ptype == GP_PORT_USB) && strlen(ppath) && strcmp(ppath, "usb:")) {
			for (p = gp_list_count (list);p--;) {
				const char *xp;

				gp_list_get_value (list, p, &xp);
				if (!strcmp (xp, ppath))
					break;
			}
			if (p<0) {
				gp_abilities_list_free (al);
				gp_port_info_list_free (il);
				gp_context_error (context, _("Could not detect any camera at port %s"), ppath);
				gp_list_free (list);
				return (GP_ERROR_FILE_NOT_FOUND);
			}
		}

		CRSL (camera, gp_list_get_name  (list, p, &model), context, list);
		m = gp_abilities_list_lookup_model (al, model);
		CRSL (camera, m, context, list);
		CRSL (camera, gp_abilities_list_get_abilities (al, m, &a), context, list);
		gp_abilities_list_free (al);
		CRSL (camera, gp_camera_set_abilities (camera, a), context, list);
		CRSL (camera, gp_list_get_value (list, p, &port), context, list);
		p = gp_port_info_list_lookup_path (il, port);
		CRSL (camera, p, context, list);
		CRSL (camera, gp_port_info_list_get_info (il, p, &info), context, list);
		CRSL (camera, gp_camera_set_port_info (camera, info), context, list);
		gp_port_info_list_free (il);
		gp_list_free (list);
	}

	if (strcasecmp (camera->pc->a.model, "Directory Browse")) {
		switch (camera->port->type) {
		case GP_PORT_NONE:
			gp_context_error (context, _("You have to set the "
				"port prior to initialization of the camera."));
			return (GP_ERROR_UNKNOWN_PORT);
		case GP_PORT_USB:
			if (gp_port_usb_find_device (camera->port,
					camera->pc->a.usb_vendor,
					camera->pc->a.usb_product) != GP_OK) {
				CRS (camera, gp_port_usb_find_device_by_class
					(camera->port,
					camera->pc->a.usb_class,
					camera->pc->a.usb_subclass,
					camera->pc->a.usb_protocol), context);
					}
			break;
		default:
			break;
		}
	}

	/* Load the library. */
	GP_LOG_D ("Loading '%s'...", camera->pc->a.library);
	gpi_libltdl_lock();
	lt_dlinit ();
	camera->pc->lh = lt_dlopenext (camera->pc->a.library);
	gpi_libltdl_unlock();
	if (!camera->pc->lh) {
		gpi_libltdl_lock();
		gp_context_error (context, _("Could not load required "
			"camera driver '%s' (%s)."), camera->pc->a.library,
			lt_dlerror ());
		lt_dlexit ();
		gpi_libltdl_unlock();
		return (GP_ERROR_LIBRARY);
	}

	/* Initialize the camera */
	gpi_libltdl_lock();
	init_func = lt_dlsym (camera->pc->lh, "camera_init");
	gpi_libltdl_unlock();
	if (!init_func) {
		gpi_libltdl_lock();
		lt_dlclose (camera->pc->lh);
		lt_dlexit ();
		gpi_libltdl_unlock();
		camera->pc->lh = NULL;
		gp_context_error (context, _("Camera driver '%s' is "
			"missing the 'camera_init' function."),
			camera->pc->a.library);
		return (GP_ERROR_LIBRARY);
	}

	if (strcasecmp (camera->pc->a.model, "Directory Browse")) {
		result = gp_port_open (camera->port);
		if (result < 0) {
			gpi_libltdl_lock();
			lt_dlclose (camera->pc->lh);
			lt_dlexit ();
			gpi_libltdl_unlock();
			camera->pc->lh = NULL;
			return (result);
		}
	}

	result = init_func (camera, context);
	if (result < 0) {
		gp_port_close (camera->port);
		gpi_libltdl_lock();
		lt_dlclose (camera->pc->lh);
		lt_dlexit ();
		gpi_libltdl_unlock();
		camera->pc->lh = NULL;
		memset (camera->functions, 0, sizeof (CameraFunctions));
		return (result);
	}

	/* We don't care if that goes wrong */
#ifdef HAVE_MULTI
	gp_port_close (camera->port);
#endif

	return (GP_OK);
}


/**
 * Retrieve a configuration \c window for the \c camera.
 *
 * @param camera a #Camera
 * @param window a #CameraWidget
 * @param context a #GPContext
 * @return gphoto2 error code
 *
 * This \c window can be used for construction of a configuration dialog.
 *
 */
int
gp_camera_get_config (Camera *camera, CameraWidget **window, GPContext *context)
{
	C_PARAMS (camera);
	CHECK_INIT (camera, context);

	if (!camera->functions->get_config) {
		gp_context_error (context, _("This camera does "
			"not provide any configuration options."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->get_config (
					camera, window, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}


/**
 * Retrieve a single configuration \c widget for the \c camera.
 *
 * @param camera a #Camera
 * @param name the name of a configuration widget
 * @param widget a #CameraWidget
 * @param context a #GPContext
 * @return gphoto2 error code
 *
 * This \c widget will then contain the current and the possible values and the type.
 *
 */
int
gp_camera_get_single_config (Camera *camera, const char *name, CameraWidget **widget, GPContext *context)
{
	CameraWidget		*rootwidget, *child;
	CameraWidgetType	type;
	const char		*label;
	int			ret, ro;

	C_PARAMS (camera);
	CHECK_INIT (camera, context);

	if (camera->functions->get_single_config) {
		CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->get_single_config (
						camera, name, widget, context), context);

		CAMERA_UNUSED (camera, context);
		return GP_OK;
	}

	if (!camera->functions->get_config) {
		gp_context_error (context, _("This camera does not provide any configuration options."));
		CAMERA_UNUSED (camera, context);
		return GP_ERROR_NOT_SUPPORTED;
	}
	/* emulate it ... */
	CHECK_OPEN (camera, context);

	ret = camera->functions->get_config ( camera, &rootwidget, context);
	if (ret != GP_OK) {
		CHECK_CLOSE (camera, context);
		CAMERA_UNUSED (camera, context);
		return ret;
	}
	ret = gp_widget_get_child_by_name (rootwidget, name, &child);
	if (ret != GP_OK) {
		gp_widget_free (rootwidget);
		CHECK_CLOSE (camera, context);
		CAMERA_UNUSED (camera, context);
		return ret;
	}

	/* We need to duplicate the widget, as we will free the widgettree */
	gp_widget_get_type (child, &type);
	gp_widget_get_label (child, &label);
	gp_widget_get_readonly (child, &ro);

	ret = gp_widget_new (type, label, widget);
	if (ret != GP_OK)
		goto out;
	gp_widget_set_name (*widget, name);
	gp_widget_set_readonly (*widget, ro);

	switch (type) {
	case GP_WIDGET_MENU:
	case GP_WIDGET_RADIO: {
		char *value;
		int i, nrofchoices;

		nrofchoices = gp_widget_count_choices (child);
		for (i = 0; i < nrofchoices; i++) {
			const char *choice;

			gp_widget_get_choice (child, i, &choice);
			gp_widget_add_choice (*widget, choice);
		}
		gp_widget_get_value (child, &value);
		gp_widget_set_value (*widget, value);
		break;
	}
	case GP_WIDGET_TEXT: {
		char *value;

		gp_widget_get_value (child, &value);
		gp_widget_set_value (*widget, value);
		break;
	}
	case GP_WIDGET_RANGE: {
		float value, rmin, rmax, rstep;

		gp_widget_get_range (child, &rmin, &rmax, &rstep);
		gp_widget_set_range (*widget, rmin, rmax, rstep);
		gp_widget_get_value (child, &value);
		gp_widget_set_value (*widget, &value);
		break;
	}
	case GP_WIDGET_TOGGLE:
	case GP_WIDGET_DATE: {
		int value;

		gp_widget_get_value (child, &value);
		gp_widget_set_value (*widget, &value);
		break;
	}
	case GP_WIDGET_BUTTON:
	case GP_WIDGET_SECTION:
	case GP_WIDGET_WINDOW:
	default:
		ret = GP_ERROR_BAD_PARAMETERS;
		break;
	}
out:
	gp_widget_free (rootwidget);
	CHECK_CLOSE (camera, context);
	CAMERA_UNUSED (camera, context);
	return ret;
}


/**
 * Retrieve a configuration \c list for the \c camera.
 *
 * @param camera a #Camera
 * @param list a #CameraList
 * @param context a #GPContext
 * @return gphoto2 error code
 *
 * The names in list can be used for the single set and get configuration calls.
 *
 */

static void
_get_widget_names (CameraWidget *widget, CameraList *list)
{
	CameraWidgetType	type;

	gp_widget_get_type (widget, &type);
	switch (type) {
	case GP_WIDGET_MENU:
	case GP_WIDGET_RADIO:
	case GP_WIDGET_TEXT:
	case GP_WIDGET_RANGE:
	case GP_WIDGET_TOGGLE:
	case GP_WIDGET_DATE: {
		const char *name;

		gp_widget_get_name (widget, &name);
		gp_list_append (list, name, NULL);
		break;
	}
	case GP_WIDGET_SECTION:
	case GP_WIDGET_WINDOW: {
		int i, nrofchildren;

		nrofchildren = gp_widget_count_children (widget);
		for (i = 0; i < nrofchildren; i++) {
			CameraWidget *child;

			gp_widget_get_child (widget, i, &child);
			_get_widget_names (child, list);
		}
		break;
	}
	case GP_WIDGET_BUTTON:
	default:
		break;
	}

}

int
gp_camera_list_config (Camera *camera, CameraList *list, GPContext *context)
{
	CameraWidget		*rootwidget;
	int			ret;
	C_PARAMS (camera);
	CHECK_INIT (camera, context);

	if (camera->functions->list_config) {
		CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->list_config (
						camera, list, context), context);

		CAMERA_UNUSED (camera, context);
		return GP_OK;
	}
	if (!camera->functions->get_config) {
		gp_context_error (context, _("This camera does not provide any configuration options."));
		CAMERA_UNUSED (camera, context);
		return GP_ERROR_NOT_SUPPORTED;
	}
	/* emulate it ... */
	CHECK_OPEN (camera, context);

	ret = camera->functions->get_config ( camera, &rootwidget, context);
	if (ret != GP_OK) {
		CHECK_CLOSE (camera, context);
		CAMERA_UNUSED (camera, context);
		return ret;
	}

	_get_widget_names (rootwidget, list);


	gp_widget_free (rootwidget);
	CHECK_CLOSE (camera, context);
	CAMERA_UNUSED (camera, context);
	return ret;
}


/**
 * Sets the configuration.
 *
 * @param camera a #Camera
 * @param window a #CameraWidget
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * Typically, a \c window is retrieved using #gp_camera_get_config and passed
 * to this function in order to adjust the settings on the camera.
 *
 **/
int
gp_camera_set_config (Camera *camera, CameraWidget *window, GPContext *context)
{
	C_PARAMS (camera && window);
	CHECK_INIT (camera, context);

	if (!camera->functions->set_config) {
		gp_context_error (context, _("This camera does "
			"not support setting configuration options."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->set_config (camera,
						window, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Set a single configuration \c widget for the \c camera.
 *
 * @param camera a #Camera
 * @param name the name of a configuration widget
 * @param widget a #CameraWidget
 * @param context a #GPContext
 * @return gphoto2 error code
 *
 * This \c widget contains the new value of the widget to set.
 *
 */
int
gp_camera_set_single_config (Camera *camera, const char *name, CameraWidget *widget, GPContext *context)
{
	CameraWidget		*rootwidget, *child;
	CameraWidgetType	type;
	int			ret;

	C_PARAMS (camera);
	CHECK_INIT (camera, context);

	if (camera->functions->set_single_config) {
		CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->set_single_config (
						camera, name, widget, context), context);

		CAMERA_UNUSED (camera, context);
		return GP_OK;
	}

	if (!camera->functions->set_config) {
		gp_context_error (context, _("This camera does not provide any configuration options."));
		CAMERA_UNUSED (camera, context);
		return GP_ERROR_NOT_SUPPORTED;
	}
	/* emulate single config with the full tree */
	CHECK_OPEN (camera, context);

	ret = camera->functions->get_config ( camera, &rootwidget, context);
	if (ret != GP_OK) {
		CHECK_CLOSE (camera, context);
		CAMERA_UNUSED (camera, context);
		return ret;
	}
	ret = gp_widget_get_child_by_name (rootwidget, name, &child);
	if (ret != GP_OK) {
		gp_widget_free (rootwidget);
		CHECK_CLOSE (camera, context);
		CAMERA_UNUSED (camera, context);
		return ret;
	}

	gp_widget_get_type (child, &type);
	ret = GP_OK;
	switch (type) {
	case GP_WIDGET_MENU:
	case GP_WIDGET_RADIO:
	case GP_WIDGET_TEXT: {
		char *value;

		gp_widget_get_value (widget, &value);
		gp_widget_set_value (child, value);
		break;
	}
	case GP_WIDGET_RANGE: {
		float value;

		gp_widget_get_value (widget, &value);
		gp_widget_set_value (child, &value);
		break;
	}
	case GP_WIDGET_TOGGLE:
	case GP_WIDGET_DATE: {
		int value;

		gp_widget_get_value (widget, &value);
		gp_widget_set_value (child, &value);
		break;
	}
	case GP_WIDGET_BUTTON:
	case GP_WIDGET_SECTION:
	case GP_WIDGET_WINDOW:
	default:
		ret = GP_ERROR_BAD_PARAMETERS;
		break;
	}
	gp_widget_set_changed (child, 1);

	if (ret == GP_OK)
		ret = camera->functions->set_config (camera, rootwidget, context);
	gp_widget_free (rootwidget);
	CHECK_CLOSE (camera, context);
	CAMERA_UNUSED (camera, context);
	return ret;
}


/**
 * Retrieves a camera summary.
 *
 * @param camera a #Camera
 * @param summary a #CameraText
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * This summary typically contains information like manufacturer, pictures
 * taken, or generally information that is not configurable.
 *
 **/
int
gp_camera_get_summary (Camera *camera, CameraText *summary, GPContext *context)
{
	C_PARAMS (camera && summary);
	CHECK_INIT (camera, context);

	if (!camera->functions->summary) {
		gp_context_error (context, _("This camera does "
				  "not support summaries."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->summary (camera,
						summary, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Retrieves the \c manual for given \c camera.
 *
 * @param camera a #Camera
 * @param manual a #CameraText
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * This manual typically contains information about using the camera.
 *
 **/
int
gp_camera_get_manual (Camera *camera, CameraText *manual, GPContext *context)
{
	C_PARAMS (camera && manual);
	CHECK_INIT (camera, context);

	if (!camera->functions->manual) {
		gp_context_error (context, _("This camera "
			"does not provide a manual."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->manual (camera,
						manual, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Retrieves information about the camera driver.
 *
 * @param camera a #Camera
 * @param about a #CameraText
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * Typically, this information contains name and address of the author,
 * acknowledgements, etc.
 *
 **/
int
gp_camera_get_about (Camera *camera, CameraText *about, GPContext *context)
{
	C_PARAMS (camera && about);
	CHECK_INIT (camera, context);

	if (!camera->functions->about) {
		gp_context_error (context, _("This camera does "
			"not provide information about the driver."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->about (camera,
						about, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Captures an image, movie, or sound clip depending on the given \c type.
 *
 * @param camera a #Camera
 * @param type a #CameraCaptureType
 * @param path a #CameraFilePath
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * The resulting file will be stored on the camera. The location gets stored
 * in \c path. The file can then be downloaded using #gp_camera_file_get.
 *
 **/
int
gp_camera_capture (Camera *camera, CameraCaptureType type,
		   CameraFilePath *path, GPContext *context)
{
	C_PARAMS (camera);
	CHECK_INIT (camera, context);

	if (!camera->functions->capture) {
		gp_context_error (context, _("This camera can not capture."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->capture (camera,
						type, path, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Triggers capture of one or more images.
 *
 * @param camera a #Camera
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * This functions just remotely causes the shutter release and returns
 * immediately. You will want to run #gp_camera_wait_event until a image
 * is added which can be downloaded using #gp_camera_file_get.
 **/
int
gp_camera_trigger_capture (Camera *camera, GPContext *context)
{
	C_PARAMS (camera);
	CHECK_INIT (camera, context);

	if (!camera->functions->trigger_capture) {
		gp_context_error (context, _("This camera can not trigger capture."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}
	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->trigger_capture (camera,
						context), context);
	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Captures a preview that won't be stored on the camera but returned in
 * supplied file.
 *
 * @param camera a #Camera
 * @param file a #CameraFile
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 * For example, you could use gp_capture_preview() for taking some sample
 * pictures before calling gp_capture().
 *
 **/
int
gp_camera_capture_preview (Camera *camera, CameraFile *file, GPContext *context)
{
	char *xname;
	C_PARAMS (camera && file);
	CHECK_INIT (camera, context);

	CR (camera, gp_file_clean (file), context);

	if (!camera->functions->capture_preview) {
		gp_context_error (context, _("This camera can "
			"not capture previews."));
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->capture_preview (
					camera, file, context), context);
	gp_file_get_name_by_type (file, "capture_preview", GP_FILE_TYPE_NORMAL, &xname);
	/* FIXME: Marcus ... will go away, just keep compatible now. */
	gp_file_set_name (file, xname);
	free (xname);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}


/**
 * Wait and retrieve an event from the camera.
 *
 * @param camera a Camera
 * @param timeout amount of time to wait in 1/1000 seconds
 * @param eventtype received CameraEventType [out]
 * @param eventdata received event specific data [out]
 * @param context a GPContext
 * @return gphoto2 error code
 *
 * This function blocks and waits for an event to come from the camera.  If
 * a timeout occurs before an event is received then
 * eventtype will be GP_EVENT_TIMEOUT and eventdata is left unchanged.
 *
 * If an event is received then eventtype is set to the type of event, and
 * eventdata is set to event specific data.  See the CameraEventType enum
 * to see which eventtypes match to which types of eventdata.
 *
 * Note that this function will return one event after each other, you need
 * to be able to call it multiple times, e.g. in a loop, when waiting for specific
 * events.
 */
int
gp_camera_wait_for_event (Camera *camera, int timeout,
			  CameraEventType *eventtype, void **eventdata,
			  GPContext *context)
{
	C_PARAMS (camera);
	CHECK_INIT (camera, context);

	if (!camera->functions->wait_for_event) {
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_NOT_SUPPORTED);
	}
	CHECK_RESULT_OPEN_CLOSE (camera, camera->functions->wait_for_event (
					camera, timeout, eventtype, eventdata,
					context), context);
	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Lists the files in supplied \c folder.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param list a #CameraList
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_folder_list_files (Camera *camera, const char *folder,
			     CameraList *list, GPContext *context)
{
	GP_LOG_D ("Listing files in '%s'...", folder);

	C_PARAMS (camera && folder && list);
	CHECK_INIT (camera, context);
	CR (camera, gp_list_reset (list), context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_list_files (camera->fs,
					folder, list, context), context);

	CR (camera, gp_list_sort (list), context);
	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Lists the folders in supplied \c folder.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param list a #CameraList
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_folder_list_folders (Camera *camera, const char* folder,
			       CameraList *list, GPContext *context)
{
	GP_LOG_D ("Listing folders in '%s'...", folder);

	C_PARAMS (camera && folder && list);
	CHECK_INIT (camera, context);
	CR (camera, gp_list_reset (list), context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_list_folders (
				camera->fs, folder, list, context), context);

	CR (camera, gp_list_sort (list), context);
	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Deletes all files in a given \c folder.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_folder_delete_all (Camera *camera, const char *folder,
			     GPContext *context)
{
	GP_LOG_D ("Deleting all files in '%s'...", folder);

	C_PARAMS (camera && folder);
	CHECK_INIT (camera, context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_delete_all (camera->fs,
						folder, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Uploads a file into given \c folder.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param file a #CameraFile
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_folder_put_file (Camera *camera,
			   const char *folder, const char *filename,
			   CameraFileType type,
			   CameraFile *file, GPContext *context)
{
	GP_LOG_D ("Uploading file into '%s'...",
		folder);

	C_PARAMS (camera && folder && file);
	CHECK_INIT (camera, context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_put_file (camera->fs,
					folder, filename, type, file, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Retrieves information about a file.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param file the name of the file
 * @param info
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_file_get_info (Camera *camera, const char *folder,
			 const char *file, CameraFileInfo *info,
			 GPContext *context)
{
	int result = GP_OK;
	const char *mime_type;
	const char *data;
	/* long int size; */
	CameraFile *cfile;

	GP_LOG_D ("Getting file info for '%s' in '%s'...", file, folder);

	C_PARAMS (camera && folder && file && info);
	CHECK_INIT (camera, context);

	memset (info, 0, sizeof (CameraFileInfo));

	/* Check first if the camera driver supports the filesystem */
	CHECK_OPEN (camera, context);
	result = gp_filesystem_get_info (camera->fs, folder, file, info,
					 context);
	CHECK_CLOSE (camera, context);
	if (result != GP_ERROR_NOT_SUPPORTED) {
		CAMERA_UNUSED (camera, context);
		return (result);
	}

	/*
	 * The CameraFilesystem doesn't support file info. We simply get
	 * the preview and the file and look for ourselves...
	 */

	/* It takes too long to get the file */
	info->file.fields = GP_FILE_INFO_NONE;

	/* Get the preview */
	info->preview.fields = GP_FILE_INFO_NONE;
	CRS (camera, gp_file_new (&cfile), context);
	if (gp_camera_file_get (camera, folder, file, GP_FILE_TYPE_PREVIEW,
						cfile, context) == GP_OK) {
		unsigned long size;
		info->preview.fields |= GP_FILE_INFO_SIZE | GP_FILE_INFO_TYPE;
		gp_file_get_data_and_size (cfile, &data, &size);
		info->preview.size = size;
		gp_file_get_mime_type (cfile, &mime_type);
		strncpy (info->preview.type, mime_type,
			 sizeof (info->preview.type));
	}
	gp_file_unref (cfile);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Sets some file properties like name or permissions.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param file the name of a file
 * @param info the #CameraFileInfo
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_file_set_info (Camera *camera, const char *folder,
			 const char *file, CameraFileInfo info,
			 GPContext *context)
{
	C_PARAMS (camera && folder && file);
	CHECK_INIT (camera, context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_set_info (camera->fs,
					folder, file, info, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Retrieves a file from the #Camera.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param file the name of a file
 * @param type the #CameraFileType
 * @param camera_file a #CameraFile
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_file_get (Camera *camera, const char *folder, const char *file,
		    CameraFileType type, CameraFile *camera_file,
		    GPContext *context)
{
	GP_LOG_D ("Getting file '%s' in folder '%s'...", file, folder);

	C_PARAMS (camera && folder && file && camera_file);
	CHECK_INIT (camera, context);

	CR (camera, gp_file_clean (camera_file), context);

	/* Did we get reasonable foldername/filename? */
	if (strlen (folder) == 0) {
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_DIRECTORY_NOT_FOUND);
	}
	if (strlen (file) == 0) {
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_FILE_NOT_FOUND);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_get_file (camera->fs,
			folder, file, type, camera_file, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Reads a file partially from the #Camera.
 *
 * @param camera a #Camera
 * @param folder a folder
 * @param file the name of a file
 * @param type the #CameraFileType
 * @param offset the offset into the camera file
 * @param data the buffer receiving the data
 * @param size the size to be read and that was read. (Note: size should not exceed 32 bits)
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_file_read (Camera *camera, const char *folder, const char *file,
		    CameraFileType type,
		    uint64_t offset, char *buf, uint64_t *size,
		    GPContext *context)
{
	GP_LOG_D ("Getting file '%s' in folder '%s'...", file, folder);

	C_PARAMS (camera && folder && file && buf && size);
	CHECK_INIT (camera, context);

	/* Did we get reasonable foldername/filename? */
	if (strlen (folder) == 0) {
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_DIRECTORY_NOT_FOUND);
	}
	if (strlen (file) == 0) {
		CAMERA_UNUSED (camera, context);
		return (GP_ERROR_FILE_NOT_FOUND);
	}

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_read_file (camera->fs,
			folder, file, type, offset, buf, size, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Deletes the file from \c folder.
 *
 * \param camera a #Camera
 * \param folder a folder
 * \param file the name of a file
 * \param context a #GPContext
 * \return a gphoto2 error code
 *
 **/
int
gp_camera_file_delete (Camera *camera, const char *folder, const char *file,
		       GPContext *context)
{
	GP_LOG_D ("Deleting file '%s' in folder '%s'...", file, folder);

	C_PARAMS (camera && folder && file);
	CHECK_INIT (camera, context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_delete_file (
				camera->fs, folder, file, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Creates a new directory called \c name in the given \c folder.
 *
 * @param camera a #Camera
 * @param folder the location where to create the new directory
 * @param name the name of the directory to be created
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 **/
int
gp_camera_folder_make_dir (Camera *camera, const char *folder,
			   const char *name, GPContext *context)
{
	C_PARAMS (camera && folder && name);
	CHECK_INIT (camera, context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_make_dir (camera->fs,
					folder, name, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * Removes an (empty) directory called \c name from the given \c folder.
 *
 * @param camera a #Camera
 * @param folder the folder from which to remove the directory
 * @param name the name of the directory to be removed
 * @param context a #GPContext
 * @return a gphoto2 error code
 *
 */
int
gp_camera_folder_remove_dir (Camera *camera, const char *folder,
			     const char *name, GPContext *context)
{
	C_PARAMS (camera && folder && name);
	CHECK_INIT (camera, context);

	CHECK_RESULT_OPEN_CLOSE (camera, gp_filesystem_remove_dir (camera->fs,
					folder, name, context), context);

	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * \brief Gets information on the camera attached storage.
 *
 * \param camera a #Camera
 * \param sifs Pointer to receive a pointer to/array of storage info items
 * \param nrofsifs Pointer to receive number of array entries
 * \param context a #GPContext
 * \return a gphoto2 error code
 *
 * Retrieves the storage information, like maximum and free space, for
 * the specified filesystem, if supported by the device. The storage
 * information is returned in an newly allocated array of
 * #CameraStorageInformation objects, to which the pointer pointed to
 * by #sifs will be set.
 *
 * The variable pointed to by #nrofsifs will be set to the number of
 * elements in that array.
 *
 * It is the caller's responsibility to free the memory of the array.
 *
 **/
int
gp_camera_get_storageinfo (
	Camera *camera, CameraStorageInformation **sifs,
	int *nrofsifs, GPContext *context)
{
	C_PARAMS (camera && sifs && nrofsifs);
	CHECK_INIT (camera, context);

	CHECK_RESULT_OPEN_CLOSE (camera,
		gp_filesystem_get_storageinfo (
			camera->fs, sifs, nrofsifs, context
		),
		context
	);
	CAMERA_UNUSED (camera, context);
	return (GP_OK);
}

/**
 * @param camera a Camera
 * @param start_func
 * @param stop_func
 * @param data
 * @return a gphoto2 error code
 *
 * If your frontend has something like idle loops, it is recommended you
 * use #gp_camera_set_timeout_funcs in order to give the camera driver the
 * possibility to keep up the connection to the camera.
 *
 */
void
gp_camera_set_timeout_funcs (Camera *camera, CameraTimeoutStartFunc start_func,
			     CameraTimeoutStopFunc stop_func,
			     void *data)
{
	if (!camera || !camera->pc)
		return;

	camera->pc->timeout_start_func = start_func;
	camera->pc->timeout_stop_func = stop_func;
	camera->pc->timeout_data = data;
}


/**
 * @param camera a #Camera
 * @param timeout number of seconds that should pass between each call to
 * 	     \c func
 * @param func the function that should be called each \c timeout seconds
 * @return The id of the background process or a gphoto2 error code
 *
 * This function should be called by the camera driver during camera_init()
 * if the camera needs to be sent messages periodically in order to prevent
 * it from shutting down.
 *
 */
int
gp_camera_start_timeout (Camera *camera, unsigned int timeout,
			 CameraTimeoutFunc func)
{
	int id;

	C_PARAMS (camera && camera->pc);

	if (!camera->pc->timeout_start_func)
		return (GP_ERROR_NOT_SUPPORTED);

	/*
	 * We remember the id here in order to automatically remove
	 * the timeout on gp_camera_exit.
	 */
	C_MEM (camera->pc->timeout_ids =
			realloc (camera->pc->timeout_ids, sizeof (int) *
					(camera->pc->timeout_ids_len + 1)));

	id = camera->pc->timeout_start_func (camera, timeout, func,
					     camera->pc->timeout_data);
	if (id < 0)
		return (id);
	camera->pc->timeout_ids[camera->pc->timeout_ids_len] = id;
	camera->pc->timeout_ids_len++;

	return (id);
}


/**
 * Stop periodic calls to keepalive function.
 *
 * @param camera a #Camera
 * @param id the id of the background process previously returned by
 *       #gp_camera_start_timeout
 *
 * Call this function in the camera driver if you want to stop a periodic
 * call to a function that has been started using #gp_camera_start_timeout.
 *
 */
void
gp_camera_stop_timeout (Camera *camera, unsigned int id)
{
	unsigned int i;

	if (!camera || !camera->pc)
		return;

	if (!camera->pc->timeout_stop_func)
		return;

	/* Check if we know this id. If yes, remove it. */
	for (i = 0; i < camera->pc->timeout_ids_len; i++)
		if (camera->pc->timeout_ids[i] == id)
			break;
	if (i == camera->pc->timeout_ids_len)
		return;
	memmove (camera->pc->timeout_ids + i, camera->pc->timeout_ids + i + 1,
		 sizeof (int) * (camera->pc->timeout_ids_len - i - 1));
	camera->pc->timeout_ids_len--;
	camera->pc->timeout_ids = realloc (camera->pc->timeout_ids,
				sizeof (int) * camera->pc->timeout_ids_len);

	camera->pc->timeout_stop_func (camera, id, camera->pc->timeout_data);
}
