/*
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