From 93715c46a99c96d6c866968312691ec9ab0f6a03 Mon Sep 17 00:00:00 2001 From: jcid Date: Sun, 7 Oct 2007 00:36:34 +0200 Subject: Initial revision --- src/cache.c | 932 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 932 insertions(+) create mode 100644 src/cache.c (limited to 'src/cache.c') diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 00000000..efe7d0c1 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,932 @@ +/* + * File: cache.c + * + * Copyright 2000, 2001, 2002, 2003, 2004 Jorge Arellano Cid + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo's cache module + */ + +#include /* for tolower */ +#include + +#include +#include +#include +#include +#include + +#include "msg.h" +#include "list.h" +#include "IO/Url.h" +#include "IO/IO.h" +#include "web.hh" +#include "dicache.h" +#include "nav.h" +#include "cookies.h" +#include "misc.h" +#include "capi.h" + +#include "timeout.hh" +#include "uicmd.hh" + +#define NULLKey 0 + +#define DEBUG_LEVEL 5 +#include "debug.h" + +/* Maximun initial size for the automatically-growing data buffer */ +#define MAX_INIT_BUF 1024*1024 +/* Maximum filesize for a URL, before offering a download */ +#define HUGE_FILESIZE 5*1024*1024 + +/* + * Local data types + */ + +typedef struct { + const DilloUrl *Url; /* Cached Url. Url is used as a primary Key */ + char *TypeDet; /* MIME type string (detected from data) */ + char *TypeHdr; /* MIME type string as from the HTTP Header */ + Dstr *Header; /* HTTP header */ + const DilloUrl *Location; /* New URI for redirects */ + Dstr *Data; /* Pointer to raw data */ + int TotalSize; /* Goal size of the whole data (0 if unknown) */ + uint_t Flags; /* Look Flag Defines in cache.h */ +} CacheEntry_t; + + +/* + * Local data + */ +/* A sorted list for cached data. Holds pointers to CacheEntry_t structs */ +static Dlist *CachedURLs; + +/* A list for cache clients. + * Although implemented as a list, we'll call it ClientQueue --Jcid */ +static Dlist *ClientQueue; + +/* A list for delayed clients (it holds weak pointers to cache entries, + * which are used to make deferred calls to Cache_process_queue) */ +static Dlist *DelayedQueue; +static uint_t DelayedQueueIdleId = 0; + + +/* + * Forward declarations + */ +static void Cache_process_queue(CacheEntry_t *entry); +static void Cache_delayed_process_queue(CacheEntry_t *entry); + + +/* + * Determine if two cache entries are equal (used by CachedURLs) + */ +static int Cache_entry_cmp(const void *v1, const void *v2) +{ + const CacheEntry_t *d1 = v1, *d2 = v2; + + return a_Url_cmp(d1->Url, d2->Url); +} + +/* + * Determine if two cache entries are equal, using a URL as key. + */ +static int Cache_entry_by_url_cmp(const void *v1, const void *v2) +{ + const DilloUrl *u1 = ((CacheEntry_t*)v1)->Url; + const DilloUrl *u2 = v2; + + return a_Url_cmp(u1, u2); +} + +/* + * Initialize dicache data + */ +void a_Cache_init(void) +{ + ClientQueue = dList_new(32); + DelayedQueue = dList_new(32); + CachedURLs = dList_new(256); + + /* inject the splash screen in the cache */ + { + DilloUrl *url = a_Url_new("about:splash", NULL, 0, 0, 0); + Dstr *ds = dStr_new(AboutSplash); + a_Cache_entry_inject(url, ds); + dStr_free(ds, 1); + a_Url_free(url); + } +} + +/* Client operations ------------------------------------------------------ */ + +/* + * Make a unique primary-key for cache clients + */ +static int Cache_client_make_key(void) +{ + static int ClientKey = 0; /* Provide a primary key for each client */ + + if (++ClientKey < 0) + ClientKey = 1; + return ClientKey; +} + +/* + * Add a client to ClientQueue. + * - Every client-camp is just a reference (except 'Web'). + * - Return a unique number for identifying the client. + */ +static int Cache_client_enqueue(const DilloUrl *Url, DilloWeb *Web, + CA_Callback_t Callback, void *CbData) +{ + int ClientKey; + CacheClient_t *NewClient; + + NewClient = dNew(CacheClient_t, 1); + ClientKey = Cache_client_make_key(); + NewClient->Key = ClientKey; + NewClient->Url = Url; + NewClient->Buf = NULL; + NewClient->Callback = Callback; + NewClient->CbData = CbData; + NewClient->Web = Web; + + dList_append(ClientQueue, NewClient); + + return ClientKey; +} + +/* + * Compare function for searching a Client by its key + */ +static int Cache_client_by_key_cmp(const void *client, const void *key) +{ + return ((CacheClient_t *)client)->Key - VOIDP2INT(key); +} + +/* + * Remove a client from the queue + */ +static void Cache_client_dequeue(CacheClient_t *Client, int Key) +{ + if (!Client) { + Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp); + } + if (Client) { + dList_remove(ClientQueue, Client); + a_Web_free(Client->Web); + dFree(Client); + } +} + + +/* Entry operations ------------------------------------------------------- */ + +/* + * Set safe values for a new cache entry + */ +static void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url) +{ + NewEntry->Url = a_Url_dup(Url); + NewEntry->TypeDet = NULL; + NewEntry->TypeHdr = NULL; + NewEntry->Header = dStr_new(""); + NewEntry->Location = NULL; + NewEntry->Data = dStr_sized_new(8*1024); + NewEntry->TotalSize = 0; + NewEntry->Flags = 0; +} + +/* + * Get the data structure for a cached URL (using 'Url' as the search key) + * If 'Url' isn't cached, return NULL + */ +static CacheEntry_t *Cache_entry_search(const DilloUrl *Url) +{ + return dList_find_sorted(CachedURLs, Url, Cache_entry_by_url_cmp); +} + +/* + * Allocate and set a new entry in the cache list + */ +static CacheEntry_t *Cache_entry_add(const DilloUrl *Url) +{ + CacheEntry_t *old_entry, *new_entry; + + if ((old_entry = Cache_entry_search(Url))) { + MSG_WARN("Cache_entry_add, leaking an entry.\n"); + dList_remove(CachedURLs, old_entry); + } + + new_entry = dNew(CacheEntry_t, 1); + Cache_entry_init(new_entry, Url); /* Set safe values */ + dList_insert_sorted(CachedURLs, new_entry, Cache_entry_cmp); + return new_entry; +} + +/* + * Inject full page content directly into the cache. + * Used for "about:splash". May be used for "about:cache" too. + */ +void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds) +{ + CacheEntry_t *entry; + + if (!(entry = Cache_entry_search(Url))) + entry = Cache_entry_add(Url); + entry->Flags |= CA_GotData + CA_GotHeader + CA_GotLength + CA_InternalUrl; + dStr_truncate(entry->Data, 0); + dStr_append_l(entry->Data, data_ds->str, data_ds->len); + dStr_fit(entry->Data); + entry->TotalSize = entry->Data->len; +} + +/* + * Free the components of a CacheEntry_t struct. + */ +static void Cache_entry_free(CacheEntry_t *entry) +{ + a_Url_free((DilloUrl *)entry->Url); + dFree(entry->TypeDet); + dFree(entry->TypeHdr); + dStr_free(entry->Header, TRUE); + a_Url_free((DilloUrl *)entry->Location); + dStr_free(entry->Data, 1); + dFree(entry); +} + +/* + * Remove an entry, from the cache. + * All the entry clients are removed too! (it may stop rendering of this + * same resource on other windows, but nothing more). + */ +void Cache_entry_remove(CacheEntry_t *entry, DilloUrl *url) +{ + int i; + CacheClient_t *Client; + + if (!entry && !(entry = Cache_entry_search(url))) + return; + if (entry->Flags & CA_InternalUrl) + return; + + /* remove all clients for this entry */ + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + a_Cache_stop_client(Client->Key); + --i; + } + } + + /* remove from DelayedQueue */ + dList_remove(DelayedQueue, entry); + + /* remove from dicache */ + a_Dicache_invalidate_entry(entry->Url); + + /* remove from cache */ + dList_remove(CachedURLs, entry); + Cache_entry_free(entry); +} + +/* + * Wrapper for capi. + */ +void a_Cache_entry_remove_by_url(DilloUrl *url) +{ + Cache_entry_remove(NULL, url); +} + +/* Misc. operations ------------------------------------------------------- */ + +/* + * Try finding the url in the cache. If it hits, send the cache contents + * from there. If it misses, set up a new connection. + * + * - 'Web' is an auxiliar data structure with misc. parameters. + * - 'Call' is the callback that receives the data + * - 'CbData' is custom data passed to 'Call' + * Note: 'Call' and/or 'CbData' can be NULL, in that case they get set + * later by a_Web_dispatch_by_type, based on content/type and 'Web' data. + * + * Return value: A primary key for identifying the client, + * 0 if the client is aborted in the process. + */ +int a_Cache_open_url(void *web, CA_Callback_t Call, void *CbData) +{ + int ClientKey; + CacheEntry_t *entry; + DilloWeb *Web = web; + DilloUrl *Url = Web->url; + + if (URL_FLAGS(Url) & URL_E2EReload) { + /* remove current entry */ + Cache_entry_remove(NULL, Url); + } + + if ((entry = Cache_entry_search(Url))) { + /* URL is cached: feed our client with cached data */ + ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); + Cache_delayed_process_queue(entry); + + } else { + /* URL not cached: create an entry, send our client to the queue, + * and open a new connection */ + entry = Cache_entry_add(Url); + ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); + } + + return ClientKey; +} + +/* + * Get the pointer to the URL document, and its size, from the cache entry. + * Return: 1 cached, 0 not cached. + */ +int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) +{ + int i; + CacheEntry_t *entry; + + for (i = 0; (entry = Cache_entry_search(Url)); ++i) { + + /* Test for a redirection loop */ + if (entry->Flags & CA_RedirectLoop || i == 3) { + MSG_WARN("Redirect loop for URL: >%s<\n", URL_STR_(Url)); + break; + } + /* Test for a working redirection */ + if (entry && entry->Flags & CA_Redirect && entry->Location) { + Url = entry->Location; + } else + break; + } + + *BufSize = (entry) ? entry->Data->len : 0; + *PBuf = (entry) ? entry->Data->str : NULL; + return (entry ? 1 : 0); +} + +/* + * Extract a single field from the header, allocating and storing the value + * in 'field'. ('fieldname' must not include the trailing ':') + * Return a new string with the field-content if found (NULL on error) + * (This function expects a '\r' stripped header) + */ +static char *Cache_parse_field(const char *header, const char *fieldname) +{ + char *field; + uint_t i, j; + + for (i = 0; header[i]; i++) { + /* Search fieldname */ + for (j = 0; fieldname[j]; j++) + if (tolower(fieldname[j]) != tolower(header[i + j])) + break; + if (fieldname[j]) { + /* skip to next line */ + for ( i += j; header[i] != '\n'; i++); + continue; + } + + i += j; + while (header[i] == ' ') i++; + if (header[i] == ':') { + /* Field found! */ + while (header[++i] == ' '); + for (j = 0; header[i + j] != '\n'; j++); + field = dStrndup(header + i, j); + return field; + } + } + return NULL; +} + +#ifndef DISABLE_COOKIES +/* + * Extract multiple fields from the header. + */ +static Dlist *Cache_parse_multiple_fields(const char *header, + const char *fieldname) +{ + uint_t i, j; + Dlist *fields = dList_new(8); + char *field; + + for (i = 0; header[i]; i++) { + /* Search fieldname */ + for (j = 0; fieldname[j]; j++) + if (tolower(fieldname[j]) != tolower(header[i + j])) + break; + if (fieldname[j]) { + /* skip to next line */ + for (i += j; header[i] != '\n'; i++); + continue; + } + + i += j; + for ( ; header[i] == ' '; i++); + if (header[i] == ':') { + /* Field found! */ + while (header[++i] == ' '); + for (j = 0; header[i + j] != '\n'; j++); + field = dStrndup(header + i, j); + dList_append(fields, field); + } + } + return fields; +} +#endif /* !DISABLE_COOKIES */ + +/* + * Scan, allocate, and set things according to header info. + * (This function needs the whole header to work) + */ +static void Cache_parse_header(CacheEntry_t *entry, + const char *buf, size_t buf_size, int HdrLen) +{ + char *header = entry->Header->str; + char *Length, *Type, *location_str; +#ifndef DISABLE_COOKIES + Dlist *Cookies; + void *data; + int i; +#endif + + if (HdrLen < 12) { + /* Not enough info. */ + + } if (header[9] == '3' && header[10] == '0') { + /* 30x: URL redirection */ + entry->Flags |= CA_Redirect; + if (header[11] == '1') + entry->Flags |= CA_ForceRedirect; /* 301 Moved Permanently */ + else if (header[11] == '2') + entry->Flags |= CA_TempRedirect; /* 302 Temporal Redirect */ + + location_str = Cache_parse_field(header, "Location"); + entry->Location = a_Url_new(location_str, URL_STR_(entry->Url), 0, 0, 0); + dFree(location_str); + + } else if (strncmp(header + 9, "404", 3) == 0) { + entry->Flags |= CA_NotFound; + } + + if ((Length = Cache_parse_field(header, "Content-Length")) != NULL) { + entry->Flags |= CA_GotLength; + entry->TotalSize = strtol(Length, NULL, 10); + if (entry->TotalSize < 0) + entry->TotalSize = 0; + dFree(Length); + } + +#ifndef DISABLE_COOKIES + /* BUG: If a server feels like mixing Set-Cookie2 and Set-Cookie + * responses which aren't identical, then we have a problem. I don't + * know if that is a real issue though. */ + if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie2")) || + (Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) { + a_Cookies_set(Cookies, entry->Url); + for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) + dFree(data); + dList_free(Cookies); + } +#endif /* !DISABLE_COOKIES */ + + if (entry->TotalSize > 0) { + if (entry->TotalSize > HUGE_FILESIZE) { + entry->Flags |= CA_HugeFile; + } + /* Avoid some reallocs. With MAX_INIT_BUF we avoid a SEGFAULT + * with huge files (e.g. iso files). + * Note: the buffer grows automatically. */ + dStr_free(entry->Data, 1); + entry->Data = dStr_sized_new(MIN(entry->TotalSize+1, MAX_INIT_BUF)); + } + dStr_append_l(entry->Data, buf + HdrLen, (int)buf_size - HdrLen); + + /* Get Content-Type */ + if ((Type = Cache_parse_field(header, "Content-Type")) == NULL) { + MSG_HTTP("Server didn't send Content-Type in header.\n"); + } else { + entry->TypeHdr = Type; + /* This Content-Type is not trusted. It's checked against real data + * in Cache_process_queue(); only then CA_GotContentType becomes true. + */ + } +} + +/* + * Consume bytes until the whole header is got (up to a "\r\n\r\n" sequence) + * (Also strip '\r' chars from header) + */ +static int Cache_get_header(CacheEntry_t *entry, + const char *buf, size_t buf_size) +{ + size_t N, i; + Dstr *hdr = entry->Header; + + /* Header finishes when N = 2 */ + N = (hdr->len && hdr->str[hdr->len - 1] == '\n'); + for (i = 0; i < buf_size && N < 2; ++i) { + if (buf[i] == '\r' || !buf[i]) + continue; + N = (buf[i] == '\n') ? N + 1 : 0; + dStr_append_c(hdr, buf[i]); + } + + if (N == 2) { + /* Got whole header */ + _MSG("Header [buf_size=%d]\n%s", i, hdr->str); + entry->Flags |= CA_GotHeader; + dStr_fit(hdr); + /* Return number of header bytes in 'buf' [1 based] */ + return i; + } + return 0; +} + +/* + * Receive new data, update the reception buffer (for next read), update the + * cache, and service the client queue. + * + * This function gets called whenever the IO has new data. + * 'Op' is the operation to perform + * 'VPtr' is a (void) pointer to the IO control structure + */ +void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, + const DilloUrl *Url) +{ + int len; + CacheEntry_t *entry = Cache_entry_search(Url); + + /* Assert a valid entry (not aborted) */ + if (!entry) + return; + + if (Op == IOClose) { + if (entry->Flags & CA_GotLength && entry->TotalSize != entry->Data->len){ + MSG_HTTP("Content-Length does NOT match message body,\n" + " at: %s\n", URL_STR_(entry->Url)); + } + entry->Flags |= CA_GotData; + entry->Flags &= ~CA_Stopped; /* it may catch up! */ + entry->TotalSize = entry->Data->len; + dStr_fit(entry->Data); /* fit buffer size! */ + Cache_process_queue(entry); + return; + } else if (Op == IOAbort) { + /* unused */ + MSG("a_Cache_process_dbuf Op = IOAbort; not implemented!\n"); + return; + } + + if (!(entry->Flags & CA_GotHeader)) { + /* Haven't got the whole header yet */ + len = Cache_get_header(entry, buf, buf_size); + if (entry->Flags & CA_GotHeader) { + /* Let's scan, allocate, and set things according to header info */ + Cache_parse_header(entry, buf, buf_size, len); + /* Now that we have it parsed, let's update our clients */ + Cache_process_queue(entry); + } + return; + } + + dStr_append_l(entry->Data, buf, (int)buf_size); + Cache_process_queue(entry); +} + +/* + * Process redirections (HTTP 30x answers) + * (This is a work in progress --not finished yet) + */ +static int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw) +{ + DilloUrl *NewUrl; + + _MSG(" Cache_redirect: redirect_level = %d\n", bw->redirect_level); + + /* if there's a redirect loop, stop now */ + if (bw->redirect_level >= 5) + entry->Flags |= CA_RedirectLoop; + + if (entry->Flags & CA_RedirectLoop) { + a_UIcmd_set_msg(bw, "ERROR: redirect loop for: %s", URL_STR_(entry->Url)); + bw->redirect_level = 0; + return 0; + } + + if ((entry->Flags & CA_Redirect && entry->Location) && + (entry->Flags & CA_ForceRedirect || entry->Flags & CA_TempRedirect || + !entry->Data->len || entry->Data->len < 1024)) { + + _MSG(">>>Redirect from: %s\n to %s\n", + URL_STR_(entry->Url), URL_STR_(entry->Location)); + _MSG("%s", entry->Header->str); + + if (Flags & WEB_RootUrl) { + /* Redirection of the main page */ + NewUrl = a_Url_new(URL_STR_(entry->Location), URL_STR_(entry->Url), + 0, 0, 0); + if (entry->Flags & CA_TempRedirect) + a_Url_set_flags(NewUrl, URL_FLAGS(NewUrl) | URL_E2EReload); + a_Nav_push(bw, NewUrl); + a_Url_free(NewUrl); + } else { + /* Sub entity redirection (most probably an image) */ + if (!entry->Data->len) { + DEBUG_MSG(3,">>>Image redirection without entity-content<<<\n"); + } else { + DEBUG_MSG(3, ">>>Image redirection with entity-content<<<\n"); + } + } + } + return 0; +} + +/* + * Check whether a URL scheme is downloadable. + * Return: 1 enabled, 0 disabled. + */ +int Cache_download_enabled(const DilloUrl *url) +{ + if (!dStrcasecmp(URL_SCHEME(url), "http") || + !dStrcasecmp(URL_SCHEME(url), "https") || + !dStrcasecmp(URL_SCHEME(url), "ftp")) + return 1; + return 0; +} + +/* + * Don't process data any further, but let the cache fill the entry. + * (Currently used to handle WEB_RootUrl redirects, + * and to ignore image redirects --Jcid) + */ +static void Cache_null_client(int Op, CacheClient_t *Client) +{ + DilloWeb *Web = Client->Web; + + /* make the stop button insensitive when done */ + if (Op == CA_Close) { + if (Web->flags & WEB_RootUrl) { + /* Remove this client from our active list */ + a_Bw_close_client(Web->bw, Client->Key); + } + } + + /* else ignore */ + + return; +} + +/* + * Update cache clients for a single cache-entry + * Tasks: + * - Set the client function (if not already set) + * - Look if new data is available and pass it to client functions + * - Remove clients when done + * - Call redirect handler + * + * todo: Implement CA_Abort Op in client callback + */ +static void Cache_process_queue(CacheEntry_t *entry) +{ + uint_t i; + int st; + const char *Type; + CacheClient_t *Client; + DilloWeb *ClientWeb; + BrowserWindow *Client_bw = NULL; + static bool_t Busy = FALSE; + bool_t AbortEntry = FALSE; + bool_t OfferDownload = FALSE; + bool_t TypeMismatch = FALSE; + + if (Busy) + MSG_ERR("FATAL!: >>>> Cache_process_queue Caught busy!!! <<<<\n"); + if (!(entry->Flags & CA_GotHeader)) + return; + if (!(entry->Flags & CA_GotContentType)) { + st = a_Misc_get_content_type_from_data( + entry->Data->str, entry->Data->len, &Type); + if (st == 0 || entry->Flags & CA_GotData) { + if (a_Misc_content_type_check(entry->TypeHdr, Type) < 0) { + MSG_HTTP("Content-Type '%s' doesn't match the real data.\n", + entry->TypeHdr); + TypeMismatch = TRUE; + } + entry->TypeDet = dStrdup(Type); + entry->Flags |= CA_GotContentType; + } else + return; /* i.e., wait for more data */ + } + + Busy = TRUE; + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + ClientWeb = Client->Web; /* It was a (void*) */ + Client_bw = ClientWeb->bw; /* 'bw' in a local var */ + + if (ClientWeb->flags & WEB_RootUrl) { + if (!(entry->Flags & CA_MsgErased)) { + /* clear the "expecting for reply..." message */ + a_UIcmd_set_msg(Client_bw, ""); + entry->Flags |= CA_MsgErased; + } + if (TypeMismatch) { + a_UIcmd_set_msg(Client_bw,"HTTP warning: Content-Type '%s' " + "doesn't match the real data.", entry->TypeHdr); + } + if (entry->Flags & CA_Redirect) { + if (!Client->Callback) { + Client->Callback = Cache_null_client; + Client_bw->redirect_level++; + } + } else { + Client_bw->redirect_level = 0; + } + if (entry->Flags & CA_HugeFile) { + a_UIcmd_set_msg(Client_bw,"Huge file! (%dMB)", + entry->TotalSize / (1024*1024)); + AbortEntry = OfferDownload = TRUE; + } + } else { + /* For non root URLs, ignore redirections and 404 answers */ + if (entry->Flags & CA_Redirect || entry->Flags & CA_NotFound) + Client->Callback = Cache_null_client; + } + + /* Set the client function */ + if (!Client->Callback) { + Client->Callback = Cache_null_client; + if (TypeMismatch) { + AbortEntry = TRUE; + } else { + st = a_Web_dispatch_by_type( + entry->TypeHdr ? entry->TypeHdr : entry->TypeDet, + ClientWeb, &Client->Callback, &Client->CbData); + if (st == -1) { + /* MIME type is not viewable */ + if (ClientWeb->flags & WEB_RootUrl) { + /* prepare a download offer... */ + AbortEntry = OfferDownload = TRUE; + } else { + /* TODO: Resource Type not handled. + * Not aborted to avoid multiple connections on the same + * resource. A better idea is to abort the connection and + * to keep a failed-resource flag in the cache entry. */ + } + } + } + if (AbortEntry) { + a_Bw_remove_client(Client_bw, Client->Key); + Cache_client_dequeue(Client, NULLKey); + --i; /* Keep the index value in the next iteration */ + continue; + } + } + + /* Send data to our client */ + if ((Client->BufSize = entry->Data->len) > 0) { + Client->Buf = entry->Data->str; + (Client->Callback)(CA_Send, Client); + } + + /* Remove client when done */ + if (entry->Flags & CA_GotData) { + /* Copy flags to a local var */ + int flags = ClientWeb->flags; + /* We finished sending data, let the client know */ + (Client->Callback)(CA_Close, Client); + Cache_client_dequeue(Client, NULLKey); + --i; /* Keep the index value in the next iteration */ + + /* within CA_GotData, we assert just one redirect call */ + if (entry->Flags & CA_Redirect) + Cache_redirect(entry, flags, Client_bw); + } + } + } /* for */ + + if (AbortEntry) { + /* Abort the entry, remove it from cache, and maybe offer download. */ + DilloUrl *url = a_Url_dup(entry->Url); + a_Capi_conn_abort_by_url(url); + Cache_entry_remove(entry, NULL); + if (OfferDownload && Cache_download_enabled(url)) { + a_UIcmd_save_link(Client_bw, url); + } + a_Url_free(url); + } + + /* Trigger cleanup when there're no cache clients */ + if (dList_length(ClientQueue) == 0) { + MSG(" a_Dicache_cleanup()\n"); + a_Dicache_cleanup(); + } + + Busy = FALSE; + _MSG("QueueSize ====> %d\n", dList_length(ClientQueue)); +} + +/* + * Callback function for Cache_delayed_process_queue. + */ +static void Cache_delayed_process_queue_callback(void *data) +{ + void *entry; + + while ((entry = dList_nth_data(DelayedQueue, 0))) { + Cache_process_queue((CacheEntry_t *)entry); + /* note that if Cache_process_queue removes the entry, + * the following dList_remove has no effect. */ + dList_remove(DelayedQueue, entry); + } + DelayedQueueIdleId = 0; + a_Timeout_remove(); +} + +/* + * Set a call to Cache_process_queue from the main cycle. + */ +static void Cache_delayed_process_queue(CacheEntry_t *entry) +{ + /* there's no need to repeat entries in the queue */ + if (!dList_find(DelayedQueue, entry)) + dList_append(DelayedQueue, entry); + + if (DelayedQueueIdleId == 0) { + _MSG(" Setting timeout callback\n"); + a_Timeout_add(0.0, Cache_delayed_process_queue_callback, NULL); + DelayedQueueIdleId = 1; + } +} + +/* + * Last Client for this entry? + * Return: Client if true, NULL otherwise + * (cache.c has only one call to a capi function. This avoids a second one) + */ +CacheClient_t *a_Cache_client_get_if_unique(int Key) +{ + int i, n = 0; + CacheClient_t *Client, *iClient; + + if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp))) { + for (i = 0; (iClient = dList_nth_data(ClientQueue, i)); ++i) { + if (iClient->Url == Client->Url) { + ++n; + } + } + } + return (n == 1) ? Client : NULL; +} + +/* + * Remove a client from the client queue + * todo: notify the dicache and upper layers + */ +void a_Cache_stop_client(int Key) +{ + CacheClient_t *Client; + + if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp))) { + Cache_client_dequeue(Client, NULLKey); + } else { + _MSG("WARNING: Cache_stop_client, inexistent client\n"); + } +} + + +/* + * Memory deallocator (only called at exit time) + */ +void a_Cache_freeall(void) +{ + CacheClient_t *Client; + void *data; + + /* free the client queue */ + while ((Client = dList_nth_data(ClientQueue, 0))) + Cache_client_dequeue(Client, NULLKey); + + /* Remove every cache entry */ + while ((data = dList_nth_data(CachedURLs, 0))) { + dList_remove(CachedURLs, data); + Cache_entry_free(data); + } + /* Remove the cache list */ + dList_free(CachedURLs); +} -- cgit v1.2.3