/* 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 #endif #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_PTHREAD #ifdef HAVE_PTHREAD_H # include #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 ); } }