/*
    gutenfetch - a small utility to list and fetch books available through
	project gutenberg

    Copyright (C) 2001, 2002, 2003, 2004 Russell Francis 

    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.
	59 Temple Place, Suite 330
	Boston, MA  02111-1307  USA

Last updated on $Date: 2004/07/07 02:41:22 $ by $Author: johntabularasa $.
*/
#include "stddefs.h"
#include "libgutenfetch_servers.h"
#include "list.h"
#ifdef HAVE_STDIO_H
#	include <stdio.h>
#endif
#ifdef HAVE_FCNTL_H
#	include <fcntl.h>
#endif
#ifdef HAVE_STDLIB_H
#	include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#	include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#	include <strings.h>
#endif
#ifdef HAVE_PTHREAD
	#ifdef HAVE_PTHREAD_H
	#	include <pthread.h>
	#endif
#endif

#ifdef HAVE_PTHREAD
static pthread_mutex_t active_server_mutex;
static pthread_mutex_t aussie_server_mutex;
#endif /* HAVE_PTHREAD */
static gutenfetch_server_t *active_server = NULL;
static gutenfetch_server_t *aussie_server = NULL;
static gutenfetch_server_t **potential_servers = NULL;

/* Private functions */

gutenfetch_continent_t
	gutenfetch_get_continent_from_string(char *);
	

/**
 * gutenfetch_get_continent_from_string
 *
 * Given a string, return the numeric constant
 * associated with that continent.
 *
 * @param str The string representing an unknown continent.
 * @return A valid gutenfetch_continent_t, which relates to
 * 		the string.
 */
gutenfetch_continent_t
gutenfetch_get_continent_from_string(char *str)
{
	if (str != NULL) {
		if (strcasecmp(str, "africa") == 0) {
			return AFRICA;
		} else if (strcasecmp(str, "asia") == 0) {
			return ASIA;
		} else if ((strcasecmp(str, "australasia_oceania") == 0) ||
					(strcasecmp(str, "australia") == 0) ||
					(strcasecmp(str, "oceania") ==0)) {
			return AUSTRALASIA_OCEANIA;			
		} else if (strcasecmp(str, "europe") == 0) {
			return EUROPE;
		} else if (strcasecmp(str, "north_america") == 0) {
			return NORTH_AMERICA;
		} else if (strcasecmp(str, "south_america") == 0) {
			return SOUTH_AMERICA;
		}
	}	
	
	return UNKNOWN_CONTINENT;
}


/* Global functions */

/**
 * gutenfetch_servers_init
 *
 * Initialize our default servers and set any
 * other persistent variables for this module.
 *
 * @return GUTENFETCH_OK on success or an error code.
 */
gutenfetch_error_t
gutenfetch_servers_init(void)
{
	gutenfetch_server_t *tserv;
	
#ifdef HAVE_PTHREAD
	if (pthread_mutex_init(&active_server_mutex, NULL) != 0)
		return GUTENFETCH_SEE_ERRNO;
	if (pthread_mutex_init(&aussie_server_mutex, NULL) != 0)
		return GUTENFETCH_SEE_ERRNO;
#endif

	/* Setup our default server */
	tserv = gutenfetch_new_server(
		"ftp://ibiblio.org/pub/docs/books/gutenberg/",
		"University of North Carolina - FTP",
		"Chapel Hill, North Carolina",
		NORTH_AMERICA);
	
	if (tserv == NULL)
		return GUTENFETCH_NOMEM;

#ifdef HAVE_PTHREAD
	pthread_mutex_lock(&active_server_mutex);
#endif	
	active_server = tserv;
#ifdef HAVE_PTHREAD	
	pthread_mutex_unlock(&active_server_mutex);
#endif	

	tserv = gutenfetch_new_server(
		"ftp://gutenberg.net.au/",
		"Project Gutenberg of Australia",
		"??, Australia",
		AUSTRALASIA_OCEANIA);
		
	if (tserv == NULL)
		return GUTENFETCH_NOMEM;
#ifdef HAVE_PTHREAD
	pthread_mutex_lock(&aussie_server_mutex);
#endif
	aussie_server = tserv;
#ifdef HAVE_PTHREAD
	pthread_mutex_unlock(&aussie_server_mutex);
#endif	

	gutenfetch_load_potential_servers();
	return GUTENFETCH_OK;
}

/**
 * gutenfetch_servers_shutdown
 *
 * Free any resources held by this module.
 */
void
gutenfetch_servers_shutdown(void)
{
#ifdef HAVE_PTHREAD
	pthread_mutex_lock(&active_server_mutex);
#endif	
	gutenfetch_free_server(active_server);
#ifdef HAVE_PTHREAD	
	pthread_mutex_unlock(&active_server_mutex);
	pthread_mutex_destroy(&active_server_mutex);
#endif	
#ifdef HAVE_PTHREAD
	pthread_mutex_lock(&aussie_server_mutex);
#endif	
	gutenfetch_free_server(aussie_server);
#ifdef HAVE_PTHREAD	
	pthread_mutex_unlock(&aussie_server_mutex);
	pthread_mutex_destroy(&aussie_server_mutex);
#endif	
	gutenfetch_free_servers(potential_servers);
}

/**
 * gutenfetch_load_potential_servers
 *
 * Read in a list of potential servers and store.
 * Currently reads from a file on disk but perhaps
 * in the future it could look on the network for
 * a newer copy. ;)
 *
 * @return GUTENFETCH_OK or another error code.
 */
gutenfetch_error_t
gutenfetch_load_potential_servers(void)
{
#define POTSERVERSFILE "servers.txt"
#define BUFFER_SIZE 4096
	int fd;
	int state;
//	int last_state;
	char *filename;
	char buffer[BUFFER_SIZE];
	gutenfetch_error_t errcode;
	size_t bytes_avail;
	size_t bytes_read;
	size_t filename_length;
	size_t host_index = 0;
	char host[BUFFER_SIZE];
	size_t area_index = 0;
	char area[BUFFER_SIZE];
	size_t name_index = 0;
	char name[BUFFER_SIZE];
	size_t continent_index = 0;
	char continent[BUFFER_SIZE];
	size_t server_count;
//	gutenfetch_server_t **pot_servers = NULL;
	gutenfetch_server_t **temp = NULL;

	/* This is a list of the states we can be
	 * in while parsing the servers.txt file. */
	enum {
		LOOKING_FOR_ENTRY,
		LOOKING_FOR_NAME,
		READING_NAME,
		LOOKING_FOR_AREA,
		READING_AREA,
		LOOKING_FOR_HOST,
		READING_HOST,
		LOOKING_FOR_CONTINENT,
		READING_CONTINENT,
		LOOKING_FOR_CLOSING,
		IGNORE_UNTIL_NEWLINE
	};	
		

	filename_length = strlen(DATADIR) + strlen(DIR_SEPARATOR) + 
		strlen(POTSERVERSFILE) + 1;
	filename = malloc(sizeof(char) * filename_length);
	snprintf(filename, filename_length, "%s%s%s",
		DATADIR, DIR_SEPARATOR, POTSERVERSFILE);

	fd = open(filename, O_RDONLY);
	FREE_NULL(filename);	
	if (fd == -1)
		return GUTENFETCH_SEE_ERRNO;

	server_count = 0;
	bytes_avail = 0;
	bytes_read = 0;
	state = LOOKING_FOR_ENTRY;
	while (TRUE) {
		if (bytes_read == bytes_avail) { /* read more. */
			bytes_avail = read(fd, buffer, BUFFER_SIZE);
			if (bytes_avail < 0) { /* Error condition. */
				errcode = GUTENFETCH_SEE_ERRNO;
				break;
			} else if (bytes_avail == 0) { /* EOF */
				errcode = GUTENFETCH_OK;
				break;
			}
			bytes_read = 0;
		}
		switch (state) {
		case LOOKING_FOR_ENTRY:
			if (buffer[bytes_read] == '{')
				state = LOOKING_FOR_NAME;
			break;
		case LOOKING_FOR_NAME:
			if (buffer[bytes_read] == '"') {
				name_index = 0;
				state = READING_NAME;
			}	
			break;
		case READING_NAME:
			if (buffer[bytes_read] == '"') {
				name[name_index] = '\0';
				state = LOOKING_FOR_AREA;
			} else {
				name[name_index++] = buffer[bytes_read];
				if (name_index == BUFFER_SIZE - 1) {
					name_index = 0;
					state = LOOKING_FOR_ENTRY;
				}	
			}
			break;
		case LOOKING_FOR_AREA:
			if (buffer[bytes_read] == '"') {
				area_index = 0;
				state = READING_AREA;
			}	
			break;
		case READING_AREA:
			if (buffer[bytes_read] == '"') {
				area[area_index] = '\0';
				state = LOOKING_FOR_HOST;
			} else {
				area[area_index++] = buffer[bytes_read];
				if (area_index == BUFFER_SIZE - 1) {
					area_index = 0;
					state = LOOKING_FOR_ENTRY;
				}	
			}
			break;
		case LOOKING_FOR_HOST:
			if (buffer[bytes_read] == '"') {
				host_index = 0;
				state = READING_HOST;
			}	
			break;
		case READING_HOST:
			if (buffer[bytes_read] == '"') {
				host[host_index] = '\0';
				state = LOOKING_FOR_CONTINENT;
			} else {
				host[host_index++] = buffer[bytes_read];
				if (host_index == BUFFER_SIZE - 1) {
					host_index = 0;
					state = LOOKING_FOR_ENTRY;
				}	
			}
			break;
		case LOOKING_FOR_CONTINENT:
			if (buffer[bytes_read] == '"') {
				continent_index = 0;
				state = READING_CONTINENT;
			}	
			break;
		case READING_CONTINENT:
			if (buffer[bytes_read] == '"') {
				continent[continent_index] = '\0';
				state = LOOKING_FOR_CLOSING;
			} else {
				continent[continent_index++] = buffer[bytes_read];
				if (continent_index == BUFFER_SIZE - 1) {
					continent_index = 0;
					state = LOOKING_FOR_ENTRY;
				}	
			}
			break;
		case LOOKING_FOR_CLOSING:
			if (buffer[bytes_read] == '}') {
				/* add entry to list of potential servers. */
				server_count++;
				temp = realloc(potential_servers, 
					sizeof(gutenfetch_server_t *) * (server_count + 1));
				if (temp == NULL) {
					close(fd);
					gutenfetch_free_servers(potential_servers);
					return GUTENFETCH_NOMEM;
				}
				potential_servers = temp;
				potential_servers[server_count - 1] = 
					gutenfetch_new_server(host, name, area, 
						gutenfetch_get_continent_from_string(continent));
				
				/* don't add the server if there was a problem. */
				if (potential_servers[server_count - 1] == NULL)
					server_count--;
				else	
					potential_servers[server_count] = NULL;
					
				state = LOOKING_FOR_ENTRY;
			}	
			break;
//		case IGNORE_UNTIL_NEWLINE:
//			if (buffer[bytes_read] == '\n')
//				state = last_state;
//			break;
		}
		++bytes_read;
	}
	close(fd);

	return errcode;
}

/**
 * gutenfetch_list_servers
 *
 * Return a NULL-terminated list of potential servers.
 *
 * @param continent Restrict results to servers on
 * 		a specific continent if you would like.
 * @return A NULL-terminated list of potential gutenfetch
 *		servers on the continent specified.
 */
gutenfetch_server_t **
gutenfetch_list_servers(gutenfetch_continent_t continent)
{
	int i;
	int num_of_servers = 0;
	gutenfetch_server_t **ss;
	size_t ssi;


	/* count the potential servers we have in our list. */
	i = 0;
	while (potential_servers[i] != NULL) {
		if ((continent == ALL_CONTINENTS) || (potential_servers[i]->continent == continent))
			++num_of_servers;
		++i;
	}
	ss = malloc(sizeof(gutenfetch_server_t*) * (num_of_servers + 1));
	
	if (ss == NULL)
		return NULL;

	ssi = 0;
	i = 0;
	while (potential_servers[i] != NULL) {
		if ((continent == ALL_CONTINENTS) || (potential_servers[i]->continent == continent)) {
			ss[ssi++] = gutenfetch_duplicate_server( potential_servers[i] );
		}
		++i;
	}	
	ss[ssi] = NULL;
	return ss;	
}

/**
 * gutenfetch_get_active_server
 *
 * Retreive a copy of the active server.
 *
 * @return NULL or a copy of the current active server which must
 *		be freed using gutenfetch_free_server().
 */
gutenfetch_server_t *
gutenfetch_get_active_server(void)
{
	gutenfetch_server_t *server;

#ifdef HAVE_PTHREAD
	pthread_mutex_lock(&active_server_mutex);
#endif	
	server = gutenfetch_duplicate_server(active_server);
#ifdef HAVE_PTHREAD	
	pthread_mutex_unlock(&active_server_mutex);
#endif

	return server;
}

/**
 * gutenfetch_get_aussie_server
 *
 * Retreive a copy of the active australian server.
 *
 * @return NULL or a copy of the current active server which must
 *		be freed using gutenfetch_free_server().
 */
gutenfetch_server_t *
gutenfetch_get_aussie_server(void)
{
	gutenfetch_server_t *server;

#ifdef HAVE_PTHREAD
	pthread_mutex_lock(&aussie_server_mutex);
#endif	
	server = gutenfetch_duplicate_server(aussie_server);
#ifdef HAVE_PTHREAD	
	pthread_mutex_unlock(&aussie_server_mutex);
#endif

	return server;
}


/**
 * gutenfetch_set_active_server
 *
 * A short version of gutenfetch_set_active_server_full.
 * It only takes the url of the gutenfetch server.
 *
 * @param url The full URL of the gutenfetch server.
 * @return GUTENFETCH_OK, or an error code.
 */
gutenfetch_error_t 
gutenfetch_set_active_server(char *url)
{
	gutenfetch_error_t errcode;
	gutenfetch_server_t *server;

	if (url == NULL)
		return GUTENFETCH_BAD_PARAM;

	server = gutenfetch_new_server(url, NULL, NULL, UNKNOWN_CONTINENT);
	errcode = gutenfetch_set_active_server_full(server);
	gutenfetch_free_server(server);
	return errcode;
}

/**
 * gutenfetch_ser_active_server_full
 *
 * Set the current active server from a valid
 * gutenfetch_server_t structre.
 *
 * @param server The server we wish to make the current
 *		gutenfetch server.
 * @return GUTENFETCH_OK, or another error code.
 */
gutenfetch_error_t
gutenfetch_set_active_server_full(gutenfetch_server_t *server)
{
	gutenfetch_server_t *temp0;

	if (server == NULL)
		return GUTENFETCH_BAD_PARAM;
	if (server->host == NULL)
		return GUTENFETCH_BAD_PARAM;

	temp0 = gutenfetch_duplicate_server(server);
	if (temp0 == NULL)
		return GUTENFETCH_NOMEM;

#ifdef HAVE_PTHREAD
	pthread_mutex_lock(&active_server_mutex);
#endif	
	gutenfetch_free_server(active_server);
	active_server = temp0;
#ifdef HAVE_PTHREAD	
	pthread_mutex_unlock(&active_server_mutex);
#endif	

	return GUTENFETCH_OK;	
}

/**
 * gutenfetch_new_server
 *
 * Given the raw information for a new server,
 * create and return a server structure.
 *
 * @param host The full URL of the host (required)
 * @param name The human readable name for the server or NULL.
 * @param area The human readable geographic area for the server or NULL.
 * @param continent The continent that the server is located on.
 * @return NULL on error, or a valid gutenfetch_server_t structure.
 */
gutenfetch_server_t *
gutenfetch_new_server(
	char *host,
	char *name,
	char *area,
	gutenfetch_continent_t continent)
{
	gutenfetch_server_t *server;
	if (host == NULL)
		return NULL;

	server = malloc(sizeof(gutenfetch_server_t));
	if (server == NULL)
		return NULL;
	
	server->host = strdup(host);
	server->name = (name != NULL) ? strdup(name) : NULL;
	server->area = (area != NULL) ? strdup(area) : NULL;
	server->continent = continent;

	return server;
}

/**
 * gutenfetch_duplicate_server
 *
 * Given a pointer to a gutenfetch server, duplicate it
 * and return the result.
 *
 * @param server The gutenfetch server we wish to duplicate.
 * @return The newly allocated gutenfetch server struct.
 *		This result must be freed with a call to gutenfetch_free_server().
 */
gutenfetch_server_t *
gutenfetch_duplicate_server(gutenfetch_server_t *server)
{
	gutenfetch_server_t *ns;

	if (server == NULL)
		return NULL;

	ns = malloc(sizeof(gutenfetch_server_t));
	if (ns == NULL)
		return NULL;
		
	ns->host = NULL;
	ns->name = NULL;
	ns->area = NULL;
	ns->continent = server->continent;
	
	if (server->host != NULL) {
		ns->host = strdup(server->host);
		if (ns->host == NULL) {
			gutenfetch_free_server(ns);
			return NULL;
		}
	}

	if (server->name != NULL) {
		ns->name = strdup(server->name);
		if (ns->name == NULL) {
			gutenfetch_free_server(ns);
			return NULL;
		}
	}

	if (server->area != NULL) {
		ns->area = strdup(server->area);
		if (ns->area == NULL) {
			gutenfetch_free_server(ns);
			return NULL;
		}
	}

	return ns;
}

/**
 * gutenfetch_free_server
 *
 * Release the memory used by a gutenfetch_server_t struct.
 *
 * @param server The server we wish to free.
 */
void
gutenfetch_free_server(gutenfetch_server_t *server)
{
	if (server != NULL) {
		FREE_NULL(server->host);
		FREE_NULL(server->name);
		FREE_NULL(server->area);
	}
	FREE_NULL(server);
}

/**
 * gutenfetch_free_servers
 *
 * Given a NULL-terminated list of gutenfetch_server_t **, release
 * the resources held by all.
 *
 * @param servers The list we wish to free.
 */
void
gutenfetch_free_servers(gutenfetch_server_t **servers)
{
	int i;

	if (servers != NULL) {
		for (i = 0; servers[i] != NULL; ++i) {
			gutenfetch_free_server( servers[i] );
		}
		FREE_NULL( servers );		
	}		
}


syntax highlighted by Code2HTML, v. 0.9.1