/*---------------------------------------------------------------------------

 Copyright (c) 2006
	by Dirk Meyer, All rights reserved.
	Im Grund 4, 34317 Habichtswald, Germany
	Email: dirk.meyer@dinoex.sub.org

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
 3. Neither the name of the author nor the names of any co-contributors
    may be used to endorse or promote products derived from this software
    without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

-----------------------------------------------------------------------------

	bilderhash
	==========

	$Id: bilderhash.c,v 1.14 2008/03/09 01:41:41 dm Exp $

----------------------------------------------------------------------------*/

#ifdef WITH_DEVCC
#define WITHOUT_SYSEXITS_H	1
#define WITHOUT_ERR_H		1
#define WITH_OPENSSL
#endif

#if defined(linux)
#define WITH_OPENSSL
#endif

#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#ifndef WITHOUT_SYSEXITS_H
#include <sysexits.h>
#else
#include "sysexits.h"
#endif
#ifndef WITHOUT_ERR_H
#include <err.h>
#else
#define err(...)	{}
#define errx(...)	{}
#define warn(...)	{}
#define warnx(...)	{}
#endif
#include <dirent.h>

#if defined(linux) || defined(__CYGWIN__)
#include <sys/file.h>
#endif

#include <fcntl.h>
#ifndef WITH_SQLITE
#if defined(linux) || defined(__CYGWIN__)
#include <db_185.h>
#else
#include <db.h>
#endif
#else
#include <sqlite3.h>
#endif

#ifdef WITH_OPENSSL
#include <openssl/md5.h>
#define	MD5Init(c)		MD5_Init(c)
#define	MD5Update(c,b,l)	MD5_Update(c,b,l)
#define	MD5Final(d,c)		MD5_Final(d,c)
#else
#include <md5.h>
#endif

#undef WITH_UDP
#define WITH_UDP
#undef WITH_UDP_PING
#define WITH_UDP_PING

#define BLOCKSIZE	4096
#define	MAX_BUF		40000
#define	MAX_KEY		256
#define	MIN_DELAY	3
#define	MAX_DELAY	31

#define	NO		0
#define	YES		1
#define	GET_NEXT_DATA(x)	{ argv++; argc--; x = *argv; \
				if (x == NULL) usage(); }

#define CT_NO_DATA		-1L
#define CT_STRING		0
#define CT_INT			1
#define CT_LONG			2
#define CT_SIZE			3
#define CT_LOWER_STRING		4

#define MATCH_BOTH	0
#define MATCH_IHAVE	1
#define MATCH_SENDME	2

#define ORDER_NONE	0
#define ORDER_SORT	1
#define ORDER_DATA	2

#ifdef linux
#define	strlcpy(x,y,z)		{ strncpy(x,y,z);x[(z)-1]=0; }
#endif

#ifdef __CYGWIN__
#define strcasestr(x,y)		strstr(x,y)
#endif

#ifdef WITH_DEVCC
#define	strlcpy(x,y,z)		{ strncpy(x,y,z);x[(z)-1]=0; }
#define strcasestr(x,y)		strstr(x,y)
#define snprintf		_snprintf
#define lstat(x,y)		stat(x,y)
#define main			main_command
#endif

typedef union {
	const void	*vvar;
	const char	**cvar;
	int		*ivar;
	long		*lvar;
	size_t		*tvar;
} VAR_POINTER;

typedef struct {
	const int	typ;
	VAR_POINTER	u;
	const char	*name;
	const char	*def;
} CONFIG_TYP;

typedef char md5_t[16];

typedef void ( *f_process)(const char *key);

#ifdef WITH_MAGIC
typedef struct {
	const char *name;
	const char *detect;
} MAGIC_TYP;
#endif

void string_to_lowercase(char *buffer);

FILE *file_open(const char *datei, const char *mode);
void file_seek(FILE *handle, long bytes, int mode);
long file_size(FILE *handle);
void file_read(void *buffer, size_t bytes, size_t count, FILE *handle);

char *local_read(const char *name);

void config_sorted(CONFIG_TYP *config);
void config_default(CONFIG_TYP *config);
long config_find(CONFIG_TYP *config, const char *key);
int config_set_var(CONFIG_TYP *config, const char *key, char *value);
void config_parse_line(CONFIG_TYP *config, const char *parameter);
char *config_read(const char *filename);
void config_check(void);

void localdb_cleanup(const char *name, time_t valid_from);
void localdb_delete(const char *name, const char *hash);
void localdb_write(const char *name, const char *hash, const char *value);
void localdb_write_raw(const char *name, const char *hash, const char *value);
char *localdb_read(const char *name, const char *hash);
size_t localdb_list_keys(const char *name, char *buffer, size_t max);
size_t localdb_dump(const char *name, char *buffer, size_t max);

int strcmp_data(char *a, char *b);
void a_bottom_up_reheap(char **feld, long max, long aindex, int order);
void a_bottom_up_heap_sort(char **feld, long anzahl, int order);
int filetag_to_key(const char *filetag, char **size, char **hash);
void digest_to_string(char *buffer, const char *digest);
int generate_hash(const char *filename, char **size, char **hash);
int filename_to_key(const char *filename, char **size, char **hash);
void print_date(const char *prefix, const char *seconds);
int show_text(const char *const *info, const char *key, const char *data);

size_t count_lines_in_buffer(const char *buffer);
size_t text_to_table(char **table, char *buffer);
void print_table(char **table, size_t lines);
void load_table_from_buffer(char ***table, size_t *lines, char *buffer, size_t found, int order);
void load_table_from_file(char ***table, size_t *lines, char **buffer, const char *keyfile, int order);
void load_table_from_db(char ***table, size_t *lines, char **buffer, const char *dbfile, int order);
void load_data_from_db(char ***table, size_t *lines, char **buffer, const char *dbfile, int order);
void print_common_lines(char **table1, size_t lines1, char **table2, size_t lines2);
void print_table1_lines(char **table1, size_t lines1, char **table2, size_t lines2);
void print_table2_lines(char **table1, size_t lines1, char **table2, size_t lines2);
void compare_keys(const char *keyfile, int typ);
void list_filenames(const char *keyfile);
void export_keys(const char *dbname);
void export_data(const char *dbname);
void print_common_data(char **table, size_t lines);
void duplicate_data(const char *dbname);
void print_clean_missing(char **table, size_t lines, const char *dbname);
void clean_missing(const char *dbname);

int run_listfile(const char *listname, f_process process);
int run_directory(const char *dirname, f_process process);
int check_if_file_recorded(const char *filename);
void hash_addfiles(const char *key);
void hash_newfiles(const char *key);
void hash_oldfiles(const char *key);
void hash_showfiles(const char *key);
void hash_testfiles(const char *key);

void usage(void);
void command_config(int argc, const char *const *argv);
void command_options(int argc, const char *const *argv);
void command_run(int argc, const char *const *argv);

static const char *Config_file = NULL;
static const char *Date_format = NULL;
static const char *Myfiles_db = NULL;
static const char *Names_db = NULL;
static int Debug = NO;
static int Verbose = NO;
static int Quiet = NO;
static int Batch_mode;
static int Cache_ignore;

static char fbuf[MAX_BUF];
static char cbuf[MAX_KEY];
static char kbuf[MAX_KEY];
static char ksize[MAX_KEY];
static char khash[MAX_KEY];
static char kname[MAX_KEY];
static char ktime[MAX_KEY];

static md5_t digestmd5;
static unsigned char digestbuf[BLOCKSIZE];

static const char C_CACHED[] = "cached";

static long Config_box_anzahl;
CONFIG_TYP Config_box[] = {
{ -1, { &Config_box_anzahl   }, "_Config_box_anzahl", NULL },
{ 1, { &Batch_mode           }, "Batch_mode",         "0" },
{ 1, { &Cache_ignore         }, "Cache_ignore",       NULL },
{ 0, { &Config_file          }, "Config_file",        ".bilderhash" },
{ 0, { &Date_format          }, "Date_format",        "%Y-%m-%d %H:%M:%S" },
{ 1, { &Debug                }, "Debug",              NULL },
{ 0, { &Myfiles_db           }, "Myfiles_db",         "myfiles.db" },
{ 0, { &Names_db             }, "Names_db",           "names.db" },
{ 1, { &Quiet                }, "Quiet",              NULL },
{ 1, { &Verbose              }, "Verbose",            NULL },
{ -1, { NULL }, NULL, NULL }
};

#ifdef WITH_MAGIC
MAGIC_TYP magic[] = {
{ "jpeg", "\xff\xd8" },
{ "png", "\x89PNG" },
{ "gif87", "GIF87a" },
{ "gif89", "GIF89a" },
{ "bmp", "BM" },
{ NULL, NULL }
};
#endif

static const char *response_myfiles[] = {
	"fullpath",
	"filedate",
	"directory",
	"storage",
	"source",
	"artists",
	"created",
	"filname",
	"directory",
	"volume",
	"url",
	"distribution"
};


void
string_to_lowercase(char *buffer)
{
	char *work;

	if (buffer == NULL)
		return;
	for (work = buffer; *work != 0; work ++) {
		if (isupper(*work))
			*work = tolower(*work);
	}
}

FILE *
file_open(const char *datei, const char *mode)
{
	FILE *handle;

	if (datei == NULL)
		return (NULL);

	handle = fopen(datei, mode);
	if (handle == NULL) {
		if (errno != ENOENT)
			err(EX_NOINPUT, "cannot open file %s", datei);
	}
	return handle;
}

void
file_seek(FILE *handle, long bytes, int mode)
{
	int seek_status;

	seek_status = fseek(handle, bytes, mode);
	if (seek_status != 0)
		err(EX_IOERR, "cannot seek file, state=%d, pos=%ld, mode=%d",
			seek_status, bytes, mode);
}

long
file_size(FILE *handle)
{
	long size;

	file_seek(handle, 0L, SEEK_END);
	size = ftell(handle);
	file_seek(handle, 0L, SEEK_SET);
	if (size < 0L)
		err(EX_IOERR, "cannot read length of file, state=%ld",
			size);
	return size;
}

void
file_read(void *buffer, size_t bytes, size_t count, FILE *handle)
{
	size_t read_status;

	read_status = fread(buffer, bytes, count, handle);
	if (read_status != count)
		err(EX_IOERR, "cannot read %ld bytes from file, count=%ld got=%ld",
			(long)bytes, (long)count, (long)read_status);
}


char *
local_read(const char *name)
{
	size_t size;
	FILE *handle;
	char *buffer;

	handle = file_open(name, "rb");
	if (handle == NULL)
		return NULL;
	size = file_size(handle);
	if (size <= 0)
		return NULL;
	buffer = (char*) malloc(size + 1);
	file_read(buffer, size, 1, handle);
	fclose(handle);
	return buffer;
}


void
config_sorted(CONFIG_TYP *config)
{
	long i;
	int flag;

	for (i = 1L; config[i].typ != -1; i ++) {
		if (config[i + 1].typ == -1)
			break;
		flag = strcasecmp(config[i].name, config[i + 1].name);
		if (flag < 0)
			continue;

		errx(EX_CONFIG, "config structure defunct, <%s> >= <%s>",
			config[i].name, config[i + 1].name);
	};
	*(config[0].u.lvar) = i + 1;
}

void
config_default(CONFIG_TYP *config)
{
	long i;

	config_sorted(config);
	for (i = 1L; config[i].typ != -1; i ++) {
		switch (config[i].typ) {
		case CT_STRING:
		case CT_LOWER_STRING:
			*(config[i].u.cvar) =
				config[i].def;
			break;
		case CT_INT:
			*(config[i].u.ivar) =
				(config[i].def != NULL ?
				atoi(config[i].def) : 0);
			break;
		case CT_LONG:
		case CT_SIZE:
			*(config[i].u.lvar) =
				(config[i].def != NULL ?
				atol(config[i].def) : 0);
			break;
		}
	}
}

long
config_find(CONFIG_TYP *config, const char *key)
{
	int how_far;
	long bin_mid;
	long bin_low;
	long bin_high;
	long anzahl;

	anzahl = *(config[0].u.lvar);
	if (anzahl > 0L) {
		bin_low = 0;
		bin_high = anzahl - 1;
		while (bin_low <= bin_high) {
			bin_mid = (bin_low + bin_high) / 2;
			how_far = strcasecmp(config[bin_mid].name, key);
			if (how_far == 0)
				return bin_mid;
			if (how_far < 0)
				bin_low = bin_mid + 1;
			else
				bin_high = bin_mid - 1;
		}
	};
	return CT_NO_DATA;
}

int
config_set_var(CONFIG_TYP *config, const char *key, char *value)
{
	long i;

	i = config_find(config, key);
	if (i > 0L) {
		switch (config[i].typ) {
		case CT_LOWER_STRING:
			string_to_lowercase(value);
			/* FALLTHROUGH */
		case CT_STRING:
			*(config[i].u.cvar) =
				value;
			break;
		case CT_INT:
			*(config[i].u.ivar) =
				atoi(value);
			break;
		case CT_LONG:
		case CT_SIZE:
			*(config[i].u.lvar) =
				atol(value);
			break;
		};
		return YES;
	};
	return NO;
}

void
config_parse_line(CONFIG_TYP *config, const char *parameter)
{
	int found;
	char *buffer;
	char *key;
	char *value;
	char *work;

	if (parameter[0] == '#')
		return;

	buffer = strdup(parameter);
	key = buffer;

	value = strchr(buffer, '=');
	if (value == NULL)
		errx(EX_CONFIG, "Key='%s' not found", buffer);
	*(value++) = 0;

	/* strip heading spaces */
	while ((*key != 0) && isspace(*key))
		key ++;
	while ((*value != 0) && isspace(*value))
		value ++;

	/* strip trailing spaces */
	work = key + strlen(key) - 1;
	while ((work >= key) && isspace(*work))
		*(work --) = 0;

	work = value + strlen(value) - 1;
	while ((work >= value) && isspace(*work))
		*(work --) = 0;

	/* escaped text */
	if (*value == '"') {
		value ++;
		work = strrchr(value, '"');
		if (work != NULL)
			*work = 0;
	}

	found = config_set_var(config, buffer, value);
	if (found != NO)
		return;

#ifndef ENABLE_EXTRA_KEYS
	errx(EX_CONFIG, "Key='%s' not found", buffer);
#endif
}

char *
config_read(const char *filename)
{
	const char *delimiter = "\r\n";
	char *buffer;
	char *line;

	buffer = local_read(filename);
	if (buffer == NULL)
		return buffer;

	line = strtok(buffer, delimiter);
	while (line != NULL) {
		config_parse_line(Config_box, line);
		line = strtok(NULL, delimiter);
	}
	return buffer;
}

void
config_check(void)
{
#if 0
	int fatal = NO;

	if (User == NULL) {
		warnx("User not set");
		fatal = YES;
	};
	if (Password == NULL) {
		warnx("Password not set");
		fatal = YES;
	};

	if (fatal != NO)
		errx(EX_CONFIG, "Please edit your configuration file");
#endif
}

#ifdef WITH_SQLITE
static char sqlbuf[MAX_KEY];

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
	int i;

	for(i=0; i<argc; i++){
		printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
	}
	printf("\n");
	return 0;
}
#endif

void
localdb_cleanup(const char *name, time_t valid_from)
{
#ifndef WITH_SQLITE
	DB *db;
	DBT key, data;
	int ret, fd, st;
	time_t tv;

	db = dbopen(name, O_RDWR, 0600, DB_HASH, NULL);
	if (db == NULL)
		err(EX_NOINPUT, "open database %s failed", name);

	/* lock all changes */
	fd = db->fd(db);
	if (fd == -1) {
		st = -1;
		warn("database has no fd");
	} else {
		st = flock(fd, LOCK_EX);
	}

	if (st == 0) {
		/* cleanup old entrys */
		ret = db->seq(db, &key, &data, R_FIRST);
		while (!ret) {
			data.size = data.size < 16 ? data.size : 15;
			strncpy(fbuf, data.data, data.size);
			fbuf[data.size] = 0;
			tv = atol(fbuf);
			if (tv < valid_from) {
				db->del(db, &key, 0);
				db->sync(db, 0);
				/* start over */
				ret = db->seq(db, &key, &data, R_FIRST);
				continue;
			}
			ret = db->seq(db, &key, &data, R_NEXT);
		}
		st = flock(fd, LOCK_UN);
	} else {
		warnx("lock database %s failed", name);
	}
	db->close(db);
#else /* WITH_SQLITE */
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;

	rc = sqlite3_open(name, &db);
	if (rc)
		err(EX_NOINPUT, "open database %s failed", name);

	snprintf(sqlbuf, sizeof(sqlbuf), "DELETE FROM t1 WHERE created < %ld", (long)valid_from);
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("SQL error in %s: %s", name, zErrMsg);
		sqlite3_free(zErrMsg);
	}
	sqlite3_close(db);
#endif /* WITH_SQLITE */
}


void
localdb_delete(const char *name, const char *hash)
{
#ifndef WITH_SQLITE
	DB *db;
	DBT key;
	char *hash2;
	int fd;
	int st;
	int rc;

	db = dbopen(name, O_RDWR|O_CREAT, 0600, DB_HASH, NULL);
	if (db == NULL)
		err(EX_NOINPUT, "open database %s failed", name);

	/* lock all changes */
	fd = db->fd(db);
	if (fd == -1) {
		st = -1;
		warn("database has no fd");
	} else {
		st = flock(fd, LOCK_EX);
	}

	if (st == 0) {
		/* generate entry */
		hash2 = strdup(hash);
		if (hash2 == NULL)
			errx(EX_CANTCREAT, "out of mem for database");
		key.data = hash2;
		key.size = strlen(key.data);
		rc = db->del(db, &key, 0);
		if ((rc != 0) && (rc != 1))
			warn("database delete returns %d", rc);
		db->sync(db, 0);
		free(hash2);
		st = flock(fd, LOCK_UN);
	} else {
		warnx("lock database %s failed", name);
	}
	db->close(db);
#else /* WITH_SQLITE */
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;

	rc = sqlite3_open(name, &db);
	if (rc)
		err(EX_NOINPUT, "open database %s failed", name);

	snprintf(sqlbuf, sizeof(sqlbuf), "DELETE FROM t1 WHERE id = '%s'", hash);
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("SQL error in %s: %s", name, zErrMsg);
		sqlite3_free(zErrMsg);
	}
	sqlite3_close(db);
#endif /* WITH_SQLITE */
}

void
localdb_write(const char *name, const char *hash, const char *value)
{
#ifndef WITH_SQLITE
	DB *db;
	DBT key, data;
	char *hash2;
	int fd;
	int st;
	int rc;

	db = dbopen(name, O_RDWR|O_CREAT, 0600, DB_HASH, NULL);
	if (db == NULL)
		err(EX_NOINPUT, "open database %s failed", name);

	/* lock all changes */
	fd = db->fd(db);
	if (fd == -1) {
		st = -1;
		warn("database has no fd");
	} else {
		st = flock(fd, LOCK_EX);
	}

	if (st == 0) {
		/* generate entry */
		hash2 = strdup(hash);
		if (hash2 == NULL)
			errx(EX_CANTCREAT, "out of mem for database");
		key.data = hash2;
		key.size = strlen(key.data);
		snprintf(fbuf, MAX_BUF, "%lu|%s", (long)time(NULL), value);
		data.data = fbuf;
		data.size = strlen(fbuf);
		rc = db->put(db, &key, &data, 0);
		if ((rc != 0) && (rc != 1))
			warn("database write returns %d", rc);
		db->sync(db, 0);
		free(hash2);
		st = flock(fd, LOCK_UN);
	} else {
		warnx("lock database %s failed", name);
	}
	db->close(db);
#else /* WITH_SQLITE */
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;

	rc = sqlite3_open(name, &db);
	if (rc)
		err(EX_NOINPUT, "open database %s failed", name);

	snprintf(sqlbuf, sizeof(sqlbuf),
		"CREATE TABLE IF NOT EXISTS t1 (id VARCHAR(100) PRIMARY KEY UNIQUE, created INTEGER, data VARCHAR(255))");
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("%s", sqlbuf);
		warnx("SQL error in %s: %s", name, zErrMsg);
		sqlite3_free(zErrMsg);
	}

	snprintf(fbuf, MAX_BUF, "%lu|%s", (long)time(NULL), value);
	snprintf(sqlbuf, sizeof(sqlbuf),
		"INSERT OR REPLACE INTO t1 (id,created,data) VALUES('%s',%ld,'%s')",
		hash, (long)time(NULL), value);
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("%s", sqlbuf);
		warnx("SQL error in %s: %s", name, zErrMsg);
		sqlite3_free(zErrMsg);
	}
	sqlite3_close(db);
#endif /* WITH_SQLITE */
}

void
localdb_write_raw(const char *name, const char *hash, const char *value)
{
#ifndef WITH_SQLITE
	DB *db;
	DBT key, data;
	char *hash2;
	int fd;
	int st;
	int rc;

	db = dbopen(name, O_RDWR|O_CREAT, 0600, DB_HASH, NULL);
	if (db == NULL)
		err(EX_NOINPUT, "open database %s failed", name);

	/* lock all changes */
	fd = db->fd(db);
	if (fd == -1) {
		st = -1;
		warn("database has no fd");
	} else {
		st = flock(fd, LOCK_EX);
	}

	if (st == 0) {
		/* generate entry */
		hash2 = strdup(hash);
		if (hash2 == NULL)
			errx(EX_CANTCREAT, "out of mem for database");
		key.data = hash2;
		key.size = strlen(key.data);
		if (value != NULL)
			snprintf(fbuf, MAX_BUF, "%s", value);
		data.data = fbuf;
		data.size = strlen(fbuf);
		rc = db->put(db, &key, &data, 0);
		if ((rc != 0) && (rc != 1))
			warn("database write returns %d", rc);
		db->sync(db, 0);
		free(hash2);
		st = flock(fd, LOCK_UN);
	} else {
		warnx("lock database %s failed", name);
	}
	db->close(db);
#else /* WITH_SQLITE */
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;

	rc = sqlite3_open(name, &db);
	if (rc)
		err(EX_NOINPUT, "open database %s failed", name);

	snprintf(sqlbuf, sizeof(sqlbuf),
		"CREATE TABLE IF NOT EXISTS t1 (id VARCHAR(100) PRIMARY KEY UNIQUE, data VARCHAR(255))");
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("%s", sqlbuf);
		warnx("SQL error in %s: %s", name, zErrMsg);
		sqlite3_free(zErrMsg);
	}

	if (value != NULL)
		snprintf(fbuf, MAX_BUF, "%s", value);
	snprintf(sqlbuf, sizeof(sqlbuf),
		"INSERT OR REPLACE INTO t1 (id,data) VALUES('%s','%s')",
		hash, fbuf);
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("%s", sqlbuf);
		warnx("SQL error in %s: %s", name, zErrMsg);
		sqlite3_free(zErrMsg);
	}
	sqlite3_close(db);
#endif /* WITH_SQLITE */
}

#ifdef WITH_SQLITE
static int callback_read(void *buffer, int argc, char **argv, char **azColName)
{
        int i;

	switch (argc) {
	case 2:
		snprintf(fbuf, sizeof(fbuf), "%s", argv[1]);
		break;
	case 3:
		snprintf(fbuf, sizeof(fbuf), "%s|%s", argv[1], argv[2]);
		break;
	default:
        	for (i=0; i<argc; i++) {
                	printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
        	}
        	printf("\n");
		break;
	}
        return 0;
}
#endif /* WITH_SQLITE */

char *
localdb_read(const char *name, const char *hash)
{
#ifndef WITH_SQLITE
	DB *db;
	DBT key, data;
	char *hash2;
	char *str = NULL;
	int rc;

	db = dbopen(name, O_RDONLY, 0600, DB_HASH, NULL);
	if (db == NULL) {
		if (errno == ENOENT)
			return NULL;
		err(EX_NOINPUT, "open database %s failed", name);
	}

	/* generate entry */
	hash2 = strdup(hash);
	if (hash2 == NULL)
		errx(EX_CANTCREAT, "out of mem for database");
	key.data = hash2;
	key.size = strlen(key.data);
	data.data = NULL;
	data.size = 0;
	data.size = sizeof(fbuf) - 1;
	rc = db->get(db, &key, &data, 0);
	if (rc == 0) {
		if ((data.data != NULL) && (data.size > 0) && (data.size < sizeof(fbuf))) {
			strncpy(fbuf, data.data, data.size);
			fbuf[data.size] = 0;
			if (fbuf[data.size - 1] == '\n')
				fbuf[data.size - 1] = 0;
			str = fbuf;
		} else {
			warnx("database get failed, size = %d", data.size);
		}
	} else {
		if (rc != 1)
			warn("database read returns %d", rc);
	}
	free(hash2);
	db->close(db);
	return str;
#else /* WITH_SQLITE */
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;

	rc = sqlite3_open(name, &db);
	if (rc)
		err(EX_NOINPUT, "open database %s failed", name);

	snprintf(sqlbuf, sizeof(sqlbuf), "SELECT * FROM t1 WHERE id = '%s'", hash);
	fbuf[ 0 ] = 0;
	rc = sqlite3_exec(db, sqlbuf, callback_read, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("%s", sqlbuf);
		warnx("SQL error: %s", zErrMsg);
		sqlite3_free(zErrMsg);
	}
	sqlite3_close(db);
	if (rc != SQLITE_OK) {
		return NULL;
	}
	if (fbuf[ 0 ] == 0)
          return NULL;
	return fbuf;
#endif /* WITH_SQLITE */
}

size_t
localdb_list_keys(const char *name, char *buffer, size_t max)
{
#ifndef WITH_SQLITE
	DB *db;
	DBT key, data;
	int ret;
	size_t left;
	size_t found;

	db = dbopen(name, O_RDONLY, 0600, DB_HASH, NULL);
	if (db == NULL) {
		if (errno == ENOENT)
			return 0;
		err(EX_NOINPUT, "open database %s failed", name);
	}

	found = 0;
	left = max - 1;
	ret = db->seq(db, &key, &data, R_FIRST);
	while (!ret) {
		if ((key.data != NULL) && (key.size > 0) && (key.size < left)) {
			if ((data.data != NULL) && (data.size > 0) && (data.size < sizeof(fbuf))) {
				strncpy(buffer, key.data, key.size);
				buffer += key.size;
				left -= key.size;
				*(buffer++) = '\n';
				found++;
				left--;
			} else {
				warnx("database get data failed, size = %d", data.size);
			}
		} else {
			warnx("database get key failed, size = %d", key.size);
		}
		ret = db->seq(db, &key, &data, R_NEXT);
	}
	db->close(db);
	*buffer = 0;
	return found;
#else /* WITH_SQLITE */
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;

	rc = sqlite3_open(name, &db);
	if (rc)
		err(EX_NOINPUT, "open database %s failed", name);

	snprintf(sqlbuf, sizeof(sqlbuf), "SELECT * FROM t1");
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("%s", sqlbuf);
		warnx("SQL error: %s", zErrMsg);
		sqlite3_free(zErrMsg);
	}
	sqlite3_close(db);
    return 0;
#endif /* WITH_SQLITE */
}

size_t
localdb_dump(const char *name, char *buffer, size_t max)
{
#ifndef WITH_SQLITE
	DB *db;
	DBT key, data;
	int ret;
	size_t left;
	size_t found;

	db = dbopen(name, O_RDONLY, 0600, DB_HASH, NULL);
	if (db == NULL) {
		if (errno == ENOENT)
			return 0;
		err(EX_NOINPUT, "open database %s failed", name);
	}

	found = 0;
	left = max - 1;
	ret = db->seq(db, &key, &data, R_FIRST);
	while (!ret) {
		if ((key.data != NULL) && (key.size > 0) && (key.size < left)) {
			strncpy(buffer, key.data, key.size);
			buffer += key.size;
			left -= key.size;
			*(buffer++) = '\t';
			left--;
			if ((data.data != NULL) && (data.size > 0) && (data.size < left)) {
				strncpy(buffer, data.data, data.size);
				buffer += data.size;
				left -= data.size;
				*(buffer++) = '\n';
				left--;
			} else {
				warnx("database get data failed, size = %d", data.size);
			}
			found++;
		} else {
			warnx("database get key failed, size = %d", key.size);
		}
		ret = db->seq(db, &key, &data, R_NEXT);
	}
	db->close(db);
	*buffer = 0;
	return found;
#else /* WITH_SQLITE */
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;

	rc = sqlite3_open(name, &db);
	if (rc)
		err(EX_NOINPUT, "open database %s failed", name);

	snprintf(sqlbuf, sizeof(sqlbuf), "SELECT * FROM t1");
	rc = sqlite3_exec(db, sqlbuf, callback, 0, &zErrMsg);
	if (rc != SQLITE_OK) {
		warnx("%s", sqlbuf);
		warnx("SQL error: %s", zErrMsg);
		sqlite3_free(zErrMsg);
	}
	sqlite3_close(db);
    return 0;
#endif /* WITH_SQLITE */
}


int
filetag_to_key(const char *filetag, char **size, char **hash)
{
	const char *work;
	char *end;

	*size = NULL;
	*hash = NULL;
/*

in:
146810880|0436df97e7fe25b620edb25380717479|

out:
146810880
0436df97e7fe25b620edb25380717479

*/
	work = filetag;
	if ( *work == '|' )
		work ++;

	strlcpy(ksize, work, sizeof(ksize));
	*size = ksize;
	end = strchr(*size, '|');
	if (end != NULL)
		*end = 0;

	work = strchr(work + 1, '|');
	if (work == NULL)
		return 1;

	strlcpy(khash, work + 1, sizeof(khash));
	*hash = khash;
	end = strchr(*hash, '|');
	if (end != NULL)
		*end = 0;
	string_to_lowercase(*hash);
	return 0;
}

// static __inline__ char
static char
hdigit(int q) {
	return ((q >= 10) ? 'a' - 10 : '0') + q;
}

void
digest_to_string(char *str, const char *digest) {
	size_t dsize = sizeof(md5_t);
	unsigned char d;

	while (dsize--) {
		d = *(digest++);
		*(str++) = hdigit(d >> 4 );
		*(str++) = hdigit(d & 0xf);
	}
	*str = '\0';
return;
}

int
generate_hash(const char *filename, char **size, char **hash)
{
        int fd;
#ifdef WITH_MAGIC
	int new = 0;
	int i;
#endif
	ssize_t len;
	struct stat statdata;
	MD5_CTX ctx_m;

	/* access file */
	if ((fd = open(filename, O_RDONLY)) < 0) {
		warn("open %s", filename );
		return 1;
	}
	if (fstat(fd, &statdata) < 0) {
		warn("stat %s", filename );
		close(fd);
		return 1;
	}
	if ((statdata.st_mode & S_IFMT) != S_IFREG) {
		warnx("stat %s: is not a regular file", filename );
		close(fd);
		return 1;
        }

	/* hash it */
	MD5Init(&ctx_m);
	for (;;) {
		len = read(fd, digestbuf, BLOCKSIZE);
		if ( len <= 0 )
			break;
#ifdef WITH_MAGIC
		if ( new == 0 ) {
			/* validate first block */
			new ++;
			for ( i=0; magic[i].name; i++ ) {
				if ( strncmp(digestbuf, magic[i].detect, strlen(magic[i].detect)) == 0 )
					warnx( "magic %s found", magic[i].name );
			}
		}
#endif
		MD5Update(&ctx_m, digestbuf, (unsigned int)len);
	}
	MD5Final((unsigned char *)digestmd5, &ctx_m);
	close(fd);

	/* result to text */
	digest_to_string(khash, digestmd5);
	snprintf(ksize, sizeof(ksize), "%lld", statdata.st_size);
	snprintf(ktime, sizeof(ktime), "%lld", (long long)statdata.st_mtime);
	*hash = khash;
	*size = ksize;
	if ( Debug != NO) {
		printf("MD5 (%s) = %s\n", filename, *hash);
		printf("SIZE (%s) = %s\n", filename, *size);
		printf("TIME (%s) = %s\n", filename, ktime);
	}

	/* prepare key */
	snprintf(fbuf, sizeof(fbuf) - 1, "%s|%s", ksize, khash);
        return 0;
}

int
filename_to_key(const char *filename, char **size, char **hash)
{
	int rc;
	const char *base;
	char *data;

	if (strncmp(filename, kname, sizeof(kname)) == 0) {
		*size = ksize;
		*hash = khash;
		return 0;
	}
	strncpy(kname, filename, sizeof(kname) - 1);
	kname[sizeof(kname) - 1] = 0;

	rc = filetag_to_key(filename, size, hash);
	if (rc == 0) {
		if ((*size != NULL) && (*hash != NULL))
			return 0;
	}

	base = strrchr(filename, '/');
	if (base != NULL)
		base ++;
	else
		base = filename;

	data = localdb_read(Names_db, base);
	if (data != NULL) {
		rc = filetag_to_key(data, size, hash);
		if (rc == 0) {
			if ((*size != NULL) && (*hash != NULL))
				return 0;
		}
		warnx("File not an hash-tag, error=%d in: %-70.70s", rc, filename);
		return 1;
	}
	rc = generate_hash(filename, size, hash);
	if (rc == 0)
		localdb_write_raw(Names_db, filename, NULL);
	return rc;
}

int
strcmp_data(char *a, char *b)
{
	char *da;
	char *db;

	if ( a == b )
		return 0; /* identical */

	da = strchr(a, '\t');
	db = strchr(b, '\t');
	if ( da == db )
		return 0; /* both not found */
	if (da == NULL)
		return -1;
	if (db == NULL)
		return 1;

	return strcmp(da, db);
}

static __inline__ int a_vergleiche(char *a, char *b, int order)
{
	switch (order) {
	case ORDER_DATA:
		return strcmp_data(a, b);
	default:
		return strcmp(a, b);
	}
}

static __inline__ void a_vertausche(char **a, char **b)
{
	char *zwischenwert;

	zwischenwert = *a;
	*a = *b;
	*b = zwischenwert;
}

void
a_bottom_up_reheap(char **feld, long max, long aindex, int order)
{
	char *zwischenwert;
	long suche, ring, tiefe, hilfe;

	/*	BLATT-SUCHE	*/
	for ( suche = (aindex << 1); suche < max; suche <<= 1 )
		if ( a_vergleiche( feld[ suche ], feld[ suche+1L ], order ) < 0 )
			suche++;
	if ( suche > max )
		suche >>= 1;

	/*	BOTTON-UP-SUCHE		*/
	zwischenwert = feld[ aindex ];
	while ( a_vergleiche( zwischenwert, feld[ suche ], order ) > 0 )
		suche >>= 1;

	/*	RINGTAUSCH	*/
	tiefe = 0;
	hilfe = ( suche / aindex );
	while ( hilfe > 1L ) {
		hilfe >>= 1;
		tiefe++;
	}; /* tiefe = ln( suche/aindex ) / ln( 2 ) */
	if (tiefe > 1) {
		for ( ring = tiefe-1; ring >= 0; ring-- )
			feld[ suche >> ( ring+1L ) ] =
					feld[ suche >> ring ];
	} else {
		feld[ aindex ] = feld[ suche ];
	};
	feld[ suche ] = zwischenwert;
}

void
a_bottom_up_heap_sort(char **feld, long anzahl, int order)
{
	long aufbau, auswahl;

	if ( anzahl <= 1 )
		return;

	feld--;
	/* sortiert wird feld[1] bis feld[max] */
	/*	AUFBAU-PHASE	*/
	for ( aufbau = (anzahl >> 1); aufbau >= 1; aufbau-- )
		a_bottom_up_reheap( feld, anzahl, aufbau, order );

	/*	AUSWAHL-PHASE	*/
	for ( auswahl = anzahl; auswahl >= 3; auswahl-- ) {
		a_vertausche( &feld[ 1 ], &feld[ auswahl ] );
		a_bottom_up_reheap( feld, auswahl-1L, 1L, order );
	};
	a_vertausche( &feld[ 1 ], &feld[ 2 ] );
}


void
print_date(const char *prefix, const char *seconds)
{
	long lsec;
	time_t now;

	if (seconds == NULL)
		return;

	lsec = atol(seconds);
	if (lsec == 0)
		return;

	now = lsec;
	strftime(kbuf, sizeof(kbuf) - 1, Date_format, localtime(&now));
	printf("%stext: %s\n", prefix, kbuf);
}

int
show_text(const char *const *info, const char *key, const char *data)
{
	char *buffer;
	char *work;
	char *field;
	char *hash;
	char *size;
	int state = -2;
	int rc;

	buffer = strdup(data);
	if (buffer == NULL)
		errx(EX_CANTCREAT, "out of memory in info: %-70.70s", key);

	field = buffer;
	work = strchr(buffer, '|');
	while (work != NULL) {
		*(work++) = 0;
		switch (++state) {
		case -1:
			rc = filename_to_key(key, &size, &hash);
			if (rc != 0)
				return 1;
			printf("size: %s\n", size);
			printf("hash: %s\n", hash);
			printf("%s: %s\n", C_CACHED, field);
			print_date(C_CACHED, field);
			break;
		default:
			printf("%s: %s\n", info[state], field);
			if (strstr(info[state], "date")) {
				print_date(info[state], field);
			};
			break;
		}
		field = work;
		work = strchr(work, '|');
	}
	if ( state >= 0 )
		printf("%s: %s\n", info[++state], field);
	printf("\n");
	return 0;
}

size_t
count_lines_in_buffer(const char *buffer)
{
	size_t found = 0;
	while (*buffer) {
		buffer = strchr(buffer, '\n');
		if (buffer == NULL)
			break;
		buffer ++;
		found++;
	}
	return found;
}

size_t
text_to_table(char **table, char *buffer)
{
	const char *delimiter = "\n";
	char *line;
	size_t found = 0;

	line = strtok(buffer, delimiter);
	while (line != NULL) {
		table[ found++ ] = line;
		line = strtok(NULL, delimiter);
	}
	return found;
}

void
print_table(char **table, size_t lines)
{
	while (lines--) {
		printf( "%s\n", *(table++) );
	}
}

void
load_table_from_buffer(char ***table, size_t *lines, char *buffer, size_t found, int order)
{
	*table = NULL;
	*lines = 0;
	if (found <= 0) {
		warnx("empty buffer");
		return;
	}

	*table = (char**) malloc(found * sizeof(char *));
	if (*table == NULL) {
		warnx("out of memory");
		return;
	}
	*lines = text_to_table(*table, buffer);
	if (order == ORDER_NONE)
		return;

	a_bottom_up_heap_sort(*table, (long)(*lines), order);
}

void
load_table_from_file(char ***table, size_t *lines, char **buffer, const char *keyfile, int order)
{
	size_t found;

	*table = NULL;
	*lines = 0;

	*buffer = local_read(keyfile);
	if (*buffer == NULL)
		return;

	found = count_lines_in_buffer(*buffer);
	load_table_from_buffer(table, lines, *buffer, found, order);
}

void
load_table_from_db(char ***table, size_t *lines, char **buffer, const char *dbfile, int order)
{
	size_t found;
	size_t size;
	struct stat statdata;

	*table = NULL;
	*buffer = NULL;
	*lines = 0;

	if (lstat(dbfile, &statdata) < 0) {
		warn("stat %s:", dbfile);
		return;
	}
	size = statdata.st_size / 2;
	*buffer = (char*) malloc(size);
	if (*buffer == NULL) {
		warnx("out of memory");
		return;
	}
	found = localdb_list_keys(dbfile, *buffer, size);
	load_table_from_buffer(table, lines, *buffer, found, order);
}

void
load_data_from_db(char ***table, size_t *lines, char **buffer, const char *dbfile, int order)
{
	size_t found;
	size_t size;
	struct stat statdata;

	*table = NULL;
	*buffer = NULL;
	*lines = 0;

	if (lstat(dbfile, &statdata) < 0) {
		warn("stat %s:", dbfile);
		return;
	}
	size = statdata.st_size;
	*buffer = (char*) malloc(size);
	if (*buffer == NULL) {
		warnx("out of memory");
		return;
	}
	found = localdb_dump(dbfile, *buffer, size);
	load_table_from_buffer(table, lines, *buffer, found, order);
}

void
print_common_lines(char **table1, size_t lines1, char **table2, size_t lines2)
{
	int wo;

	while (lines1 && lines2) {
		wo = strcmp( *table1, *table2 );
		if (wo == 0) {
			printf("%s\n", *table1);
			table1++;
			table2++;
			lines1--;
			lines2--;
			continue;
		}
		if (wo > 0) {
			table2++;
			lines2--;
		} else {
			table1++;
			lines1--;
		}
	}
}

void
print_table1_lines(char **table1, size_t lines1, char **table2, size_t lines2)
{
	int wo;

	while (lines1 && lines2) {
		wo = strcmp( *table1, *table2 );
		if (wo == 0) {
			table1++;
			table2++;
			lines1--;
			lines2--;
			continue;
		}
		if (wo > 0) {
			table2++;
			lines2--;
		} else {
			printf("%s\n", *table1);
			table1++;
			lines1--;
		}
	}
	print_table(table1, lines1);
}

void
print_table2_lines(char **table1, size_t lines1, char **table2, size_t lines2)
{
	int wo;

	while (lines1 && lines2) {
		wo = strcmp( *table1, *table2 );
		if (wo == 0) {
			table1++;
			table2++;
			lines1--;
			lines2--;
			continue;
		}
		if (wo > 0) {
			printf("%s\n", *table2);
			table2++;
			lines2--;
		} else {
			table1++;
			lines1--;
		}
	}
	print_table(table2, lines2);
}

void
compare_keys(const char *keyfile, int typ)
{
	char *mybuffer;
	char *buffer;
	char **mytable;
	char **table;
	size_t mylines;
	size_t lines;

	if (Verbose != NO)
		warnx("loading myfiles");
	load_table_from_db(&mytable, &mylines, &mybuffer, Myfiles_db, ORDER_SORT);
	if (Verbose != NO)
		warnx("loading keyfile \"%s\"", keyfile);
	load_table_from_file(&table, &lines, &buffer, keyfile, ORDER_SORT);

	if ((mytable != NULL) && (table != NULL)) {
		switch (typ) {
		case MATCH_BOTH:
			if (Verbose != NO)
				warnx("common keys");
			print_common_lines(mytable, mylines, table, lines);
			break;
		case MATCH_IHAVE:
			if (Verbose != NO)
				warnx("keys only I have");
			print_table1_lines(mytable, mylines, table, lines);
			break;
		case MATCH_SENDME:
			if (Verbose != NO)
				warnx("keys I do not have");
			print_table2_lines(mytable, mylines, table, lines);
			break;
		}
	}

	if (table != NULL)
		free(table);
	if (buffer != NULL)
		free(buffer);
	if (mytable != NULL)
		free(mytable);
	if (mybuffer != NULL)
		free(mybuffer);
}

void
list_filenames(const char *keyfile)
{
	char *buffer;
	char **table;
	char **work;
	char *data;
	char *name;
	char *end;
	size_t lines;
	size_t i;

	if (Verbose != NO)
		warnx("loading keyfile \"%s\"", keyfile);
	load_table_from_file(&table, &lines, &buffer, keyfile, ORDER_NONE);

	i = lines;
	work = table;
	while (i--) {
		/* we have a valid key */
		snprintf(kbuf, sizeof(kbuf) - 1, "%s", *(work++));

		data = localdb_read(Myfiles_db, kbuf);
		if (data == NULL) {
			warnx("not in yfiles \"%s\"", kbuf);
			continue;
		}
		name = strchr(data, '|');
		if (name == NULL)
			continue;
		name ++;
		end = strchr(name, '|');
		if (end != NULL)
			*end = 0;
		printf("%s\n", name);
#if 0
		show_text(response_myfiles, kbuf, data);
#endif
	}

	if (table != NULL)
		free(table);
	if (buffer != NULL)
		free(buffer);
}

void
export_keys(const char *dbname)
{
	char *mybuffer;
	char **mytable;
	size_t mylines;

	load_table_from_db(&mytable, &mylines, &mybuffer, dbname, ORDER_SORT);

	if (mytable != NULL)
		print_table(mytable, mylines);

	if (mytable != NULL)
		free(mytable);
	if (mybuffer != NULL)
		free(mybuffer);
}

void
export_data(const char *dbname)
{
	char *mybuffer;
	char **mytable;
	size_t mylines;

	load_data_from_db(&mytable, &mylines, &mybuffer, dbname, ORDER_SORT);

	if (mytable != NULL)
		print_table(mytable, mylines);

	if (mytable != NULL)
		free(mytable);
	if (mybuffer != NULL)
		free(mybuffer);
}

void
print_common_data(char **table, size_t lines)
{
	char *last;
	char *data;
	size_t found;

	last = NULL;
	found = 0;
	while (lines--) {
		data = strchr(*(table++), '\t');
		if (last != NULL) {
			found ++;
			if (strcmp(data, last) != 0) {
				/* end of duplicates */
				if (found > 2)
					print_table(table - found, found - 1);
				found = 1;
			}
		}
		last = data;
	}
}

void
duplicate_data(const char *dbname)
{
	char *mybuffer;
	char **mytable;
	size_t mylines;

	load_data_from_db(&mytable, &mylines, &mybuffer, dbname, ORDER_DATA);
	if (mytable != NULL)
		print_common_data(mytable, mylines);

	if (mytable != NULL)
		free(mytable);
	if (mybuffer != NULL)
		free(mybuffer);
}

void
print_clean_missing(char **table, size_t lines, const char *dbname)
{
	struct stat statdata;
	char *key;
	char *filename;
	long found;

	found = 0;
	while (lines--) {
		filename = strdup(*(table++));
		if ( filename == NULL) {
			warnx("out of memomory");
			continue;
		}
		key = strchr(filename, '\t');
		if ( key != NULL) {
			*(key++) = 0;
			if (strstr(filename, "//") != NULL) {
				if (Verbose != NO)
					warnx("delete bad %s", filename );
				localdb_delete(dbname, filename);
				found ++;
				free( filename );
				continue;
			}
#ifndef WITHOUT_REL_PATH
			if (strstr(filename, "../") != NULL) {
				if (Verbose != NO)
					warnx("delete bad %s", filename );
				localdb_delete(dbname, filename);
				found ++;
				free( filename );
				continue;
			}
#endif
			if (lstat(filename, &statdata) < 0) {
				if (errno == ENOENT) {
					if (Verbose != NO)
						warnx("delete %s", filename );
					localdb_delete(dbname, filename);
					found ++;
				} else {
					warn("cannot open file %s", filename);
				}
			}
		} else {
			warnx("bad record: \"%s\"", filename);
		}
		free( filename );
	}
	if ( found > 0 )
		warnx("deleted %ld", found );
}

void
clean_missing(const char *dbname)
{
	char *mybuffer;
	char **mytable;
	size_t mylines;

	load_data_from_db(&mytable, &mylines, &mybuffer, dbname, ORDER_DATA);
	if (mytable != NULL)
		print_clean_missing(mytable, mylines, dbname);

	if (mytable != NULL)
		free(mytable);
	if (mybuffer != NULL)
		free(mybuffer);
}

int
run_listfile(const char *listname, f_process process)
{
	struct stat statdata;
	const char *delimiter = "\r\n";
        char *buffer;
        char *line;

	if (lstat(listname, &statdata) < 0)
		return 0;

	if ((statdata.st_mode & S_IFMT) != S_IFREG)
		return 0;

        buffer = local_read(listname);
        if (buffer == NULL)
                return 1;

        line = strtok(buffer, delimiter);
        while (line != NULL) {
		if (( line[ 0 ] != '#' ) && ( strlen( line ) > 0 )) {
			if (Debug != NO)
				warnx("running file \"%s\"", line);
			process(line);
		}
                line = strtok(NULL, delimiter);
        }
	free(buffer);
	return 1;
}

int
run_directory(const char *dirname, f_process process)
{
	struct stat statdata;
	struct dirent *f;
	DIR *d;
	char dbuf[MAX_KEY];

	if ( dirname[ 0 ] == '@' ) {
		if ( run_listfile( dirname + 1, process) != 0 )
			return 1;
        }

	if ( strcasestr(dirname, "Thumbs.db") != NULL) {
		if (Verbose != NO)
			warnx("ignoring file \"%s\"", dirname);
		return 1;
	}

	if (lstat(dirname, &statdata) < 0)
		return 0;

	if ((statdata.st_mode & S_IFMT) != S_IFDIR)
		return 0;

	if ( strstr(dirname, ".xvpics") != NULL) {
		if (Verbose != NO)
			warnx("ignoring directory \"%s\"", dirname);
		return 1;
	}

	d = opendir(dirname);
	if (d == 0) {
		warnx("skipping directory \"%s\"", dirname);
		return 1;
	}

	while ((f = readdir(d))) {
		if ( dirname[ strlen(dirname) ] == '/' )
			snprintf(dbuf, sizeof(dbuf) - 1, "%s%s", dirname, f->d_name);
		else
			snprintf(dbuf, sizeof(dbuf) - 1, "%s/%s", dirname, f->d_name);
		if (stat(dbuf, &statdata) < 0) {
			warn("stat \"%s\"", dbuf);
			continue;
		}
		if ((statdata.st_mode & S_IFMT) == S_IFREG) {
			if (Debug != NO)
				warnx("running file \"%s\"", dbuf);
			process(dbuf);
			continue;
		}
		if ((statdata.st_mode & S_IFMT) == S_IFDIR) {
			if (strcmp(f->d_name, ".") == 0)
				continue;
			if (strcmp(f->d_name, "..") == 0)
				continue;
			if (Debug != NO)
				warnx("running directory \"%s\"", dirname);
			run_directory(dbuf, process);
			continue;
		}
	}
	closedir(d);
	return 1;
}

int
check_if_file_recorded(const char *filename)
{
	char *data;
	char *path;
	char *work;

	if (Cache_ignore != NO)
		return NO;

	data = localdb_read(Myfiles_db, kbuf);
	if (data == NULL)
		return NO;

	path = strchr(data, '|');
	if (path == NULL ) {
		if (Verbose != NO)
			warnx("updating bad entry in myfiles \"%s\"", filename);
		return NO;
	}

	work = strchr(++path, '|');
	if (work == NULL ) {
		if (Verbose != NO)
			warnx("updating short entry in myfiles \"%s\"", filename);
		return NO;
	}
	*work = 0;

	if (strcmp(path, filename) == 0) {
		if (Verbose != NO)
			warnx("already added to myfiles \"%s\"", filename);
		return YES;
	}

	if (Verbose != NO)
		warnx("updating in myfiles \"%s\" to \"%s\"", path, filename);
	return NO;
}

void
hash_addfiles(const char *key)
{
	char *size = NULL;
	char *hash = NULL;
	int rc;

	if (run_directory(key, hash_addfiles))
		return;

	rc = filename_to_key(key, &size, &hash);
	if (rc != 0)
		return;

	/* we have a valid key */
	snprintf(kbuf, sizeof(kbuf) - 1, "%s|%s", size, hash);

	if (check_if_file_recorded(key) != NO)
		return;

	/* save pathname, mtime */
	snprintf(cbuf, sizeof(cbuf) - 1, "%s|%s|", key, ktime);
	localdb_write(Myfiles_db, kbuf, cbuf);
	if (Verbose != NO)
		warnx("        added to myfiles \"%s\"", key);
	if (Quiet == NO)
		show_text(response_myfiles, kbuf, fbuf);
}

void
hash_newfiles(const char *key)
{
	char *size = NULL;
	char *hash = NULL;
	char *data;
	int rc;

	if (run_directory(key, hash_newfiles))
		return;

	rc = generate_hash(key, &size, &hash);
	if (rc != 0)
		return;

	/* we have a valid key */
	snprintf(kbuf, sizeof(kbuf) - 1, "%s|%s", size, hash);

	data = localdb_read(Myfiles_db, kbuf);
	if (data != NULL) {
		if (Verbose != NO)
			warnx("already in myfiles \"%s\"", key);
		return;
	}
	printf( "%s\n", key );
}

void
hash_oldfiles(const char *key)
{
	char *size = NULL;
	char *hash = NULL;
	char *data;
	int rc;

	if (run_directory(key, hash_oldfiles))
		return;

	rc = generate_hash(key, &size, &hash);
	if (rc != 0)
		return;

	/* we have a valid key */
	snprintf(kbuf, sizeof(kbuf) - 1, "%s|%s", size, hash);

	data = localdb_read(Myfiles_db, kbuf);
	if (data != NULL) {
		printf("in myfiles \"%s\"\n", key);
		if (Quiet == NO)
			show_text(response_myfiles, kbuf, data);
		return;
	}
	if (Verbose != NO)
		warnx("not in myfiles \"%s\"", key);
}

void
hash_showfiles(const char *key)
{
	char *size = NULL;
	char *hash = NULL;
	char *data;
	int rc;

	if (run_directory(key, hash_showfiles))
		return;

	rc = filename_to_key(key, &size, &hash);
	if (rc != 0)
		return;

	/* we have a valid key */
	snprintf(kbuf, sizeof(kbuf) - 1, "%s|%s", size, hash);

	data = localdb_read(Myfiles_db, kbuf);
	if (data == NULL) {
		if (Verbose != NO)
			warnx("not in myfiles \"%s\"", key);
		return;
	}

	if (Quiet == NO)
		show_text(response_myfiles, kbuf, fbuf);
}

void
hash_testfiles(const char *key)
{
	char *size = NULL;
	char *hash = NULL;
	int rc;

	if (run_directory(key, hash_testfiles))
		return;

	rc = generate_hash(key, &size, &hash);
	if (rc != 0)
		return;

	/* we have a valid key */
	printf( "%s|%s|%s\n", size, hash, key);
}


void
usage(void)
{
	fprintf(stderr,
"\n"
" Usage: bilderhash [options] [...] commands [...]\n"
"\n"
" options:\n"
"-debug            show datagramms\n"
"-verbose          show files processed\n"
"-quiet            don't print data\n"
"-f <config>       set name of config file, default '.bilderhash'\n"
"--<name>=<value>  override config with given value\n"
"\n"
"+myfiles [ directory | files ... ]    add files to myfiles database\n"
"+newfiles [ directory | files ... ]   show files that are not in myfiles database\n"
"+oldfiles [ directory | files ... ]   check files that are already in myfiles database\n"
"+showfiles [ directory | files ... ]  show data for files in myfiles database\n"
"+hashfiles [ directory | files ... ]  show size and hash for given files\n"
"+duplicates               list path of all duplicates files\n"
"+clean                    drop all moved or delete pathnames\n"
"\n"
"+exportkeys               list all keys from myfiles database\n"
"+exportdata               list all data from myfiles database\n"
"+common keyfile           list all common keys from keyfile and myfiles database\n"
"+get keyfile              list all keys only in keyfile\n"
"+put keyfile              list all keys only in myfiles\n"
"+filenames keyfile        translate keys into filenames if possible\n"
"\n");
	exit(EX_USAGE);
}

void
command_config(int argc, const char *const *argv)
{
	const char *cptr;
	char ch;

	while (--argc > 0) {
		cptr = *(++argv);
		if (*cptr == '-') {
			ch = *(++cptr);
			switch (ch) {
			case 'd':
				Debug = YES;
				break;
			case 'f':
				GET_NEXT_DATA(cptr);
				Config_file = cptr;
				break;
			case 'q':
				Quiet = YES;
				break;
			case 'v':
				Verbose = YES;
				break;
			}
			continue;
		}
	}
}

void
command_options(int argc, const char *const *argv)
{
	const char *cptr;
	char ch;
	char fc = 0;

	while (--argc > 0) {
		cptr = *(++argv);
		if (*cptr == '-') {
			ch = *(++cptr);
			switch (ch) {
			case '-':
				config_parse_line(Config_box, ++cptr);
				break;
			case 'd':
				Debug = YES;
				break;
			case 'f':
				break;
#if 0
			case 'l':
				GET_NEXT_DATA(cptr);
				Local_port = atol(cptr);
				break;
			case 'r':
				GET_NEXT_DATA(cptr);
				Remote_port = atol(cptr);
				break;
			case 'p':
				GET_NEXT_DATA(cptr);
				Password = cptr;
				break;
#endif
			case 'q':
				Quiet = YES;
				break;
#if 0
			case 's':
				GET_NEXT_DATA(cptr);
				Server_name = cptr;
				break;
			case 'u':
				GET_NEXT_DATA(cptr);
				User = cptr;
				break;
#endif
			case 'v':
				Verbose = YES;
				break;
			default:
				usage();
			}
			continue;
		}
		/* Syntax Check */
		if (*cptr == '+') {
			ch = *(++cptr);
			switch (ch) {
			case 'w': /* edit mylist */
				GET_NEXT_DATA(cptr);
				break;
			case 'd': /* duplicate data */
			case 'f': /* filename keys */
			case 'h': /* hash files */
			case 'g': /* get keys */
			case 'm': /* myfiles to myfiles */
			case 'n': /* new to myfiles */
			case 'o': /* show matches in myfiles */
			case 'p': /* put keys */
			case 's': /* show from myfiles */
				break;
			case 'c': /* common keys or clean */
				if (strcmp(cptr, "common") == 0 )
					break;
				if (strcmp(cptr, "clean") == 0 )
					break;
				usage();
			case 'e': /* export keys or data */
				if (strcmp(cptr, "exportkeys") == 0 )
					break;
				if (strcmp(cptr, "exportdata") == 0 )
					break;
				usage();
			default:
				usage();
			}
			fc = ch;
			continue;
		}
		/* Arguments for */
		switch (fc) {
		case 'c':
		case 'f':
		case 'g':
		case 'h':
		case 'm':
		case 'n':
		case 'o':
		case 'p':
		case 's':
			break;
		default:
			usage();
			break;
		}
	}
}


void
command_run(int argc, const char *const *argv)
{
	const char *cptr;
	char ch;
	char fc = 0;
	const char *field = NULL;

	while (--argc > 0) {
		cptr = *(++argv);
		if (*cptr == '-') {
			continue;
		}
		if (*cptr == '+') {
			ch = *(++cptr);
			switch (ch) {
			case 'w': /* edit mylist */
				GET_NEXT_DATA(cptr);
				field = cptr;
				break;
			case 'e': /* export keys or data */
				if (strcmp(cptr, "exportkeys") == 0 ) {
					export_keys(Myfiles_db);
					break;
				}
				if (strcmp(cptr, "exportdata") == 0 ) {
					export_data(Myfiles_db);
					break;
				}
				break;
			case 'c': /* common keys or clean */
				if (strcmp(cptr, "common") == 0 )
					break;
				if (strcmp(cptr, "clean") == 0 ) {
					clean_missing(Names_db);
					break;
				}
				break;
			case 'd': /* duplicate data */
				duplicate_data(Names_db);
				break;
			case 'f': /* filename keys */
			case 'g': /* get keys */
			case 'h': /* hash files */
			case 'm': /* myfiles to myfilesd */
			case 'n': /* new to myfileds */
			case 'o': /* show matches in myfileds */
			case 'p': /* put keys */
			case 's': /* show from myfiles */
				break;
			default:
				usage();
			}
			fc = ch;
			continue;
		}
		switch (fc) {
		case 'c':
			compare_keys(cptr, MATCH_BOTH);
			break;
		case 'f':
			list_filenames(cptr);
			break;
		case 'g':
			compare_keys(cptr, MATCH_SENDME);
			break;
		case 'h':
			hash_testfiles(cptr);
			break;
		case 'm':
			hash_addfiles(cptr);
			break;
		case 'n':
			hash_newfiles(cptr);
			break;
		case 'o':
			hash_oldfiles(cptr);
			break;
		case 'p':
			compare_keys(cptr, MATCH_IHAVE);
			break;
		case 's':
			hash_showfiles(cptr);
			break;
		default:
			usage();
			break;
		}
	}
}

int
main(int argc, const char *const *argv)
{
	char *buffer;

	config_default(Config_box);
	command_config(argc, argv);
	buffer = config_read(Config_file);

	command_options(argc, argv);
	config_check();

#if 0
//	anidb_alive();
#endif

	command_run(argc, argv);

#if 0
	if (Keep_session == NO)
		anidb_logout();
	network_close();
#endif
	if (buffer != NULL)
		free(buffer);
	return 0;
}

