/* unieject - Universal eject command
   Copyright (C) 2005-2006, Diego Pettenò

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with unieject; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <config.h>

#include <unieject_internal.h>
#include <unieject.h>

#include <unistd.h>

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

#include <cdio/cdio.h>
#include <cdio/mmc.h>
#include <cdio/logging.h>

#include <popt.h>

#ifdef HAVE_LIBCONFUSE
#include <confuse.h>
#endif

#include <gettext.h>
#ifdef HAVE_SETLOCALE
#include <locale.h>
#endif

struct unieject_opts opts;
static poptContext optCon = NULL;

enum {
	OP_IGNORE,
	OP_DEFAULT, // --default
	OP_SPEED,
	OP_CHANGER,
	OP_VERSION,
	OP_LOCK,
	OP_UNLOCK,
	OP_TOGGLE,
	OP_ERROR
};

void init_opts() __attribute__((constructor));
void cleanup() __attribute__((destructor));

/* Initialize the default options (set as constructor) */
void init_opts()
{
	opts.eject = 1;
	opts.fake = 0;
	opts.verbose = 0;
	opts.unmount = 1;
	opts.speed = 0;
	opts.force = 0;
	opts.caps = 1;
	opts.device = NULL;
	opts.umount_wrapper = NULL;
	opts.accessmethod = NULL;
}

void cleanup()
{
	free(opts.progname);
	free(opts.device);
	if ( opts.cdio ) cdio_destroy((CdIo_t*)opts.cdio);
	if ( optCon ) poptFreeContext(optCon);
}

#ifdef HAVE_LIBCONFUSE
static void parse_configuration()
{
	cfg_opt_t cfgopts[] =
	{
		CFG_SIMPLE_STR("device", &opts.device),
		CFG_SIMPLE_INT("verbosity", &opts.verbose),
		CFG_SIMPLE_BOOL("unmount", &opts.unmount),
		CFG_SIMPLE_BOOL("force", &opts.force),
		CFG_SIMPLE_STR("accessmethod", &opts.accessmethod),
		CFG_SIMPLE_INT("debugcdio", &cdio_loglevel_default),
		CFG_SIMPLE_BOOL("respect-capabilities", &opts.caps),
		CFG_SIMPLE_STR("unmount-wrapper", &opts.umount_wrapper),
		CFG_END()
	};
	
	cfg_t *cfg = cfg_init(cfgopts, CFGF_NONE);
	if ( cfg_parse(cfg, SYSCONFDIR "/unieject.conf") == CFG_PARSE_ERROR )
	{
		unieject_error(opts, _("Error parsing configuration file %s\n"), SYSCONFDIR "/unieject.conf");
		cfg_free(cfg);
		exit(-5);
	}
	
	char *userconf;
	asprintf(&userconf, "%s/.unieject", getenv("HOME"));
	if ( cfg_parse(cfg, userconf) == CFG_PARSE_ERROR )
	{
		unieject_error(opts, _("Error parsing configuration file %s\n"), userconf);
		free(userconf);
		cfg_free(cfg);
		exit(-5);
	}
	
	free(userconf);
	cfg_free(cfg);
}
#endif

/* Parse a all options. */
static int parse_options (int argc, const char *argv[])
{
	int8_t tmpopt, opt = OP_IGNORE; /* used for argument parsing */
	
	struct poptOption optionsTable[] = {
		{ "trayclose",		't', POPT_ARG_VAL, &opts.eject, 0,
		  gettext_noop("Close CD-Rom tray."), NULL },
		{ "traytoggle",		'T', POPT_ARG_NONE, NULL, OP_TOGGLE,
		  gettext_noop("Toggle tray open/close."), NULL },
		{ "speed",		'x', POPT_ARG_INT, &opts.speed, OP_SPEED,
		  gettext_noop("Set CD-Rom max speed."),
		  "max_speed" },
		{ "changerslot",	'c', POPT_ARG_NONE, NULL, OP_CHANGER,
		  gettext_noop("Switch discs on a CD-ROM changer."),
		  "changer" },
		{ "lock",		'l', POPT_ARG_NONE, NULL, OP_LOCK,
		  gettext_noop("Lock the CD-Rom drive."), NULL },
		{ "unlock",		'L', POPT_ARG_NONE, NULL, OP_UNLOCK,
		  gettext_noop("Unlock the CD-Rom drive."), NULL },
		
		{ "noop", 		'n', POPT_ARG_VAL, &opts.fake, 1,
		  gettext_noop("Don't eject, just show device found."), NULL },
		{ "default",		'd', POPT_ARG_NONE, NULL, OP_DEFAULT,
		  gettext_noop("Display default device."), NULL },
		
		{ "verbose",		'v', POPT_ARG_VAL, &opts.verbose, 1,
		  gettext_noop("Enable verbose output."), NULL },
		{ "quiet",		'Q', POPT_ARG_VAL, &opts.verbose, -1,
		  gettext_noop("Disable output of error messages."), NULL },
		
		{ "no-unmount",		'm', POPT_ARG_VAL, &opts.unmount, 0,
		  gettext_noop("Do not umount device even if it is mounted."), NULL },
		{ "unmount",		'u', POPT_ARG_VAL, &opts.unmount, 1,
		  gettext_noop("Unmount device if mounted (default behavior)."), NULL },
		
		{ "force",		'f', POPT_ARG_VAL, &opts.force, 1,
		  gettext_noop("Force unmount of device."), NULL },
		{ "no-force",		0, POPT_ARG_VAL, &opts.force, 0,
		  gettext_noop("Don't force unmount of device (default behavior)."), NULL },
		
		{ "ignore-caps",	0, POPT_ARG_VAL, &opts.caps, 0,
		  gettext_noop("Ignore capabilities as reported by device."), NULL },
		{ "no-ignore-caps",	0, POPT_ARG_VAL, &opts.caps, 1,
		  gettext_noop("Don't ignore capabilities reported by device (default behavior)."), NULL },
		
		{ "umount-wrapper",	'W', POPT_ARG_STRING, &opts.umount_wrapper, OP_IGNORE,
		  gettext_noop("Use an umount wrapper to unmount."),
		  "wrapper" },
		
		{ "accessmethod", 	'A', POPT_ARG_STRING, &opts.accessmethod, OP_IGNORE,
		  gettext_noop("Select the access method for libcdio."),
		  "method" },
		{ "debugcdio",		'D', POPT_ARG_INT, &cdio_loglevel_default, OP_IGNORE,
		  gettext_noop("Set debugging level for libcdio."),
		  "level" },
		
		{ "version",		'V', POPT_ARG_NONE, NULL, OP_VERSION,
		  gettext_noop("Display version and copyright information and exit."), NULL },
		
		{ "proc",		'p', POPT_ARG_NONE, NULL, OP_IGNORE,
		  gettext_noop("Ignored (classic eject compatibility)."), NULL },
		{ "tape",		'q', POPT_ARG_NONE, NULL, OP_IGNORE,
		  gettext_noop("Ignored"), NULL },
		{ "floppy",		'f', POPT_ARG_NONE, NULL, OP_IGNORE,
		  gettext_noop("Ignored"), NULL },
		{ "cdrom",		'r', POPT_ARG_NONE, NULL, OP_IGNORE,
		  gettext_noop("Ignored"), NULL },
		{ "scsi",		's', POPT_ARG_NONE, NULL, OP_IGNORE,
		  gettext_noop("Ignored"), NULL },
		{ "auto",		'a', POPT_ARG_NONE, NULL, OP_IGNORE,
		  gettext_noop("Ignored"), NULL },
		POPT_AUTOHELP {NULL, 0, 0, NULL, 0, NULL, NULL}
	};

	optCon = poptGetContext (NULL, argc, argv, optionsTable, 0);
	
	opts.progname = strrchr(argv[0],'/');
	opts.progname = opts.progname ? strdup(opts.progname+1) : strdup(argv[0]);
	
	while ((tmpopt = poptGetNextOpt (optCon)) >= 0)
	{
		if ( tmpopt == OP_IGNORE ) continue;
		
		if ( opt != OP_IGNORE )
		{
			unieject_error(opts, _("you can use just one of -x, -c, -l, -L and -d options\n"));
			return OP_ERROR;
		} else
			opt = tmpopt;
	}
	
	if (opt < -1)
	{
		/* an error occurred during option processing */
		fprintf(stderr, "%s: %s\n",
			poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
			poptStrerror(opt));
		
		exit(-1);
	}
	
	const char *arg_device = poptGetArg(optCon);
	if ( ! arg_device )
		arg_device = opts.device;
	
	if ( poptGetArg(optCon) )
		unieject_verbose(opts, _("further non-option arguments ignored.\n"));
	
	opts.device = libunieject_getdevice(opts, arg_device);
	
	return opt;
}

int main(int argc, const char *argv[])
{
#ifdef HAVE_SETLOCALE
	setlocale (LC_ALL, "");
#endif

#ifdef ENABLE_NLS
	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);
#endif

#ifdef HAVE_LIBCONFUSE
	parse_configuration();
#endif
	int what = parse_options(argc, argv);
	
	/*
	 * To make simpler the user experience with unieject, it's possible to
	 * provide a "loadcd" symlink or alias that defaults to trayclose
	 * instead of eject.
	 */
	if ( strcmp("loadcd", opts.progname) == 0 )
	{
		unieject_verbose(opts, _("default to closing tray instead of eject.\n"));
		opts.eject = 0;
	}
	
	// First switch, non-device options
	switch(what)
	{
	case OP_DEFAULT: {
			char *default_device = libunieject_defaultdevice();
			printf(_("%s: default device: `%s'\n"), opts.progname, default_device);
			
			return 0;
		}
	case OP_VERSION: {
			printf(_("unieject version %s\n"), PACKAGE_VERSION);
			printf("Copyright (C) 2005-2006 Diego Pettenò\n");
			printf("This is free software.  You may redistribute copies of it under the terms of\n"
				"the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n"
				"There is NO WARRANTY, to the extent permitted by law.\n");

			return 0;
		}
	case OP_CHANGER:
	case OP_TOGGLE:
	case OP_IGNORE:
		if ( ! libunieject_umountdev(opts, opts.device) )
		{
			unieject_error(opts, _("unable to unmount device '%s'.\n"), opts.device);
			return -4;
		}
		break;
	case OP_ERROR:
		return -1;
	}
	
	if ( UNLIKELY(! libunieject_open(&opts)) )
		return -1;

	int retval;
	switch(what)
	{
		case OP_SPEED:
			retval = libunieject_setspeed(opts);
			break;
		case OP_CHANGER:
			retval = libunieject_slotchange(opts);
			break;
		case OP_LOCK:
			retval = libunieject_togglelock(&opts, 1);
			break;
		case OP_UNLOCK:
			retval = libunieject_togglelock(&opts, 0);
			break;
		case OP_TOGGLE:
			retval = libunieject_traytoggle(&opts);
		default:
			retval = libunieject_eject(&opts);
	}
	
	return retval;
}


syntax highlighted by Code2HTML, v. 0.9.1