1 /*
2 libgutenfetch - a small library to help developers of utilities to list
3 and fetch books available through 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
25 $Date: 2004/07/07 02:41:22 $
26 $Author: johntabularasa $.
27
28 */
29
30 #include "stddefs.h"
31 #include "gutenfetch.h"
32 #include "libgutenfetch_cache.h"
33 #include "libgutenfetch_init.h"
34 #include "libgutenfetch_utility.h"
35 #include <curl/curl.h>
36 #ifdef HAVE_FCNTL_H
37 # include <fcntl.h>
38 #endif /* HAVE_FCNTL_H */
39 #ifdef HAVE_SYS_TYPES_H
40 # include <sys/types.h>
41 #endif /* HAVE_SYS_TYPES_H */
42 #ifdef HAVE_SYS_STAT_H
43 # include <sys/stat.h>
44 #endif /* HAVE_SYS_STAT_H */
45 #ifdef HAVE_ERRNO_H
46 # include <errno.h>
47 #endif /* HAVE_ERRNO_H */
48 #ifdef HAVE_ASSERT_H
49 # include <assert.h>
50 #endif /* HAVE_ASSERT_H */
51 #ifdef HAVE_STRING_H
52 # include <string.h>
53 #endif /* HAVE_STRING_H */
54 #ifdef HAVE_STRINGS_H
55 # include <strings.h>
56 #endif /* HAVE_STRINGS_H */
57
58 static int
59 gutenfetch_curl_progress_func(void *, double, double, double, double);
60
61 typedef struct {
62 void *data;
63 int (*func)(void *, double, double, double, const char *);
64 char msg[4096];
65 } progress_struct_t;
66
67 extern int errno;
68 static int cache_enabled = FALSE;
69 static char *cache_base_dir = NULL;
70 static time_t expires = 0;
71
72
73 /**
74 * gutenfetch_curl_write
75 *
76 * This is a private function which is called by
77 * CURL periodically to write the downloaded data
78 * to the file descriptor passed in through data.
79 *
80 * @param ptr The buffer of data which CURL downloaded.
81 * @param size The size of each element in the buffer.
82 * @param nelem The number of elements in the buffer.
83 * @param data User supplied pointer to file descriptor where
84 * buffer should be written to.
85 * @return Same as write, number of bytes written on success,
86 * -1 and errno is set on failure. CURL will abort
87 * a transfer on failure.
88 */
89 size_t
gutenfetch_curl_write(void * ptr,size_t size,size_t nelem,void * data)90 gutenfetch_curl_write(
91 void *ptr,
92 size_t size,
93 size_t nelem,
94 void *data)
95 {
96 int fd;
97
98 if (data == NULL) {
99 return -1;
100 }
101
102 fd = *(int*)data;
103
104 if (fd < 0) {
105 return -1;
106 }
107
108 return write(fd, ptr, size * nelem);
109 }
110
111
112 /**
113 * gutenfetch_curl_progress_func
114 *
115 * Called periodically by CURL to display the progress of
116 * a current download.
117 *
118 * @param data User specified data to pass to func.
119 * @param dltotal The total filesize of the download.
120 * @param dlnow The amount we have downloaded.
121 * @param ultotal The total filesize of the upload.
122 * @param ulnow The amount we have uploaded.
123 * @return An integer from the user supplied function.
124 * returning anything but 0 will cause CURL to
125 * abort the transfer.
126 */
127 int
gutenfetch_curl_progress_func(void * data,double dltotal,double dlnow,double ultotal,double ulnow)128 gutenfetch_curl_progress_func(
129 void *data, double dltotal, double dlnow, double ultotal, double ulnow)
130 {
131 progress_struct_t *pstruct = data;
132 double total_progress = 0.0;
133
134 assert(pstruct != NULL);
135 assert(pstruct->func != NULL);
136
137 if (dltotal != 0) {
138 total_progress = dlnow / dltotal;
139 }
140
141 return pstruct->func(
142 pstruct->data, total_progress,
143 dltotal, dlnow, pstruct->msg);
144 }
145
146
147 /**
148 * gutenfetch_cache_init()
149 *
150 * Initialize resources used by the cacheing module
151 * of libgutenfetch.
152 *
153 * @param should_enable TRUE if we should enable caching.
154 * @return
155 */
156 gutenfetch_error_t
gutenfetch_cache_init(int should_enable)157 gutenfetch_cache_init(int should_enable)
158 {
159 return gutenfetch_cache_enable(should_enable);
160 }
161
162
163 /**
164 * gutenfetch_cache_shutdown
165 *
166 * Release all resources held by the cache.
167 *
168 */
169 void
gutenfetch_cache_shutdown(void)170 gutenfetch_cache_shutdown(void)
171 {
172 FREE_NULL(cache_base_dir);
173 }
174
175 gutenfetch_error_t
gutenfetch_cache_enable(int e)176 gutenfetch_cache_enable(int e)
177 {
178 char *home_dir;
179 int retval;
180
181 if (e == TRUE) {
182 home_dir = gutenfetch_util_get_home_directory();
183 if (home_dir != NULL) {
184 FREE_NULL(cache_base_dir);
185 cache_base_dir = gutenfetch_util_strcat(home_dir,
186 "/.libgutenfetch-cache", NULL);
187 if (cache_base_dir != NULL) {
188 retval = mkdir(cache_base_dir, S_IRUSR | S_IWUSR | S_IXUSR );
189 if ((retval == 0) ||
190 ((retval == -1) && (errno == EEXIST))){ /* Success */
191 home_dir = gutenfetch_util_strcat(cache_base_dir, DIR_SEPARATOR, NULL);
192 FREE_NULL(cache_base_dir);
193 cache_base_dir = home_dir;
194 cache_enabled = TRUE;
195 expires = (60 * 60 * 24 * 7); // 1 week
196 } else {
197 cache_enabled = FALSE;
198 FREE_NULL(cache_base_dir);
199 return GUTENFETCH_UNABLE_TO_INIT_CACHE;
200 }
201 }
202 }
203 } else {
204 cache_enabled = FALSE;
205 FREE_NULL(cache_base_dir);
206 }
207 return GUTENFETCH_OK;
208 }
209
210 int
gutenfetch_cache_is_enabled(void)211 gutenfetch_cache_is_enabled(void)
212 {
213 return cache_enabled;
214 }
215
216 void
gutenfetch_cache_set_expires(time_t t)217 gutenfetch_cache_set_expires(time_t t)
218 {
219 expires = t;
220 }
221
222 time_t
gutenfetch_cache_get_expires(void)223 gutenfetch_cache_get_expires(void)
224 {
225 return expires;
226 }
227
228 /**
229 * gutenfetch_cache_flush
230 *
231 * Remove all files in the cache.
232 */
233 gutenfetch_error_t
gutenfetch_cache_flush(void)234 gutenfetch_cache_flush(void)
235 {
236 if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) {
237 gutenfetch_util_rm_below_dir(cache_base_dir);
238 }
239 return GUTENFETCH_OK;
240 }
241
242 /**
243 * gutenfetch_cache_fetch
244 *
245 * This function looks for a file in the cache, if it
246 * finds it, it returns a file descriptor for reading
247 * if it doesn't it fetchs the file, stores it in the
248 * cache and returns a file descriptor for reading.
249 * it it can't do that it returns -1.
250 *
251 * @param loc Whether we should look in australian or non-australian server.
252 * @param file The file we are looking for.
253 * @param progress_func NULL or a pointer of a function to display progress
254 * to.
255 * @param progress_func_data Optional user supplied data to progress function.
256 * @return A valid file descriptor or -1.
257 */
258 int
gutenfetch_cache_fetch(server_location_t loc,const char * file,int (* progress_func)(void *,double,double,double,const char *),void * progress_func_data)259 gutenfetch_cache_fetch(
260 server_location_t loc,
261 const char *file,
262 int (*progress_func)(void *, double, double, double, const char *),
263 void *progress_func_data)
264 {
265 CURL *handle;
266 progress_struct_t progress;
267 gutenfetch_server_t *server;
268 char *cache_filename = NULL;
269 char *temp_filename = NULL;
270 struct stat sb;
271 time_t now;
272 int fd = -1;
273 char *url;
274
275 if (file == NULL)
276 return -1;
277
278 /* Look for the file in the cache */
279 if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) {
280 if ((strlen(file) >= 2) && (file[0] == '/')) {
281 cache_filename = gutenfetch_util_strcat(cache_base_dir, &file[1], NULL);
282 } else {
283 cache_filename = gutenfetch_util_strcat(cache_base_dir, file, NULL);
284 }
285 if (cache_filename != NULL) {
286 if(stat(cache_filename, &sb) != -1) {
287 now = time(NULL);
288 if ((now - sb.st_mtime) < expires) { /* in cache and valid. */
289 fd = open(cache_filename, O_RDONLY);
290 FREE_NULL(cache_filename);
291 return fd;
292 } else {
293 unlink(cache_filename);
294 }
295 }
296 }
297 }
298
299
300 if (fd == -1) { /* The file doesn't exist in the cache */
301 // lets try to open a temp file to write this to.
302 fd = gutenfetch_util_get_temp_file(&temp_filename);
303 }
304
305 /* if we still don't have something, fail! */
306 if ((fd == -1) || (temp_filename == NULL)) {
307 FREE_NULL(temp_filename);
308 FREE_NULL(cache_filename);
309 close(fd);
310 return -1;
311 }
312
313 /* Get the server. */
314 /* XXX The australian server is not accessible from the US,
315 if there is anyone who can get to it and wants to test
316 this code please feel free.
317 */
318 // if (loc == NON_AUSTRALIAN) {
319 server = gutenfetch_get_active_server();
320 // } else {
321 // server = gutenfetch_get_aussie_server();
322 // }
323
324 if (server == NULL) {
325 if (fd != -1) {
326 close(fd);
327 }
328 FREE_NULL(cache_filename);
329 return -1;
330 }
331
332 /* Build the URL */
333 url = gutenfetch_util_build_URL(server, file);
334 gutenfetch_free_server(server);
335
336 handle = gutenfetch_init_curl_handle();
337 if (handle == NULL) {
338 if (fd != -1) {
339 close (fd);
340 }
341 FREE_NULL(cache_filename);
342 FREE_NULL(url);
343 return -1;
344 }
345
346 // Assign the URL to curl.
347 curl_easy_setopt(handle, CURLOPT_URL, url);
348
349 // Setup our progress display function.
350 if (progress_func != NULL) {
351 progress.data = progress_func_data;
352 progress.func = progress_func;
353 snprintf(progress.msg, 4096, "Downloading %s", file);
354 curl_easy_setopt(handle, CURLOPT_NOPROGRESS, FALSE);
355 curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION,
356 gutenfetch_curl_progress_func);
357 curl_easy_setopt(handle, CURLOPT_PROGRESSDATA,
358 &progress);
359 } else {
360 curl_easy_setopt(handle, CURLOPT_NOPROGRESS, TRUE);
361 }
362
363 // setup our write function
364 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION,
365 gutenfetch_curl_write);
366
367 // fd is guaranteed to be valid here!
368 curl_easy_setopt(handle, CURLOPT_WRITEDATA, &fd);
369 if (curl_easy_perform(handle) != 0) {
370 FREE_NULL(cache_filename);
371 close(fd);
372 } else {
373 lseek(fd, 0, SEEK_SET);
374 }
375 FREE_NULL(url);
376
377 if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) {
378 if(gutenfetch_util_move(temp_filename, cache_filename) == 1) {
379 unlink(temp_filename);
380 close(fd); /* close the tempfile */
381 fd = open(cache_filename, O_RDONLY);
382 if (fd > 0) {
383 lseek(fd, 0, SEEK_SET);
384 }
385 }
386 }
387
388 FREE_NULL(temp_filename);
389 FREE_NULL(cache_filename);
390 return fd;
391 }
392
393 /**
394 * gutenfetch_cache_flush_old
395 *
396 * Flush all old entries from the cache.
397 */
398 void
gutenfetch_cache_flush_old(void)399 gutenfetch_cache_flush_old(void)
400 {
401 if ((cache_enabled == TRUE) && (cache_base_dir != NULL)) {
402 gutenfetch_util_rm_old_below_dir(expires, cache_base_dir);
403 }
404 }
405