// scanbuttond.c: the actual daemon ("frontend")
// This file is part of scanbuttond.
// Copyleft )c( 2004-2006 by Bernhard Stiftner
//
// 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 this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <syslog.h>
#include <errno.h>
#include <getopt.h>
#include "scanbuttond/config.h"
#include "scanbuttond/common.h"
#include "scanbuttond/scanbuttond.h"
#include "scanbuttond/loader.h"

#define DEF_BACKEND_FILENAME		STRINGIFY(LIB_DIR) "/libscanbtnd-backend_meta.so"
#define DEF_BUTTONPRESSED_SCRIPT	STRINGIFY(CFG_DIR) "/buttonpressed.sh"
#define DEF_INITSCANNER_SCRIPT		STRINGIFY(CFG_DIR) "/initscanner.sh"
#define DEF_POLL_DELAY			333000L
#define MIN_POLL_DELAY			1000L
#define DEF_RETRY_DELAY			2000000L
#define MIN_RETRY_DELAY			10000L
#define BUF_SIZE			256

static char* connection_names[NUM_CONNECTIONS] =
{ "none", "libusb" };


static struct option const long_opts[] = {
	{"foreground", no_argument, NULL, 'f'},
	{"backend", required_argument, NULL, 'b'},
	{"buttonscript", required_argument, NULL, 's'},
	{"initscript", required_argument, NULL, 'S'},
	{"pollingdelay", required_argument, NULL, 'p'},
	{"retrydelay", required_argument, NULL, 'r'},
	{"help", no_argument, NULL, 'h'},
	{"version", no_argument, NULL, 'v'},
	{NULL, 0, NULL, 0}
};


char* buttonpressed_script;
char* initscanner_script;
char* backend_filename;
backend_t* backend;
long poll_delay;
long retry_delay;
int daemonize;
int killed = 0;
char* path;


char* scanbtnd_get_connection_name(int connection)
{
	return connection_names[connection];
}


void shutdown(void)
{
	syslog(LOG_INFO, "shutting down...");
	backend->scanbtnd_exit();
	unload_backend(backend);
	syslog(LOG_DEBUG, "shutdown complete");
	closelog();
}


// Ensures a graceful exit on SIGHUP/SIGTERM/SIGINT/SIGSEGV
void sighandler(int i)
{
	killed = 1;
	syslog(LOG_INFO, "received signal %d", i);
	shutdown();
	exit(i == SIGTERM ? EXIT_SUCCESS : EXIT_FAILURE);
}


// Executes an external program and wait until it terminates
void execute_and_wait(const char* program)
{
	system(program);
}


void list_devices(scanner_t* devices)
{
	scanner_t* dev = devices;
	while (dev != NULL) {
		syslog(LOG_INFO, "found scanner: vendor=\"%s\", product=\"%s\", connection=\"%s\", sane_name=\"%s\"",
			   dev->vendor, dev->product, scanbtnd_get_connection_name(dev->connection),
			   backend->scanbtnd_get_sane_device_descriptor(dev));
		dev = dev->next;
	}
}


void show_version(void)
{
	printf("This is scanbuttond, version %s\n", VERSION);
	printf("Copyleft )c( 2004-2006 by Bernhard Stiftner and contributors.\n");
	printf("Scanbuttond comes with ABSOLUTELY NO WARRANTY!\n");
	printf("This is free software, and you are welcome to redistribute it\n");
	printf("under certain conditions; see the file COPYING for details.\n");
}


void show_usage(void)
{
	printf("Usage: scanbuttond [OPTION]...\n\n");
	printf("Starts a script when a button on a scanner has been pressed.\n\n");
	printf("Options:\n");
	printf("  -f, --foreground            Run in foreground instead of background\n");
	printf("  -b, --backend=FILE          Use the specified backend library file\n");
	printf("                              default: %s\n", DEF_BACKEND_FILENAME);
	printf("  -s, --buttonscript=SCRIPT   The name of the script to be run when a button has been pressed\n");
	printf("                              default: %s\n", DEF_BUTTONPRESSED_SCRIPT);
	printf("  -S, --initscript=SCRIPT     The name of the script to be run to initialize the scanners\n");
	printf("                              default: %s\n", DEF_INITSCANNER_SCRIPT);
	printf("  -p, --pollingdelay=DELAY    The polling delay (ms), default: %ld\n", DEF_POLL_DELAY);
	printf("  -r, --retrydelay=DELAY      The retry delay (ms), default: %ld\n", DEF_RETRY_DELAY);
	printf("  -h, --help                  Shows this screen\n");
	printf("  -v, --version               Shows the version\n");
}


void process_options(int argc, char** argv)
{
	int c;

	buttonpressed_script = NULL;
	initscanner_script = NULL;
	poll_delay = -1;
	retry_delay = -1;
	daemonize = 1;

	while ((c = getopt_long (argc, argv, "fb:s:S:p:r:hv", long_opts, NULL)) != -1) {
		switch (c) {
			case 'f':
				daemonize = 0;
				break;
			case 'b':
				backend_filename = optarg;
				break;
			case 's':
				buttonpressed_script = optarg;
				break;
			case 'S':
				initscanner_script = optarg;
				break;
			case 'p':
				poll_delay = atol(optarg);
				if (poll_delay < MIN_POLL_DELAY) {
					printf("Invalid polling delay (%ld). Must be at least %ld.\n",
						   poll_delay, MIN_POLL_DELAY);
					exit(EXIT_FAILURE);
				}
				break;
			case 'r':
				retry_delay = atol(optarg);
				if (retry_delay < MIN_RETRY_DELAY) {
					printf("Invalid retry delay (%ld). Must be at least %ld.\n",
						   retry_delay, MIN_RETRY_DELAY);
					exit(EXIT_FAILURE);
				}
				break;
			case 'h':
				show_usage();
				exit(EXIT_SUCCESS);
				break;
			case 'v':
				show_version();
				exit(EXIT_SUCCESS);
				break;
		}
	}

	if (backend_filename == NULL)
		backend_filename = DEF_BACKEND_FILENAME;
	if (buttonpressed_script == NULL)
		buttonpressed_script = DEF_BUTTONPRESSED_SCRIPT;
	if (initscanner_script == NULL)
		initscanner_script = DEF_INITSCANNER_SCRIPT;
	if (poll_delay == -1)
		poll_delay = DEF_POLL_DELAY;
	if (retry_delay == -1)
		retry_delay = DEF_RETRY_DELAY;
}


int main(int argc, char** argv)
{
	int button;
	int result;
	pid_t pid, sid;
	scanner_t* scanners;
	scanner_t* scanner;

	process_options(argc, argv);

	backend = load_backend(backend_filename);
	if (!backend) {
		printf("Unable to load backend library \"%s\"!\n", backend_filename);
		exit(EXIT_FAILURE);
	}

	openlog(NULL, 0, LOG_DAEMON);

	// daemonize
	if (daemonize) {
		if (daemon(0, 0) != 0) {
			syslog(LOG_ERR, "daemon() failed.");
		}
	}
/*
		pid = fork();
		if (pid < 0) {
			printf("Can't fork!\n");
			exit(EXIT_FAILURE);
		} else if (pid > 0) {
			exit(EXIT_SUCCESS);
		}
	}

	umask(0);

	openlog(NULL, 0, LOG_DAEMON);

	// create a new session for the child process
	if (daemonize) {
		sid = setsid();
		if (sid < 0) {
			syslog(LOG_ERR, "Could not create a new SID! Terminating.");
			exit(EXIT_FAILURE);
		}
	}

	// Change the current working directory
	if ((chdir("/")) < 0) {
		syslog(LOG_WARNING, "Could not chdir to /. Hmmm, strange... "\
				"Trying to continue.");
	}

	// close standard file descriptors
	if (daemonize) {
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);
	}
*/

	// setup the environment
	char* oldpath = getenv("PATH");
	char* dir = dirname(argv[0]);
	path = (char*)malloc(strlen(oldpath) + strlen(dir) + 1);
	strcpy(path, oldpath);
	strcat(path, ":");
	strcat(path, dir);
	setenv("PATH", path, 1);
	free(path);

	syslog(LOG_DEBUG, "running scanner initialization script...");
	execute_and_wait(initscanner_script);
	syslog(LOG_DEBUG, "initialization script executed.");

	if (backend->scanbtnd_init() != 0) {
		syslog(LOG_ERR, "Error initializing backend. Terminating.");
		exit(EXIT_FAILURE);
	}

	scanners = backend->scanbtnd_get_supported_devices();

	if (scanners == NULL) {
		syslog(LOG_WARNING, "no known scanner found yet, " \
				"waiting for device to be attached");
	}

	list_devices(scanners);

	signal(SIGTERM, &sighandler);
	signal(SIGHUP, &sighandler);
	signal(SIGINT, &sighandler);
	signal(SIGSEGV, &sighandler);
	signal(SIGCHLD, SIG_IGN);

	syslog(LOG_INFO, "scanbuttond started");

	// main loop
	while (killed == 0) {

		if (scanners == NULL) {
			syslog(LOG_DEBUG, "rescanning devices...");
			backend->scanbtnd_rescan();
			scanners = backend->scanbtnd_get_supported_devices();
			if (scanners == NULL) {
				syslog(LOG_DEBUG, "no supported devices found. rescanning in a few seconds...");
				usleep(retry_delay);
				continue;
			}
			syslog(LOG_DEBUG, "found supported devices. running scanner initialization script...");
			execute_and_wait(initscanner_script);
			syslog(LOG_DEBUG, "initialization script executed.");
			scanners = backend->scanbtnd_get_supported_devices();
			continue;
		}

		scanner = scanners;
		while (scanner != NULL) {
			result = backend->scanbtnd_open(scanner);
			if (result != 0) {
				syslog(LOG_WARNING, "scanbtnd_open failed, error code: %d", result);
				if (result == -ENODEV) {
					// device has been disconnected, force re-scan
					syslog(LOG_INFO, "scanbtnd_open returned -ENODEV, device rescan will be performed");
					scanners = NULL;
					usleep(retry_delay);
					break;
				}
				usleep(retry_delay);
				break;
			}

			button = backend->scanbtnd_get_button(scanner);
			backend->scanbtnd_close(scanner);

			if ((button > 0) && (button != scanner->lastbutton)) {
				syslog(LOG_INFO, "button %d has been pressed.", button);
				scanner->lastbutton = button;
				char cmd[BUF_SIZE];
				snprintf(cmd, BUF_SIZE, "%s %d %s", buttonpressed_script, button,
						 backend->scanbtnd_get_sane_device_descriptor(scanner));
				execute_and_wait(cmd);
			}
			if ((button == 0) && (scanner->lastbutton > 0)) {
				syslog(LOG_INFO, "button %d has been released.", scanner->lastbutton);
				scanner->lastbutton = button;
			}
			scanner = scanner->next;
		}

		usleep(poll_delay);

	}

	syslog(LOG_WARNING, "exited main loop!?!");

	shutdown();
	exit(EXIT_SUCCESS);
}



syntax highlighted by Code2HTML, v. 0.9.1