1 /*
2 gutenfetch - a small utility to list and fetch books available through
3 project gutenberg
4
5 Copyright (C) 2001, 2002, 2003, 2004 Russell Francis
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the
19
20 Free Software Foundation, Inc.
21 59 Temple Place, Suite 330
22 Boston, MA 02111-1307 USA
23
24 Last updated on $Date: 2004/07/07 02:41:22 $ by $Author: johntabularasa $.
25 */
26 #include "stddefs.h"
27 #include "libgutenfetch_servers.h"
28 #include "list.h"
29 #ifdef HAVE_STDIO_H
30 # include <stdio.h>
31 #endif
32 #ifdef HAVE_FCNTL_H
33 # include <fcntl.h>
34 #endif
35 #ifdef HAVE_STDLIB_H
36 # include <stdlib.h>
37 #endif
38 #ifdef HAVE_STRING_H
39 # include <string.h>
40 #endif
41 #ifdef HAVE_STRINGS_H
42 # include <strings.h>
43 #endif
44 #ifdef HAVE_PTHREAD
45 #ifdef HAVE_PTHREAD_H
46 # include <pthread.h>
47 #endif
48 #endif
49
50 #ifdef HAVE_PTHREAD
51 static pthread_mutex_t active_server_mutex;
52 static pthread_mutex_t aussie_server_mutex;
53 #endif /* HAVE_PTHREAD */
54 static gutenfetch_server_t *active_server = NULL;
55 static gutenfetch_server_t *aussie_server = NULL;
56 static gutenfetch_server_t **potential_servers = NULL;
57
58 /* Private functions */
59
60 gutenfetch_continent_t
61 gutenfetch_get_continent_from_string(char *);
62
63
64 /**
65 * gutenfetch_get_continent_from_string
66 *
67 * Given a string, return the numeric constant
68 * associated with that continent.
69 *
70 * @param str The string representing an unknown continent.
71 * @return A valid gutenfetch_continent_t, which relates to
72 * the string.
73 */
74 gutenfetch_continent_t
gutenfetch_get_continent_from_string(char * str)75 gutenfetch_get_continent_from_string(char *str)
76 {
77 if (str != NULL) {
78 if (strcasecmp(str, "africa") == 0) {
79 return AFRICA;
80 } else if (strcasecmp(str, "asia") == 0) {
81 return ASIA;
82 } else if ((strcasecmp(str, "australasia_oceania") == 0) ||
83 (strcasecmp(str, "australia") == 0) ||
84 (strcasecmp(str, "oceania") ==0)) {
85 return AUSTRALASIA_OCEANIA;
86 } else if (strcasecmp(str, "europe") == 0) {
87 return EUROPE;
88 } else if (strcasecmp(str, "north_america") == 0) {
89 return NORTH_AMERICA;
90 } else if (strcasecmp(str, "south_america") == 0) {
91 return SOUTH_AMERICA;
92 }
93 }
94
95 return UNKNOWN_CONTINENT;
96 }
97
98
99 /* Global functions */
100
101 /**
102 * gutenfetch_servers_init
103 *
104 * Initialize our default servers and set any
105 * other persistent variables for this module.
106 *
107 * @return GUTENFETCH_OK on success or an error code.
108 */
109 gutenfetch_error_t
gutenfetch_servers_init(void)110 gutenfetch_servers_init(void)
111 {
112 gutenfetch_server_t *tserv;
113
114 #ifdef HAVE_PTHREAD
115 if (pthread_mutex_init(&active_server_mutex, NULL) != 0)
116 return GUTENFETCH_SEE_ERRNO;
117 if (pthread_mutex_init(&aussie_server_mutex, NULL) != 0)
118 return GUTENFETCH_SEE_ERRNO;
119 #endif
120
121 /* Setup our default server */
122 tserv = gutenfetch_new_server(
123 "ftp://ibiblio.org/pub/docs/books/gutenberg/",
124 "University of North Carolina - FTP",
125 "Chapel Hill, North Carolina",
126 NORTH_AMERICA);
127
128 if (tserv == NULL)
129 return GUTENFETCH_NOMEM;
130
131 #ifdef HAVE_PTHREAD
132 pthread_mutex_lock(&active_server_mutex);
133 #endif
134 active_server = tserv;
135 #ifdef HAVE_PTHREAD
136 pthread_mutex_unlock(&active_server_mutex);
137 #endif
138
139 tserv = gutenfetch_new_server(
140 "ftp://gutenberg.net.au/",
141 "Project Gutenberg of Australia",
142 "??, Australia",
143 AUSTRALASIA_OCEANIA);
144
145 if (tserv == NULL)
146 return GUTENFETCH_NOMEM;
147 #ifdef HAVE_PTHREAD
148 pthread_mutex_lock(&aussie_server_mutex);
149 #endif
150 aussie_server = tserv;
151 #ifdef HAVE_PTHREAD
152 pthread_mutex_unlock(&aussie_server_mutex);
153 #endif
154
155 gutenfetch_load_potential_servers();
156 return GUTENFETCH_OK;
157 }
158
159 /**
160 * gutenfetch_servers_shutdown
161 *
162 * Free any resources held by this module.
163 */
164 void
gutenfetch_servers_shutdown(void)165 gutenfetch_servers_shutdown(void)
166 {
167 #ifdef HAVE_PTHREAD
168 pthread_mutex_lock(&active_server_mutex);
169 #endif
170 gutenfetch_free_server(active_server);
171 #ifdef HAVE_PTHREAD
172 pthread_mutex_unlock(&active_server_mutex);
173 pthread_mutex_destroy(&active_server_mutex);
174 #endif
175 #ifdef HAVE_PTHREAD
176 pthread_mutex_lock(&aussie_server_mutex);
177 #endif
178 gutenfetch_free_server(aussie_server);
179 #ifdef HAVE_PTHREAD
180 pthread_mutex_unlock(&aussie_server_mutex);
181 pthread_mutex_destroy(&aussie_server_mutex);
182 #endif
183 gutenfetch_free_servers(potential_servers);
184 }
185
186 /**
187 * gutenfetch_load_potential_servers
188 *
189 * Read in a list of potential servers and store.
190 * Currently reads from a file on disk but perhaps
191 * in the future it could look on the network for
192 * a newer copy. ;)
193 *
194 * @return GUTENFETCH_OK or another error code.
195 */
196 gutenfetch_error_t
gutenfetch_load_potential_servers(void)197 gutenfetch_load_potential_servers(void)
198 {
199 #define POTSERVERSFILE "servers.txt"
200 #define BUFFER_SIZE 4096
201 int fd;
202 int state;
203 // int last_state;
204 char *filename;
205 char buffer[BUFFER_SIZE];
206 gutenfetch_error_t errcode;
207 size_t bytes_avail;
208 size_t bytes_read;
209 size_t filename_length;
210 size_t host_index = 0;
211 char host[BUFFER_SIZE];
212 size_t area_index = 0;
213 char area[BUFFER_SIZE];
214 size_t name_index = 0;
215 char name[BUFFER_SIZE];
216 size_t continent_index = 0;
217 char continent[BUFFER_SIZE];
218 size_t server_count;
219 // gutenfetch_server_t **pot_servers = NULL;
220 gutenfetch_server_t **temp = NULL;
221
222 /* This is a list of the states we can be
223 * in while parsing the servers.txt file. */
224 enum {
225 LOOKING_FOR_ENTRY,
226 LOOKING_FOR_NAME,
227 READING_NAME,
228 LOOKING_FOR_AREA,
229 READING_AREA,
230 LOOKING_FOR_HOST,
231 READING_HOST,
232 LOOKING_FOR_CONTINENT,
233 READING_CONTINENT,
234 LOOKING_FOR_CLOSING,
235 IGNORE_UNTIL_NEWLINE
236 };
237
238
239 filename_length = strlen(DATADIR) + strlen(DIR_SEPARATOR) +
240 strlen(POTSERVERSFILE) + 1;
241 filename = malloc(sizeof(char) * filename_length);
242 snprintf(filename, filename_length, "%s%s%s",
243 DATADIR, DIR_SEPARATOR, POTSERVERSFILE);
244
245 fd = open(filename, O_RDONLY);
246 FREE_NULL(filename);
247 if (fd == -1)
248 return GUTENFETCH_SEE_ERRNO;
249
250 server_count = 0;
251 bytes_avail = 0;
252 bytes_read = 0;
253 state = LOOKING_FOR_ENTRY;
254 while (TRUE) {
255 if (bytes_read == bytes_avail) { /* read more. */
256 bytes_avail = read(fd, buffer, BUFFER_SIZE);
257 if (bytes_avail < 0) { /* Error condition. */
258 errcode = GUTENFETCH_SEE_ERRNO;
259 break;
260 } else if (bytes_avail == 0) { /* EOF */
261 errcode = GUTENFETCH_OK;
262 break;
263 }
264 bytes_read = 0;
265 }
266 switch (state) {
267 case LOOKING_FOR_ENTRY:
268 if (buffer[bytes_read] == '{')
269 state = LOOKING_FOR_NAME;
270 break;
271 case LOOKING_FOR_NAME:
272 if (buffer[bytes_read] == '"') {
273 name_index = 0;
274 state = READING_NAME;
275 }
276 break;
277 case READING_NAME:
278 if (buffer[bytes_read] == '"') {
279 name[name_index] = '\0';
280 state = LOOKING_FOR_AREA;
281 } else {
282 name[name_index++] = buffer[bytes_read];
283 if (name_index == BUFFER_SIZE - 1) {
284 name_index = 0;
285 state = LOOKING_FOR_ENTRY;
286 }
287 }
288 break;
289 case LOOKING_FOR_AREA:
290 if (buffer[bytes_read] == '"') {
291 area_index = 0;
292 state = READING_AREA;
293 }
294 break;
295 case READING_AREA:
296 if (buffer[bytes_read] == '"') {
297 area[area_index] = '\0';
298 state = LOOKING_FOR_HOST;
299 } else {
300 area[area_index++] = buffer[bytes_read];
301 if (area_index == BUFFER_SIZE - 1) {
302 area_index = 0;
303 state = LOOKING_FOR_ENTRY;
304 }
305 }
306 break;
307 case LOOKING_FOR_HOST:
308 if (buffer[bytes_read] == '"') {
309 host_index = 0;
310 state = READING_HOST;
311 }
312 break;
313 case READING_HOST:
314 if (buffer[bytes_read] == '"') {
315 host[host_index] = '\0';
316 state = LOOKING_FOR_CONTINENT;
317 } else {
318 host[host_index++] = buffer[bytes_read];
319 if (host_index == BUFFER_SIZE - 1) {
320 host_index = 0;
321 state = LOOKING_FOR_ENTRY;
322 }
323 }
324 break;
325 case LOOKING_FOR_CONTINENT:
326 if (buffer[bytes_read] == '"') {
327 continent_index = 0;
328 state = READING_CONTINENT;
329 }
330 break;
331 case READING_CONTINENT:
332 if (buffer[bytes_read] == '"') {
333 continent[continent_index] = '\0';
334 state = LOOKING_FOR_CLOSING;
335 } else {
336 continent[continent_index++] = buffer[bytes_read];
337 if (continent_index == BUFFER_SIZE - 1) {
338 continent_index = 0;
339 state = LOOKING_FOR_ENTRY;
340 }
341 }
342 break;
343 case LOOKING_FOR_CLOSING:
344 if (buffer[bytes_read] == '}') {
345 /* add entry to list of potential servers. */
346 server_count++;
347 temp = realloc(potential_servers,
348 sizeof(gutenfetch_server_t *) * (server_count + 1));
349 if (temp == NULL) {
350 close(fd);
351 gutenfetch_free_servers(potential_servers);
352 return GUTENFETCH_NOMEM;
353 }
354 potential_servers = temp;
355 potential_servers[server_count - 1] =
356 gutenfetch_new_server(host, name, area,
357 gutenfetch_get_continent_from_string(continent));
358
359 /* don't add the server if there was a problem. */
360 if (potential_servers[server_count - 1] == NULL)
361 server_count--;
362 else
363 potential_servers[server_count] = NULL;
364
365 state = LOOKING_FOR_ENTRY;
366 }
367 break;
368 // case IGNORE_UNTIL_NEWLINE:
369 // if (buffer[bytes_read] == '\n')
370 // state = last_state;
371 // break;
372 }
373 ++bytes_read;
374 }
375 close(fd);
376
377 return errcode;
378 }
379
380 /**
381 * gutenfetch_list_servers
382 *
383 * Return a NULL-terminated list of potential servers.
384 *
385 * @param continent Restrict results to servers on
386 * a specific continent if you would like.
387 * @return A NULL-terminated list of potential gutenfetch
388 * servers on the continent specified.
389 */
390 gutenfetch_server_t **
gutenfetch_list_servers(gutenfetch_continent_t continent)391 gutenfetch_list_servers(gutenfetch_continent_t continent)
392 {
393 int i;
394 int num_of_servers = 0;
395 gutenfetch_server_t **ss;
396 size_t ssi;
397
398
399 /* count the potential servers we have in our list. */
400 i = 0;
401 while (potential_servers[i] != NULL) {
402 if ((continent == ALL_CONTINENTS) || (potential_servers[i]->continent == continent))
403 ++num_of_servers;
404 ++i;
405 }
406 ss = malloc(sizeof(gutenfetch_server_t*) * (num_of_servers + 1));
407
408 if (ss == NULL)
409 return NULL;
410
411 ssi = 0;
412 i = 0;
413 while (potential_servers[i] != NULL) {
414 if ((continent == ALL_CONTINENTS) || (potential_servers[i]->continent == continent)) {
415 ss[ssi++] = gutenfetch_duplicate_server( potential_servers[i] );
416 }
417 ++i;
418 }
419 ss[ssi] = NULL;
420 return ss;
421 }
422
423 /**
424 * gutenfetch_get_active_server
425 *
426 * Retreive a copy of the active server.
427 *
428 * @return NULL or a copy of the current active server which must
429 * be freed using gutenfetch_free_server().
430 */
431 gutenfetch_server_t *
gutenfetch_get_active_server(void)432 gutenfetch_get_active_server(void)
433 {
434 gutenfetch_server_t *server;
435
436 #ifdef HAVE_PTHREAD
437 pthread_mutex_lock(&active_server_mutex);
438 #endif
439 server = gutenfetch_duplicate_server(active_server);
440 #ifdef HAVE_PTHREAD
441 pthread_mutex_unlock(&active_server_mutex);
442 #endif
443
444 return server;
445 }
446
447 /**
448 * gutenfetch_get_aussie_server
449 *
450 * Retreive a copy of the active australian server.
451 *
452 * @return NULL or a copy of the current active server which must
453 * be freed using gutenfetch_free_server().
454 */
455 gutenfetch_server_t *
gutenfetch_get_aussie_server(void)456 gutenfetch_get_aussie_server(void)
457 {
458 gutenfetch_server_t *server;
459
460 #ifdef HAVE_PTHREAD
461 pthread_mutex_lock(&aussie_server_mutex);
462 #endif
463 server = gutenfetch_duplicate_server(aussie_server);
464 #ifdef HAVE_PTHREAD
465 pthread_mutex_unlock(&aussie_server_mutex);
466 #endif
467
468 return server;
469 }
470
471
472 /**
473 * gutenfetch_set_active_server
474 *
475 * A short version of gutenfetch_set_active_server_full.
476 * It only takes the url of the gutenfetch server.
477 *
478 * @param url The full URL of the gutenfetch server.
479 * @return GUTENFETCH_OK, or an error code.
480 */
481 gutenfetch_error_t
gutenfetch_set_active_server(char * url)482 gutenfetch_set_active_server(char *url)
483 {
484 gutenfetch_error_t errcode;
485 gutenfetch_server_t *server;
486
487 if (url == NULL)
488 return GUTENFETCH_BAD_PARAM;
489
490 server = gutenfetch_new_server(url, NULL, NULL, UNKNOWN_CONTINENT);
491 errcode = gutenfetch_set_active_server_full(server);
492 gutenfetch_free_server(server);
493 return errcode;
494 }
495
496 /**
497 * gutenfetch_ser_active_server_full
498 *
499 * Set the current active server from a valid
500 * gutenfetch_server_t structre.
501 *
502 * @param server The server we wish to make the current
503 * gutenfetch server.
504 * @return GUTENFETCH_OK, or another error code.
505 */
506 gutenfetch_error_t
gutenfetch_set_active_server_full(gutenfetch_server_t * server)507 gutenfetch_set_active_server_full(gutenfetch_server_t *server)
508 {
509 gutenfetch_server_t *temp0;
510
511 if (server == NULL)
512 return GUTENFETCH_BAD_PARAM;
513 if (server->host == NULL)
514 return GUTENFETCH_BAD_PARAM;
515
516 temp0 = gutenfetch_duplicate_server(server);
517 if (temp0 == NULL)
518 return GUTENFETCH_NOMEM;
519
520 #ifdef HAVE_PTHREAD
521 pthread_mutex_lock(&active_server_mutex);
522 #endif
523 gutenfetch_free_server(active_server);
524 active_server = temp0;
525 #ifdef HAVE_PTHREAD
526 pthread_mutex_unlock(&active_server_mutex);
527 #endif
528
529 return GUTENFETCH_OK;
530 }
531
532 /**
533 * gutenfetch_new_server
534 *
535 * Given the raw information for a new server,
536 * create and return a server structure.
537 *
538 * @param host The full URL of the host (required)
539 * @param name The human readable name for the server or NULL.
540 * @param area The human readable geographic area for the server or NULL.
541 * @param continent The continent that the server is located on.
542 * @return NULL on error, or a valid gutenfetch_server_t structure.
543 */
544 gutenfetch_server_t *
gutenfetch_new_server(char * host,char * name,char * area,gutenfetch_continent_t continent)545 gutenfetch_new_server(
546 char *host,
547 char *name,
548 char *area,
549 gutenfetch_continent_t continent)
550 {
551 gutenfetch_server_t *server;
552 if (host == NULL)
553 return NULL;
554
555 server = malloc(sizeof(gutenfetch_server_t));
556 if (server == NULL)
557 return NULL;
558
559 server->host = strdup(host);
560 server->name = (name != NULL) ? strdup(name) : NULL;
561 server->area = (area != NULL) ? strdup(area) : NULL;
562 server->continent = continent;
563
564 return server;
565 }
566
567 /**
568 * gutenfetch_duplicate_server
569 *
570 * Given a pointer to a gutenfetch server, duplicate it
571 * and return the result.
572 *
573 * @param server The gutenfetch server we wish to duplicate.
574 * @return The newly allocated gutenfetch server struct.
575 * This result must be freed with a call to gutenfetch_free_server().
576 */
577 gutenfetch_server_t *
gutenfetch_duplicate_server(gutenfetch_server_t * server)578 gutenfetch_duplicate_server(gutenfetch_server_t *server)
579 {
580 gutenfetch_server_t *ns;
581
582 if (server == NULL)
583 return NULL;
584
585 ns = malloc(sizeof(gutenfetch_server_t));
586 if (ns == NULL)
587 return NULL;
588
589 ns->host = NULL;
590 ns->name = NULL;
591 ns->area = NULL;
592 ns->continent = server->continent;
593
594 if (server->host != NULL) {
595 ns->host = strdup(server->host);
596 if (ns->host == NULL) {
597 gutenfetch_free_server(ns);
598 return NULL;
599 }
600 }
601
602 if (server->name != NULL) {
603 ns->name = strdup(server->name);
604 if (ns->name == NULL) {
605 gutenfetch_free_server(ns);
606 return NULL;
607 }
608 }
609
610 if (server->area != NULL) {
611 ns->area = strdup(server->area);
612 if (ns->area == NULL) {
613 gutenfetch_free_server(ns);
614 return NULL;
615 }
616 }
617
618 return ns;
619 }
620
621 /**
622 * gutenfetch_free_server
623 *
624 * Release the memory used by a gutenfetch_server_t struct.
625 *
626 * @param server The server we wish to free.
627 */
628 void
gutenfetch_free_server(gutenfetch_server_t * server)629 gutenfetch_free_server(gutenfetch_server_t *server)
630 {
631 if (server != NULL) {
632 FREE_NULL(server->host);
633 FREE_NULL(server->name);
634 FREE_NULL(server->area);
635 }
636 FREE_NULL(server);
637 }
638
639 /**
640 * gutenfetch_free_servers
641 *
642 * Given a NULL-terminated list of gutenfetch_server_t **, release
643 * the resources held by all.
644 *
645 * @param servers The list we wish to free.
646 */
647 void
gutenfetch_free_servers(gutenfetch_server_t ** servers)648 gutenfetch_free_servers(gutenfetch_server_t **servers)
649 {
650 int i;
651
652 if (servers != NULL) {
653 for (i = 0; servers[i] != NULL; ++i) {
654 gutenfetch_free_server( servers[i] );
655 }
656 FREE_NULL( servers );
657 }
658 }
659