/* libgutenfetch - a small library to help developers of utilities 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 $ $Author: johntabularasa $. */ #include "stddefs.h" #include "gutenfetch.h" #include "libgutenfetch_cache.h" #include "libgutenfetch_init.h" #include "libgutenfetch_utility.h" #include #ifdef HAVE_FCNTL_H # include #endif /* HAVE_FCNTL_H */ #ifdef HAVE_SYS_TYPES_H # include #endif /* HAVE_SYS_TYPES_H */ #ifdef HAVE_SYS_STAT_H # include #endif /* HAVE_SYS_STAT_H */ #ifdef HAVE_ERRNO_H # include #endif /* HAVE_ERRNO_H */ #ifdef HAVE_ASSERT_H # include #endif /* HAVE_ASSERT_H */ #ifdef HAVE_STRING_H # include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ static int gutenfetch_curl_progress_func(void *, double, double, double, double); typedef struct { void *data; int (*func)(void *, double, double, double, const char *); char msg[4096]; } progress_struct_t; extern int errno; static int cache_enabled = FALSE; static char *cache_base_dir = NULL; static time_t expires = 0; /** * gutenfetch_curl_write * * This is a private function which is called by * CURL periodically to write the downloaded data * to the file descriptor passed in through data. * * @param ptr The buffer of data which CURL downloaded. * @param size The size of each element in the buffer. * @param nelem The number of elements in the buffer. * @param data User supplied pointer to file descriptor where * buffer should be written to. * @return Same as write, number of bytes written on success, * -1 and errno is set on failure. CURL will abort * a transfer on failure. */ size_t gutenfetch_curl_write( void *ptr, size_t size, size_t nelem, void *data) { int fd; if (data == NULL) { return -1; } fd = *(int*)data; if (fd < 0) { return -1; } return write(fd, ptr, size * nelem); } /** * gutenfetch_curl_progress_func * * Called periodically by CURL to display the progress of * a current download. * * @param data User specified data to pass to func. * @param dltotal The total filesize of the download. * @param dlnow The amount we have downloaded. * @param ultotal The total filesize of the upload. * @param ulnow The amount we have uploaded. * @return An integer from the user supplied function. * returning anything but 0 will cause CURL to * abort the transfer. */ int gutenfetch_curl_progress_func( void *data, double dltotal, double dlnow, double ultotal, double ulnow) { progress_struct_t *pstruct = data; double total_progress = 0.0; assert(pstruct != NULL); assert(pstruct->func != NULL); if (dltotal != 0) { total_progress = dlnow / dltotal; } return pstruct->func( pstruct->data, total_progress, dltotal, dlnow, pstruct->msg); } /** * gutenfetch_cache_init() * * Initialize resources used by the cacheing module * of libgutenfetch. * * @param should_enable TRUE if we should enable caching. * @return */ gutenfetch_error_t gutenfetch_cache_init(int should_enable) { return gutenfetch_cache_enable(should_enable); } /** * gutenfetch_cache_shutdown * * Release all resources held by the cache. * */ void gutenfetch_cache_shutdown(void) { FREE_NULL(cache_base_dir); } gutenfetch_error_t gutenfetch_cache_enable(int e) { char *home_dir; int retval; if (e == TRUE) { home_dir = gutenfetch_util_get_home_directory(); if (home_dir != NULL) { FREE_NULL(cache_base_dir); cache_base_dir = gutenfetch_util_strcat(home_dir, "/.libgutenfetch-cache", NULL); if (cache_base_dir != NULL) { retval = mkdir(cache_base_dir, S_IRUSR | S_IWUSR | S_IXUSR ); if ((retval == 0) || ((retval == -1) && (errno == EEXIST))){ /* Success */ home_dir = gutenfetch_util_strcat(cache_base_dir, DIR_SEPARATOR, NULL); FREE_NULL(cache_base_dir); cache_base_dir = home_dir; cache_enabled = TRUE; expires = (60 * 60 * 24 * 7); // 1 week } else { cache_enabled = FALSE; FREE_NULL(cache_base_dir); return GUTENFETCH_UNABLE_TO_INIT_CACHE; } } } } else { cache_enabled = FALSE; FREE_NULL(cache_base_dir); } return GUTENFETCH_OK; } int gutenfetch_cache_is_enabled(void) { return cache_enabled; } void gutenfetch_cache_set_expires(time_t t) { expires = t; } time_t gutenfetch_cache_get_expires(void) { return expires; } /** * gutenfetch_cache_flush * * Remove all files in the cache. */ gutenfetch_error_t gutenfetch_cache_flush(void) { if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) { gutenfetch_util_rm_below_dir(cache_base_dir); } return GUTENFETCH_OK; } /** * gutenfetch_cache_fetch * * This function looks for a file in the cache, if it * finds it, it returns a file descriptor for reading * if it doesn't it fetchs the file, stores it in the * cache and returns a file descriptor for reading. * it it can't do that it returns -1. * * @param loc Whether we should look in australian or non-australian server. * @param file The file we are looking for. * @param progress_func NULL or a pointer of a function to display progress * to. * @param progress_func_data Optional user supplied data to progress function. * @return A valid file descriptor or -1. */ int gutenfetch_cache_fetch( server_location_t loc, const char *file, int (*progress_func)(void *, double, double, double, const char *), void *progress_func_data) { CURL *handle; progress_struct_t progress; gutenfetch_server_t *server; char *cache_filename = NULL; char *temp_filename = NULL; struct stat sb; time_t now; int fd = -1; char *url; if (file == NULL) return -1; /* Look for the file in the cache */ if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) { if ((strlen(file) >= 2) && (file[0] == '/')) { cache_filename = gutenfetch_util_strcat(cache_base_dir, &file[1], NULL); } else { cache_filename = gutenfetch_util_strcat(cache_base_dir, file, NULL); } if (cache_filename != NULL) { if(stat(cache_filename, &sb) != -1) { now = time(NULL); if ((now - sb.st_mtime) < expires) { /* in cache and valid. */ fd = open(cache_filename, O_RDONLY); FREE_NULL(cache_filename); return fd; } else { unlink(cache_filename); } } } } if (fd == -1) { /* The file doesn't exist in the cache */ // lets try to open a temp file to write this to. fd = gutenfetch_util_get_temp_file(&temp_filename); } /* if we still don't have something, fail! */ if ((fd == -1) || (temp_filename == NULL)) { FREE_NULL(temp_filename); FREE_NULL(cache_filename); close(fd); return -1; } /* Get the server. */ /* XXX The australian server is not accessible from the US, if there is anyone who can get to it and wants to test this code please feel free. */ // if (loc == NON_AUSTRALIAN) { server = gutenfetch_get_active_server(); // } else { // server = gutenfetch_get_aussie_server(); // } if (server == NULL) { if (fd != -1) { close(fd); } FREE_NULL(cache_filename); return -1; } /* Build the URL */ url = gutenfetch_util_build_URL(server, file); gutenfetch_free_server(server); handle = gutenfetch_init_curl_handle(); if (handle == NULL) { if (fd != -1) { close (fd); } FREE_NULL(cache_filename); FREE_NULL(url); return -1; } // Assign the URL to curl. curl_easy_setopt(handle, CURLOPT_URL, url); // Setup our progress display function. if (progress_func != NULL) { progress.data = progress_func_data; progress.func = progress_func; snprintf(progress.msg, 4096, "Downloading %s", file); curl_easy_setopt(handle, CURLOPT_NOPROGRESS, FALSE); curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, gutenfetch_curl_progress_func); curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, &progress); } else { curl_easy_setopt(handle, CURLOPT_NOPROGRESS, TRUE); } // setup our write function curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, gutenfetch_curl_write); // fd is guaranteed to be valid here! curl_easy_setopt(handle, CURLOPT_WRITEDATA, &fd); if (curl_easy_perform(handle) != 0) { FREE_NULL(cache_filename); close(fd); } else { lseek(fd, 0, SEEK_SET); } FREE_NULL(url); if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) { if(gutenfetch_util_move(temp_filename, cache_filename) == 1) { unlink(temp_filename); close(fd); /* close the tempfile */ fd = open(cache_filename, O_RDONLY); if (fd > 0) { lseek(fd, 0, SEEK_SET); } } } FREE_NULL(temp_filename); FREE_NULL(cache_filename); return fd; } /** * gutenfetch_cache_flush_old * * Flush all old entries from the cache. */ void gutenfetch_cache_flush_old(void) { if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) { gutenfetch_util_rm_old_below_dir(expires, cache_base_dir); } }