/*
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 <curl/curl.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif /* HAVE_FCNTL_H */
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif /* HAVE_SYS_STAT_H */
#ifdef HAVE_ERRNO_H
# include <errno.h>
#endif /* HAVE_ERRNO_H */
#ifdef HAVE_ASSERT_H
# include <assert.h>
#endif /* HAVE_ASSERT_H */
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1