r9624 jmb - in /trunk/tools/trace/src: Makefile trace.c
by netsurf@semichrome.net
Author: jmb
Date: Mon Oct 12 09:58:07 2009
New Revision: 9624
URL: http://source.netsurf-browser.org?rev=9624&view=rev
Log:
Fix the multitude of failure.
Modified:
trunk/tools/trace/src/Makefile
trunk/tools/trace/src/trace.c
Modified: trunk/tools/trace/src/Makefile
URL: http://source.netsurf-browser.org/trunk/tools/trace/src/Makefile?rev=9624...
==============================================================================
--- trunk/tools/trace/src/Makefile (original)
+++ trunk/tools/trace/src/Makefile Mon Oct 12 09:58:07 2009
@@ -1,4 +1,4 @@
# Sources
-DIR_SOURCES := shim.s trace.s
+DIR_SOURCES := shim.s trace.c
include build/makefiles/Makefile.subdir
Modified: trunk/tools/trace/src/trace.c
URL: http://source.netsurf-browser.org/trunk/tools/trace/src/trace.c?rev=9624&...
==============================================================================
--- trunk/tools/trace/src/trace.c (original)
+++ trunk/tools/trace/src/trace.c Mon Oct 12 09:58:07 2009
@@ -1,14 +1,17 @@
#include <stdint.h>
#include <stdio.h>
-extern uint32_t image__ro__base;
-extern uint32_t image__ro__limit;
+extern void __cyg_profile_enter(void *, void *);
+extern void __cyg_profile_exit(void *, void *);
+
+extern void *image__ro__base;
+extern void *image__ro__limit;
static uint32_t depth;
-static void print_function_name(uint32_t fn_address)
+static void print_function_name(void *fn_address)
{
- uint32_t *offaddr = ((uint32_t *) address) - 1;
+ uint32_t *offaddr = ((uint32_t *) fn_address) - 1;
uint32_t offset = *offaddr;
if ((offset >> 24) == 0xff) {
@@ -19,9 +22,11 @@
}
}
-void __cyg_profile_enter(uint32_t fn_address, uint32_t call_site)
+void __cyg_profile_enter(void *fn_address, void *call_site)
{
uint32_t i = depth;
+
+ (void) call_site;
/* Ignore if address is out of bounds */
if (fn_address < image__ro__base || image__ro__limit < fn_address)
@@ -36,9 +41,11 @@
depth++;
}
-void __cyg_profile_exit(uint32_t fn_address, uint32_t call_site)
+void __cyg_profile_exit(void *fn_address, void *call_site)
{
uint32_t i = depth;
+
+ (void) call_site;
/* Ignore if address is out of bounds */
if (fn_address < image__ro__base || image__ro__limit < fn_address)
13 years, 8 months
r9623 jmb - in /trunk/tools/trace: ./ Makefile build/ src/ src/Makefile src/shim.s src/trace.c
by netsurf@semichrome.net
Author: jmb
Date: Mon Oct 12 09:52:35 2009
New Revision: 9623
URL: http://source.netsurf-browser.org?rev=9623&view=rev
Log:
First cut at a call tracing library
Added:
trunk/tools/trace/
trunk/tools/trace/Makefile
trunk/tools/trace/build/ (with props)
trunk/tools/trace/src/
trunk/tools/trace/src/Makefile
trunk/tools/trace/src/shim.s
trunk/tools/trace/src/trace.c
Added: trunk/tools/trace/Makefile
URL: http://source.netsurf-browser.org/trunk/tools/trace/Makefile?rev=9623&vie...
==============================================================================
--- trunk/tools/trace/Makefile (added)
+++ trunk/tools/trace/Makefile Mon Oct 12 09:52:35 2009
@@ -1,0 +1,29 @@
+# Component settings
+COMPONENT := trace
+COMPONENT_VERSION := 0.0.1
+# Default to a static library
+COMPONENT_TYPE ?= lib-static
+
+# Setup the tooling
+include build/makefiles/Makefile.tools
+
+# Toolchain flags
+WARNFLAGS := -Wall -Wundef -Wpointer-arith -Wcast-align \
+ -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes \
+ -Wmissing-declarations -Wnested-externs -Werror -pedantic
+ifneq ($(GCCVER),2)
+ WARNFLAGS := $(WARNFLAGS) -Wextra
+endif
+CFLAGS := -D_BSD_SOURCE -I$(CURDIR)/include/ \
+ -I$(CURDIR)/src $(WARNFLAGS) $(CFLAGS)
+ifneq ($(GCCVER),2)
+ CFLAGS := $(CFLAGS) -std=c99
+else
+ # __inline__ is a GCCism
+ CFLAGS := $(CFLAGS) -Dinline="__inline__"
+endif
+
+include build/makefiles/Makefile.top
+
+# Extra installation rules
+INSTALL_ITEMS := $(INSTALL_ITEMS) /lib:$(OUTPUT)
Propchange: trunk/tools/trace/build/
------------------------------------------------------------------------------
--- svn:externals (added)
+++ svn:externals Mon Oct 12 09:52:35 2009
@@ -1,0 +1,1 @@
+makefiles svn://svn.netsurf-browser.org/trunk/tools/buildsystem/makefiles
Added: trunk/tools/trace/src/Makefile
URL: http://source.netsurf-browser.org/trunk/tools/trace/src/Makefile?rev=9623...
==============================================================================
--- trunk/tools/trace/src/Makefile (added)
+++ trunk/tools/trace/src/Makefile Mon Oct 12 09:52:35 2009
@@ -1,0 +1,4 @@
+# Sources
+DIR_SOURCES := shim.s trace.s
+
+include build/makefiles/Makefile.subdir
Added: trunk/tools/trace/src/shim.s
URL: http://source.netsurf-browser.org/trunk/tools/trace/src/shim.s?rev=9623&v...
==============================================================================
--- trunk/tools/trace/src/shim.s (added)
+++ trunk/tools/trace/src/shim.s Mon Oct 12 09:52:35 2009
@@ -1,0 +1,26 @@
+#if !defined(__aof__)
+ .section ".text"
+
+ .global image__ro__base
+ .global image__ro__limit
+
+image__ro__base:
+ .word Image$$RO$$Base
+image__ro__limit:
+ .word Image$$RO$$Limit
+#else
+ AREA |ARM$$Code|, CODE, READONLY
+
+ IMPORT |Image$$RO$$Base|
+ IMPORT |Image$$RO$$Limit|
+
+ EXPORT image__ro__base
+ EXPORT image__ro__limit
+
+image__ro__base
+ DCD |Image$$RO$$Base|
+image__ro__limit
+ DCD |Image$$RO$$Limit|
+
+ END
+#endif
Added: trunk/tools/trace/src/trace.c
URL: http://source.netsurf-browser.org/trunk/tools/trace/src/trace.c?rev=9623&...
==============================================================================
--- trunk/tools/trace/src/trace.c (added)
+++ trunk/tools/trace/src/trace.c Mon Oct 12 09:52:35 2009
@@ -1,0 +1,55 @@
+#include <stdint.h>
+#include <stdio.h>
+
+extern uint32_t image__ro__base;
+extern uint32_t image__ro__limit;
+
+static uint32_t depth;
+
+static void print_function_name(uint32_t fn_address)
+{
+ uint32_t *offaddr = ((uint32_t *) address) - 1;
+ uint32_t offset = *offaddr;
+
+ if ((offset >> 24) == 0xff) {
+ fprintf(stderr, "%s\n", (const char *) (offaddr -
+ ((offset & ~0xff000000) / 4)));
+ } else {
+ fprintf(stderr, "(unknown)\n");
+ }
+}
+
+void __cyg_profile_enter(uint32_t fn_address, uint32_t call_site)
+{
+ uint32_t i = depth;
+
+ /* Ignore if address is out of bounds */
+ if (fn_address < image__ro__base || image__ro__limit < fn_address)
+ return;
+
+ while (i-- > 0)
+ fputc(' ', stderr);
+
+ fprintf(stderr, "Entering ");
+ print_function_name(fn_address);
+
+ depth++;
+}
+
+void __cyg_profile_exit(uint32_t fn_address, uint32_t call_site)
+{
+ uint32_t i = depth;
+
+ /* Ignore if address is out of bounds */
+ if (fn_address < image__ro__base || image__ro__limit < fn_address)
+ return;
+
+ while (i-- > 0)
+ fputc(' ', stderr);
+
+ fprintf(stderr, "Leaving ");
+ print_function_name(fn_address);
+
+ depth--;
+}
+
13 years, 8 months
r9622 jmb - /branches/jmb/new-cache/content/llcache.c
by netsurf@semichrome.net
Author: jmb
Date: Sun Oct 11 18:37:27 2009
New Revision: 9622
URL: http://source.netsurf-browser.org?rev=9622&view=rev
Log:
Don't cache GET with query segment, as before.
Move FETCH_TYPE case between FETCH_HEADER and FETCH_DATA to better reflect when it will be issued. I suspect, however, that FETCH_TYPE is entirely redundant and it would be better to perform the type detection stuff in llcache when the first chunk of data arrives off the wire instead of within each and every fetch protocol handler.
Modified:
branches/jmb/new-cache/content/llcache.c
Modified: branches/jmb/new-cache/content/llcache.c
URL: http://source.netsurf-browser.org/branches/jmb/new-cache/content/llcache....
==============================================================================
--- branches/jmb/new-cache/content/llcache.c (original)
+++ branches/jmb/new-cache/content/llcache.c Sun Oct 11 18:37:27 2009
@@ -390,6 +390,9 @@
{
nserror error;
llcache_object *obj;
+ bool has_query;
+ url_func_result res;
+ struct url_components components;
/**
* Caching Rules:
@@ -399,9 +402,18 @@
* 3) POST requests are never cached
*
* \todo Find out if restriction (2) can be removed
- * \todo Actually check for GET with a query part
*/
- if (flags & LLCACHE_RETRIEVE_FORCE_FETCH || post != NULL) {
+
+ /* Look for a query segment */
+ res = url_get_components(url, &components);
+ if (res == URL_FUNC_NOMEM)
+ return NSERROR_NOMEM;
+
+ has_query = (components.query != NULL);
+
+ url_destroy_components(&components);
+
+ if (flags & LLCACHE_RETRIEVE_FORCE_FETCH || has_query || post != NULL) {
/* Create new object */
error = llcache_object_new(url, &obj);
if (error != NSERROR_OK)
@@ -986,14 +998,18 @@
/* Normal 2xx state machine */
/** \todo Merge FETCH_TYPE and FETCH_HEADER? */
- case FETCH_TYPE:
- /* Received MIME type for object */
- break;
case FETCH_HEADER:
/* Received a fetch header */
object->fetch.state = LLCACHE_FETCH_HEADERS;
error = llcache_fetch_process_header(object, data, size);
+ break;
+ case FETCH_TYPE:
+ /* Determined MIME type for object */
+ /* This is always emitted after all headers have been received
+ * and immediately before the first FETCH_DATA event. ::data
+ * specifies the detected type (defaulted, if no Content-Type
+ * header) and ::size contains the Content-Length or 0. */
break;
case FETCH_DATA:
/* Received some data */
13 years, 8 months
r9621 jmb - /branches/jmb/new-cache/content/llcache.c
by netsurf@semichrome.net
Author: jmb
Date: Sun Oct 11 10:16:53 2009
New Revision: 9621
URL: http://source.netsurf-browser.org?rev=9621&view=rev
Log:
Implement llcache_object_get_header.
A bunch of fixes to error recovery.
Modified:
branches/jmb/new-cache/content/llcache.c
Modified: branches/jmb/new-cache/content/llcache.c
URL: http://source.netsurf-browser.org/branches/jmb/new-cache/content/llcache....
==============================================================================
--- branches/jmb/new-cache/content/llcache.c (original)
+++ branches/jmb/new-cache/content/llcache.c Sun Oct 11 10:16:53 2009
@@ -321,7 +321,14 @@
const char *llcache_object_get_header(const llcache_object *object,
const char *key)
{
- /** \todo implement */
+ size_t i;
+
+ /* About as trivial as possible */
+ for (i = 0; i < object->num_headers; i++) {
+ if (strcasecmp(key, object->headers[i].name) == 0)
+ return object->headers[i].value;
+ }
+
return NULL;
}
@@ -1022,8 +1029,10 @@
/* Deal with any errors reported by event handlers */
if (error != NSERROR_OK) {
/** \todo Error handling */
- fetch_abort(object->fetch.fetch);
- object->fetch.fetch = NULL;
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
return;
}
@@ -1031,8 +1040,10 @@
error = llcache_object_notify_users(object);
if (error != NSERROR_OK) {
/** \todo Error handling */
- fetch_abort(object->fetch.fetch);
- object->fetch.fetch = NULL;
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
}
}
@@ -1066,6 +1077,7 @@
result = url_join(target, object->url, &absurl);
if (result != URL_FUNC_OK) {
/** \todo handle error */
+ return NSERROR_NOMEM;
}
/* Ensure target is normalised */
@@ -1076,6 +1088,7 @@
if (result != URL_FUNC_OK) {
/** \todo handle error */
+ return NSERROR_NOMEM;
}
/** \todo Ensure that redirects to file:/// don't happen? */
@@ -1088,6 +1101,8 @@
post = NULL;
} else {
/** \todo 300, 305, 307 */
+ free(url);
+ return NSERROR_OK;
}
/* Attempt to fetch target URL */
@@ -1098,9 +1113,8 @@
/* No longer require url */
free(url);
- if (error != NSERROR_OK) {
- /** \todo handle error */
- }
+ if (error != NSERROR_OK)
+ return error;
/* Move user(s) to replacement object */
for (user = object->users; user != NULL; user = next) {
13 years, 8 months
r9620 jmb - /branches/jmb/new-cache/content/llcache.c
by netsurf@semichrome.net
Author: jmb
Date: Sun Oct 11 10:02:27 2009
New Revision: 9620
URL: http://source.netsurf-browser.org?rev=9620&view=rev
Log:
Implement rather more of the fetch state machine
Modified:
branches/jmb/new-cache/content/llcache.c
Modified: branches/jmb/new-cache/content/llcache.c
URL: http://source.netsurf-browser.org/branches/jmb/new-cache/content/llcache....
==============================================================================
--- branches/jmb/new-cache/content/llcache.c (original)
+++ branches/jmb/new-cache/content/llcache.c Sun Oct 11 10:02:27 2009
@@ -20,9 +20,12 @@
* Low-level resource cache (implementation)
*/
+#define _GNU_SOURCE /* For strndup. Ugh. */
#include <stdlib.h>
#include <string.h>
#include <time.h>
+
+#include <curl/curl.h>
#include "content/fetch.h"
#include "content/llcache.h"
@@ -82,6 +85,12 @@
time_t last_modified; /**< Last-Modified: response header */
} llcache_cache_control;
+/** Representation of a fetch header */
+typedef struct {
+ char *name; /**< Header name */
+ char *value; /**< Header value */
+} llcache_header;
+
/** Low-level cache object */
/** \todo Consider whether a list is a sane container */
struct llcache_object {
@@ -105,7 +114,8 @@
uint32_t candidate_count; /**< Count of objects this is a
* candidate for */
- /** \todo Need fetch headers */
+ llcache_header *headers; /**< Fetch headers */
+ size_t num_headers; /**< Number of fetch headers */
};
/** Handler for fetch-related queries */
@@ -157,6 +167,15 @@
const char *target, llcache_object **replacement);
static nserror llcache_fetch_notmodified(llcache_object *object,
llcache_object **replacement);
+static nserror llcache_fetch_split_header(const char *data, size_t len,
+ char **name, char **value);
+static nserror llcache_fetch_parse_header(llcache_object *object,
+ const char *data, size_t len, char **name, char **value);
+static nserror llcache_fetch_process_header(llcache_object *object,
+ const char *data, size_t len);
+static nserror llcache_fetch_process_data(llcache_object *object,
+ const uint8_t *data, size_t len);
+
/******************************************************************************
* Public API *
@@ -676,6 +695,8 @@
*/
nserror llcache_object_destroy(llcache_object *object)
{
+ size_t i;
+
free(object->url);
free(object->source_data);
@@ -689,7 +710,11 @@
free(object->cache.etag);
- /** \todo Headers */
+ for (i = 0; i < object->num_headers; i++) {
+ free(object->headers[i].name);
+ free(object->headers[i].value);
+ }
+ free(object->headers);
free(object);
@@ -936,38 +961,45 @@
void llcache_fetch_callback(fetch_msg msg, void *p, const void *data,
unsigned long size)
{
- nserror error;
+ nserror error = NSERROR_OK;
llcache_object *object = p;
switch (msg) {
/* 3xx responses */
case FETCH_REDIRECT:
/* Request resulted in a redirect */
- llcache_fetch_redirect(object, data, &object);
+ error = llcache_fetch_redirect(object, data, &object);
break;
case FETCH_NOTMODIFIED:
/* Conditional request determined that cached object is fresh */
- llcache_fetch_notmodified(object, &object);
+ error = llcache_fetch_notmodified(object, &object);
break;
/** \todo Handle the rest of the FETCH_ events */
/* Normal 2xx state machine */
- /** \todo Merge FETCH_TYPE and FETCH_HEADER */
+ /** \todo Merge FETCH_TYPE and FETCH_HEADER? */
case FETCH_TYPE:
/* Received MIME type for object */
break;
case FETCH_HEADER:
/* Received a fetch header */
object->fetch.state = LLCACHE_FETCH_HEADERS;
+
+ error = llcache_fetch_process_header(object, data, size);
break;
case FETCH_DATA:
/* Received some data */
object->fetch.state = LLCACHE_FETCH_DATA;
+
+ error = llcache_fetch_process_data(object, data, size);
break;
case FETCH_FINISHED:
/* Finished fetching */
object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ object->fetch.fetch = NULL;
+
+ llcache_object_cache_update(object);
break;
/* Out-of-band progress information */
@@ -987,10 +1019,20 @@
break;
}
+ /* Deal with any errors reported by event handlers */
+ if (error != NSERROR_OK) {
+ /** \todo Error handling */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ return;
+ }
+
/* Keep users in sync with reality */
error = llcache_object_notify_users(object);
if (error != NSERROR_OK) {
/** \todo Error handling */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
}
}
@@ -1117,3 +1159,240 @@
return NSERROR_OK;
}
+
+/**
+ * Split a fetch header into name and value
+ *
+ * \param data Header string
+ * \param len Byte length of header
+ * \param name Pointer to location to receive header name
+ * \param value Pointer to location to receive header value
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_split_header(const char *data, size_t len, char **name,
+ char **value)
+{
+ char *n, *v;
+ const char *colon;
+
+ /* Find colon */
+ colon = strchr(data, ':');
+ if (colon == NULL) {
+ /* Failed, assume a key with no value */
+ n = strdup(data);
+ if (n == NULL)
+ return NSERROR_NOMEM;
+
+ v = strdup("");
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
+ }
+ } else {
+ /* Split header into name & value */
+
+ /* Strip leading whitespace from name */
+ while (data[0] == ' ' || data[0] == '\t' ||
+ data[0] == '\r' || data[0] == '\n') {
+ data++;
+ }
+
+ /* Strip trailing whitespace from name */
+ do {
+ colon--;
+ } while (*colon == ' ' || *colon == '\t' ||
+ *colon == '\r' || *colon == '\n');
+
+ n = strndup(data, colon - data - 1);
+ if (n == NULL)
+ return NSERROR_NOMEM;
+
+ /* Find colon again */
+ while (*colon != ':') {
+ colon++;
+ }
+
+ /* Skip over colon and any subsequent whitespace */
+ do {
+ colon++;
+ } while (*colon == ' ' || *colon == '\t' ||
+ *colon == '\r' || *colon == '\n');
+
+ /* Strip trailing whitespace from value */
+ while (len > 0 && (data[len - 1] == ' ' ||
+ data[len - 1] == '\t' ||
+ data[len - 1] == '\r' ||
+ data[len - 1] == '\n')) {
+ len--;
+ }
+
+ v = strndup(colon, len - (colon - data));
+ if (v == NULL) {
+ free(n);
+ return NSERROR_NOMEM;
+ }
+ }
+
+ *name = n;
+ *value = v;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Parse a fetch header
+ *
+ * \param object Object to parse header for
+ * \param data Header string
+ * \param len Byte length of header
+ * \param name Pointer to location to receive header name
+ * \param value Pointer to location to receive header value
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_parse_header(llcache_object *object, const char *data,
+ size_t len, char **name, char **value)
+{
+ nserror error;
+
+ /* Set fetch response time if not already set */
+ if (object->cache.res_time == 0)
+ object->cache.res_time = time(NULL);
+
+ /* Decompose header into name-value pair */
+ error = llcache_fetch_split_header(data, len, name, value);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Parse cache headers to populate cache control data */
+#define SKIP_ST(p) while (*p != '\0' && (*p == ' ' || *p == '\t')) p++
+
+ if (5 < len && strcasecmp(*name, "Date") == 0) {
+ /* extract Date header */
+ object->cache.date = curl_getdate(*value, NULL);
+ } else if (4 < len && strcasecmp(*name, "Age") == 0) {
+ /* extract Age header */
+ if ('0' <= **value && **value <= '9')
+ object->cache.age = atoi(*value);
+ } else if (8 < len && strcasecmp(*name, "Expires") == 0) {
+ /* extract Expires header */
+ object->cache.expires = curl_getdate(*value, NULL);
+ } else if (14 < len && strcasecmp(*name, "Cache-Control") == 0) {
+ /* extract and parse Cache-Control header */
+ const char *start = *value;
+ const char *comma = *value;
+
+ while (*comma != '\0') {
+ while (*comma != '\0' && *comma != ',')
+ comma++;
+
+ if (8 < comma - start && (strncasecmp(start,
+ "no-cache", 8) == 0 ||
+ strncasecmp(start, "no-store", 8) == 0))
+ /* When we get a disk cache we should
+ * distinguish between these two */
+ object->cache.no_cache = true;
+ else if (7 < comma - start &&
+ strncasecmp(start, "max-age", 7) == 0) {
+ /* Find '=' */
+ while (start < comma && *start != '=')
+ start++;
+
+ /* Skip over it */
+ start++;
+
+ /* Skip whitespace */
+ SKIP_ST(start);
+
+ if (start < comma)
+ object->cache.max_age = atoi(start);
+ }
+
+ /* Skip past comma */
+ comma++;
+ /* Skip whitespace */
+ SKIP_ST(comma);
+ /* Set start for next token */
+ start = comma;
+ }
+ } else if (5 < len && strcasecmp(*name, "ETag") == 0) {
+ /* extract ETag header */
+ free(object->cache.etag);
+ object->cache.etag = strdup(*value);
+ if (object->cache.etag == NULL)
+ return NSERROR_NOMEM;
+ } else if (14 < len && strcasecmp(*name, "Last-Modified") == 0) {
+ /* extract Last-Modified header */
+ object->cache.last_modified = curl_getdate(*value, NULL);
+ }
+
+#undef SKIP_ST
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process a fetch header
+ *
+ * \param object Object being fetched
+ * \param data Header string
+ * \param len Byte length of header
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_process_header(llcache_object *object, const char *data,
+ size_t len)
+{
+ nserror error;
+ char *name, *value;
+ llcache_header *temp;
+
+ error = llcache_fetch_parse_header(object, data, len, &name, &value);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Append header data to the object's headers array */
+ temp = realloc(object->headers, (object->num_headers + 1) *
+ sizeof(llcache_header));
+ if (temp == NULL) {
+ free(name);
+ free(value);
+ return NSERROR_NOMEM;
+ }
+
+ object->headers = temp;
+
+ object->headers[object->num_headers].name = name;
+ object->headers[object->num_headers].value = value;
+
+ object->num_headers++;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Process a chunk of fetched data
+ *
+ * \param object Object being fetched
+ * \param data Data to process
+ * \param len Byte length of data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_fetch_process_data(llcache_object *object, const uint8_t *data,
+ size_t len)
+{
+ /* Resize source buffer if it's too small */
+ if (object->source_len + len >= object->source_alloc) {
+ const size_t new_len = object->source_len + len + 64 * 1024;
+ uint8_t *temp = realloc(object->source_data, new_len);
+ if (temp == NULL)
+ return NSERROR_NOMEM;
+
+ object->source_data = temp;
+ object->source_alloc = new_len;
+ }
+
+ /* Append this data chunk to source buffer */
+ memcpy(object->source_data + object->source_len, data, len);
+ object->source_len += len;
+
+ return NSERROR_OK;
+}
13 years, 8 months
r9619 jmb - in /branches/jmb/new-cache: Makefile.sources content/llcache.c
by netsurf@semichrome.net
Author: jmb
Date: Sat Oct 10 07:03:47 2009
New Revision: 9619
URL: http://source.netsurf-browser.org?rev=9619&view=rev
Log:
Most of a low-level cache implementation.
It compiles. Don't expect it to work yet -- there's a bunch of the fetch state machine it doesn't implement right now.
Added:
branches/jmb/new-cache/content/llcache.c
Modified:
branches/jmb/new-cache/Makefile.sources
Modified: branches/jmb/new-cache/Makefile.sources
URL: http://source.netsurf-browser.org/branches/jmb/new-cache/Makefile.sources...
==============================================================================
--- branches/jmb/new-cache/Makefile.sources (original)
+++ branches/jmb/new-cache/Makefile.sources Sat Oct 10 07:03:47 2009
@@ -5,7 +5,7 @@
# for each build.
#
-S_CONTENT := content.c fetch.c fetchcache.c urldb.c \
+S_CONTENT := content.c fetch.c fetchcache.c llcache.c urldb.c \
fetchers/fetch_curl.c fetchers/fetch_data.c
S_CSS := css.c dump.c internal.c select.c utils.c
S_RENDER := box.c box_construct.c box_normalise.c directory.c \
Added: branches/jmb/new-cache/content/llcache.c
URL: http://source.netsurf-browser.org/branches/jmb/new-cache/content/llcache....
==============================================================================
--- branches/jmb/new-cache/content/llcache.c (added)
+++ branches/jmb/new-cache/content/llcache.c Sat Oct 10 07:03:47 2009
@@ -1,0 +1,1119 @@
+/*
+ * Copyright 2009 John-Mark Bell <jmb(a)netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf 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; version 2 of the License.
+ *
+ * NetSurf 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Low-level resource cache (implementation)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "content/fetch.h"
+#include "content/llcache.h"
+#include "utils/url.h"
+#include "utils/utils.h"
+
+/** State of a low-level cache object fetch */
+typedef enum {
+ LLCACHE_FETCH_INIT, /**< Initial state, before fetch */
+ LLCACHE_FETCH_HEADERS, /**< Fetching headers */
+ LLCACHE_FETCH_DATA, /**< Fetching object data */
+ LLCACHE_FETCH_COMPLETE /**< Fetch completed */
+} llcache_fetch_state;
+
+/** Handle to low-level cache object */
+struct llcache_handle {
+ llcache_object *object; /**< Pointer to associated object */
+
+ llcache_handle_callback cb; /**< Client callback */
+ void *pw; /**< Client data */
+
+ llcache_fetch_state state; /**< Last known state of object fetch */
+ size_t bytes; /**< Last reported byte count */
+};
+
+/** Low-level cache object user record */
+typedef struct llcache_object_user {
+ /* Must be first in struct */
+ llcache_handle handle; /**< Handle data for client */
+
+ struct llcache_object_user *prev; /**< Previous in list */
+ struct llcache_object_user *next; /**< Next in list */
+} llcache_object_user;
+
+/** Low-level cache object fetch context */
+typedef struct {
+ uint32_t flags; /**< Fetch flags */
+ char *referer; /**< Referring URL, or NULL if none */
+ llcache_post_data *post; /**< POST data, or NULL for GET */
+
+ struct fetch *fetch; /**< Fetch handle for this object */
+
+ llcache_fetch_state state; /**< Current state of object fetch */
+} llcache_fetch_ctx;
+
+/** Cache control data */
+typedef struct {
+ time_t req_time; /**< Time of request */
+ time_t res_time; /**< Time of response */
+ time_t date; /**< Date: response header */
+ time_t expires; /**< Expires: response header */
+#define INVALID_AGE -1
+ int age; /**< Age: response header */
+ int max_age; /**< Max-Age Cache-control parameter */
+ bool no_cache; /**< No-Cache Cache-control parameter */
+ char *etag; /**< Etag: response header */
+ time_t last_modified; /**< Last-Modified: response header */
+} llcache_cache_control;
+
+/** Low-level cache object */
+/** \todo Consider whether a list is a sane container */
+struct llcache_object {
+ llcache_object *prev; /**< Previous in list */
+ llcache_object *next; /**< Next in list */
+
+ char *url; /**< Post-redirect URL for object */
+
+ /** \todo We need a generic dynamic buffer object */
+ uint8_t *source_data; /**< Source data for object */
+ size_t source_len; /**< Byte length of source data */
+ size_t source_alloc; /**< Allocated size of source buffer */
+
+ llcache_object_user *users; /**< List of users */
+
+ llcache_fetch_ctx fetch; /**< Fetch context for object */
+
+ llcache_cache_control cache; /**< Cache control data for object */
+ llcache_object *candidate; /**< Object to use, if fetch determines
+ * that it is still fresh */
+ uint32_t candidate_count; /**< Count of objects this is a
+ * candidate for */
+
+ /** \todo Need fetch headers */
+};
+
+/** Handler for fetch-related queries */
+static llcache_query_callback query_cb;
+/** Data for fetch-related query handler */
+static void *query_cb_pw;
+
+/** Head of the low-level cached object list */
+static llcache_object *llcache_cached_objects;
+/** Head of the low-level uncached object list */
+static llcache_object *llcache_uncached_objects;
+
+static nserror llcache_object_user_new(llcache_handle_callback cb, void *pw,
+ llcache_object_user **user);
+static nserror llcache_object_user_destroy(llcache_object_user *user);
+
+static nserror llcache_object_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_object **result);
+static nserror llcache_object_retrieve_from_cache(const char *url,
+ uint32_t flags, const char *referer,
+ const llcache_post_data *post, llcache_object **result);
+static bool llcache_object_is_fresh(const llcache_object *object);
+static nserror llcache_object_cache_update(llcache_object *object);
+static nserror llcache_object_clone_cache_data(const llcache_object *source,
+ llcache_object *destination, bool deep);
+static nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
+ const char *referer, const llcache_post_data *post);
+
+static nserror llcache_object_new(const char *url, llcache_object **result);
+static nserror llcache_object_destroy(llcache_object *object);
+static nserror llcache_object_add_user(llcache_object *object,
+ llcache_object_user *user);
+static nserror llcache_object_remove_user(llcache_object *object,
+ llcache_object_user *user);
+
+static nserror llcache_object_add_to_list(llcache_object *object,
+ llcache_object **list);
+static nserror llcache_object_remove_from_list(llcache_object *object,
+ llcache_object **list);
+
+static nserror llcache_object_notify_users(llcache_object *object);
+
+static nserror llcache_clean(void);
+
+static void llcache_fetch_callback(fetch_msg msg, void *p, const void *data,
+ unsigned long size);
+static nserror llcache_fetch_redirect(llcache_object *object,
+ const char *target, llcache_object **replacement);
+static nserror llcache_fetch_notmodified(llcache_object *object,
+ llcache_object **replacement);
+
+/******************************************************************************
+ * Public API *
+ ******************************************************************************/
+
+/**
+ * Initialise the low-level cache
+ *
+ * \param cb Query handler
+ * \param pw Pointer to query handler data
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_initialise(llcache_query_callback cb, void *pw)
+{
+ query_cb = cb;
+ query_cb_pw = pw;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Poll the low-level cache
+ *
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_poll(void)
+{
+ llcache_object *object;
+
+ /* Catch new users up with state of objects */
+ for (object = llcache_cached_objects; object != NULL;
+ object = object->next) {
+ llcache_object_notify_users(object);
+ }
+
+ for (object = llcache_uncached_objects; object != NULL;
+ object = object->next) {
+ llcache_object_notify_users(object);
+ }
+
+ /* Attempt to clean the cache */
+ llcache_clean();
+
+ return NSERROR_OK;
+}
+
+/**
+ * Retrieve a handle for a low-level cache object
+ *
+ * \param url URL of the object to fetch
+ * \param flags Object retrieval flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param cb Client callback for events
+ * \param pw Pointer to client-specific data
+ * \param result Pointer to location to recieve cache handle
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_handle_callback cb, void *pw,
+ llcache_handle **result)
+{
+ nserror error;
+ llcache_object_user *user;
+ llcache_object *object;
+
+ /* Create a new object user */
+ error = llcache_object_user_new(cb, pw, &user);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Retrieve a suitable object from the cache,
+ * creating a new one if needed. */
+ error = llcache_object_retrieve(url, flags, referer, post, &object);
+ if (error != NSERROR_OK) {
+ llcache_object_user_destroy(user);
+ return error;
+ }
+
+ /* Add user to object */
+ llcache_object_add_user(object, user);
+
+ *result = &user->handle;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Release a low-level cache handle
+ *
+ * \param handle Handle to release
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_release(llcache_handle *handle)
+{
+ nserror error = NSERROR_OK;
+ llcache_object *object = handle->object;
+ llcache_object_user *user = (llcache_object_user *) handle;
+
+ /* Remove the user from the object and destroy it */
+ error = llcache_object_remove_user(object, user);
+ if (error == NSERROR_OK)
+ error = llcache_object_user_destroy(user);
+
+ return error;
+}
+
+/**
+ * Retrieve the low-level cache object associated with a handle
+ *
+ * \param handle Handle to dereference
+ * \return Pointer to low-level cache object
+ */
+const llcache_object *llcache_object_from_handle(const llcache_handle *handle)
+{
+ return handle->object;
+}
+
+/**
+ * Retrieve the post-redirect URL of a low-level cache object
+ *
+ * \param object Object to retrieve URL from
+ * \return Post-redirect URL of cache object
+ */
+const char *llcache_object_get_url(const llcache_object *object)
+{
+ return object->url;
+}
+
+/**
+ * Retrieve a header value associated with a low-level cache object
+ *
+ * \param object Object to retrieve header from
+ * \param key Header name
+ * \return Header value, or NULL if header does not exist
+ *
+ * \todo Make the key an enumeration, to avoid needless string comparisons
+ * \todo Forcing the client to parse the header value seems wrong.
+ * Better would be to return the actual value part and an array of
+ * key-value pairs for any additional parameters.
+ */
+const char *llcache_object_get_header(const llcache_object *object,
+ const char *key)
+{
+ /** \todo implement */
+ return NULL;
+}
+
+/******************************************************************************
+ * Low-level cache internals *
+ ******************************************************************************/
+
+/**
+ * Create a new object user
+ *
+ * \param cb Callback routine
+ * \param pw Private data for callback
+ * \param user Pointer to location to receive result
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_user_new(llcache_handle_callback cb, void *pw,
+ llcache_object_user **user)
+{
+ llcache_object_user *u = calloc(1, sizeof(llcache_object_user));
+ if (u == NULL)
+ return NSERROR_NOMEM;
+
+ u->handle.cb = cb;
+ u->handle.pw = pw;
+
+ *user = u;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Destroy an object user
+ *
+ * \param user User to destroy
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre User is not attached to an object
+ */
+nserror llcache_object_user_destroy(llcache_object_user *user)
+{
+ free(user);
+
+ return NSERROR_OK;
+}
+
+/**
+ * Retrieve an object from the cache, fetching it if necessary.
+ *
+ * \param url URL of object to retrieve
+ * \param flags Fetch flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param result Pointer to location to recieve retrieved object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_object **result)
+{
+ nserror error;
+ llcache_object *obj;
+
+ /**
+ * Caching Rules:
+ *
+ * 1) Forced fetches are never cached
+ * 2) GET requests with query segments are never cached
+ * 3) POST requests are never cached
+ *
+ * \todo Find out if restriction (2) can be removed
+ * \todo Actually check for GET with a query part
+ */
+ if (flags & LLCACHE_RETRIEVE_FORCE_FETCH || post != NULL) {
+ /* Create new object */
+ error = llcache_object_new(url, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Attempt to kick-off fetch */
+ error = llcache_object_fetch(obj, flags, referer, post);
+ if (error != NSERROR_OK) {
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Add new object to uncached list */
+ llcache_object_add_to_list(obj, &llcache_uncached_objects);
+ } else {
+ error = llcache_object_retrieve_from_cache(url, flags, referer,
+ post, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Returned object is already in the cached list */
+ }
+
+ *result = obj;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Retrieve a potentially cached object
+ *
+ * \param url URL of object to retrieve
+ * \param flags Fetch flags
+ * \param referer Referring URL, or NULL if none
+ * \param post POST data, or NULL for a GET request
+ * \param result Pointer to location to recieve retrieved object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_retrieve_from_cache(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_object **result)
+{
+ nserror error;
+ llcache_object *obj, *newest = NULL;
+
+ /* Search for the most recently fetched matching object */
+ for (obj = llcache_cached_objects; obj != NULL; obj = obj->next) {
+ if (strcasecmp(obj->url, url) == 0 && (newest == NULL ||
+ obj->cache.req_time > newest->cache.req_time))
+ newest = obj;
+ }
+
+ if (newest != NULL && llcache_object_is_fresh(newest)) {
+ /* Found a suitable object, and it's still fresh, so use it */
+ obj = newest;
+
+ /* The client needs to catch up with the object's state.
+ * This will occur the next time that llcache_poll is called.
+ */
+ } else if (newest != NULL) {
+ /* Found a candidate object but it needs freshness validation */
+ /* Create a new object */
+ error = llcache_object_new(url, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Clone candidate's cache data */
+ error = llcache_object_clone_cache_data(newest, obj, true);
+ if (error != NSERROR_OK) {
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Record candidate, so we can fall back if it is still fresh */
+ newest->candidate_count++;
+ obj->candidate = newest;
+
+ /* Attempt to kick-off fetch */
+ error = llcache_object_fetch(obj, flags, referer, post);
+ if (error != NSERROR_OK) {
+ newest->candidate_count--;
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Add new object to cache */
+ llcache_object_add_to_list(obj, &llcache_cached_objects);
+ } else {
+ /* No object found; create a new one */
+ /* Create new object */
+ error = llcache_object_new(url, &obj);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Attempt to kick-off fetch */
+ error = llcache_object_fetch(obj, flags, referer, post);
+ if (error != NSERROR_OK) {
+ llcache_object_destroy(obj);
+ return error;
+ }
+
+ /* Add new object to cache */
+ llcache_object_add_to_list(obj, &llcache_cached_objects);
+ }
+
+ *result = obj;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Determine if an object is still fresh
+ *
+ * \param object Object to consider
+ * \return True if object is still fresh, false otherwise
+ */
+bool llcache_object_is_fresh(const llcache_object *object)
+{
+ const llcache_cache_control *cd = &object->cache;
+ int current_age, freshness_lifetime;
+ time_t now = time(NULL);
+
+ /* Calculate staleness of cached object as per RFC 2616 13.2.3/13.2.4 */
+ current_age = max(0, (cd->res_time - cd->date));
+ current_age = max(current_age, (cd->age == INVALID_AGE) ? 0 : cd->age);
+ current_age += cd->res_time - cd->req_time + now - cd->res_time;
+
+ /* Determine freshness lifetime of this object */
+ if (cd->max_age != INVALID_AGE)
+ freshness_lifetime = cd->max_age;
+ else if (cd->expires != 0)
+ freshness_lifetime = cd->expires - cd->date;
+ else if (cd->last_modified != 0)
+ freshness_lifetime = (now - cd->last_modified) / 10;
+ else
+ freshness_lifetime = 0;
+
+ /* The object is fresh if its current age is within the freshness
+ * lifetime or if we're still fetching the object */
+ return (freshness_lifetime > current_age ||
+ object->fetch.state != LLCACHE_FETCH_COMPLETE);
+}
+
+/**
+ * Update an object's cache state
+ *
+ * \param object Object to update cache for
+ * \return NSERROR_OK.
+ */
+nserror llcache_object_cache_update(llcache_object *object)
+{
+ if (object->cache.date == 0)
+ object->cache.date = time(NULL);
+
+ /** \todo Any magic we need to do for no_cache? */
+
+ return NSERROR_OK;
+}
+
+/**
+ * Clone an object's cache data
+ *
+ * \param source Source object containing cache data to clone
+ * \param destination Destination object to clone cache data into
+ * \param deep Whether to deep-copy the data or not
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_clone_cache_data(const llcache_object *source,
+ llcache_object *destination, bool deep)
+{
+ /* ETag must be first, as it can fail when deep cloning */
+ if (source->cache.etag != NULL) {
+ char *etag = source->cache.etag;
+
+ if (deep) {
+ /* Copy the etag */
+ etag = strdup(source->cache.etag);
+ if (etag == NULL)
+ return NSERROR_NOMEM;
+ }
+
+ if (destination->cache.etag != NULL)
+ free(destination->cache.etag);
+
+ destination->cache.etag = etag;
+ }
+
+ destination->cache.req_time = source->cache.req_time;
+ destination->cache.res_time = source->cache.res_time;
+
+ if (source->cache.date != 0)
+ destination->cache.date = source->cache.date;
+
+ if (source->cache.expires != 0)
+ destination->cache.expires = source->cache.expires;
+
+ if (source->cache.age != INVALID_AGE)
+ destination->cache.age = source->cache.age;
+
+ if (source->cache.max_age != INVALID_AGE)
+ destination->cache.max_age = source->cache.max_age;
+
+ if (source->cache.no_cache)
+ destination->cache.no_cache = source->cache.no_cache;
+
+ if (source->cache.last_modified != 0)
+ destination->cache.last_modified = source->cache.last_modified;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Kick-off a fetch for an object
+ *
+ * \param object Object to fetch
+ * \param flags Fetch flags
+ * \param referer Referring URL, or NULL for none
+ * \param post POST data, or NULL for GET
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre object::url must contain the URL to fetch
+ * \pre If there is a freshness validation candidate,
+ * object::candidate and object::cache must be filled in
+ * \pre There must not be a fetch in progress for \a object
+ */
+nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
+ const char *referer, const llcache_post_data *post)
+{
+ char *referer_clone;
+ llcache_post_data *post_clone;
+ const char *urlenc = NULL;
+ struct form_successful_control *multipart = NULL;
+
+ referer_clone = strdup(referer);
+ if (referer_clone == NULL)
+ return NSERROR_NOMEM;
+
+ /** \todo clone post */
+ post_clone = (llcache_post_data *) post;
+
+ object->fetch.flags = flags;
+ object->fetch.referer = referer_clone;
+ object->fetch.post = post_clone;
+
+ if (object->fetch.post != NULL) {
+ if (object->fetch.post->type == LLCACHE_POST_URL_ENCODED)
+ urlenc = object->fetch.post->data.urlenc;
+ else
+ multipart = object->fetch.post->data.multipart;
+ }
+
+ object->fetch.fetch = fetch_start(object->url, object->fetch.referer,
+ llcache_fetch_callback, object,
+ flags & LLCACHE_RETRIEVE_NO_ERROR_PAGES,
+ urlenc, multipart,
+ flags & LLCACHE_RETRIEVE_VERIFIABLE,
+ NULL, /** \todo Remove parent from this API */
+ NULL /** \todo Generate cache-control headers */);
+ if (object->fetch.fetch == NULL)
+ return NSERROR_NOMEM;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Create a new low-level cache object
+ *
+ * \param url URL of object to create
+ * \param result Pointer to location to receive result
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_new(const char *url, llcache_object **result)
+{
+ llcache_object *obj = calloc(1, sizeof(llcache_object));
+ if (obj == NULL)
+ return NSERROR_NOMEM;
+
+ obj->url = strdup(url);
+ if (obj->url == NULL) {
+ free(obj);
+ return NSERROR_NOMEM;
+ }
+
+ *result = obj;
+
+ return NSERROR_NOMEM;
+}
+
+/**
+ * Destroy a low-level cache object
+ *
+ * \param object Object to destroy
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * \pre Object is detached from cache list
+ * \pre Object has no users
+ * \pre Object is not a candidate (i.e. object::candidate_count == 0)
+ */
+nserror llcache_object_destroy(llcache_object *object)
+{
+ free(object->url);
+ free(object->source_data);
+
+ if (object->fetch.fetch != NULL) {
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+ }
+
+ free(object->fetch.referer);
+ /** \todo Destroy POST data */
+
+ free(object->cache.etag);
+
+ /** \todo Headers */
+
+ free(object);
+
+ return NSERROR_OK;
+}
+
+/**
+ * Add a user to a low-level cache object
+ *
+ * \param object Object to add user to
+ * \param user User to add
+ * \return NSERROR_OK.
+ */
+nserror llcache_object_add_user(llcache_object *object,
+ llcache_object_user *user)
+{
+ user->handle.object = object;
+
+ user->prev = NULL;
+ user->next = object->users;
+
+ if (object->users != NULL)
+ object->users->prev = user;
+ object->users = user;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Remove a user from a low-level cache object
+ *
+ * \param object Object to remove user from
+ * \param user User to remove
+ * \return NSERROR_OK.
+ */
+nserror llcache_object_remove_user(llcache_object *object,
+ llcache_object_user *user)
+{
+ if (user == object->users)
+ object->users = user->next;
+ else
+ user->prev->next = user->next;
+
+ if (user->next != NULL)
+ user->next->prev = user->prev;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Add a low-level cache object to a cache list
+ *
+ * \param object Object to add
+ * \param list List to add to
+ * \return NSERROR_OK
+ */
+nserror llcache_object_add_to_list(llcache_object *object,
+ llcache_object **list)
+{
+ object->prev = NULL;
+ object->next = *list;
+
+ if (*list != NULL)
+ (*list)->prev = object;
+ *list = object;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Remove a low-level cache object from a cache list
+ *
+ * \param object Object to remove
+ * \param list List to remove from
+ * \return NSERROR_OK
+ */
+nserror llcache_object_remove_from_list(llcache_object *object,
+ llcache_object **list)
+{
+ if (object == *list)
+ *list = object->next;
+ else
+ object->prev->next = object->next;
+
+ if (object->next != NULL)
+ object->next->prev = object->next;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Notify users of an object's current state
+ *
+ * \param object Object to notify users about
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_object_notify_users(llcache_object *object)
+{
+ nserror error;
+ llcache_object_user *user;
+ llcache_event event;
+
+ /**
+ * State transitions and event emission for users.
+ * Rows: user state. Cols: object state.
+ *
+ * User\Obj INIT HEADERS DATA COMPLETE
+ * INIT - T T* T*
+ * HEADERS - - T T*
+ * DATA - - M T
+ * COMPLETE - - - -
+ *
+ * T => transition user to object state
+ * M => no transition required, but may need to emit event
+ *
+ * The transitions marked with an asterisk can be removed by moving
+ * the user context into the subsequent state and then reevaluating.
+ *
+ * Events are issued as follows:
+ *
+ * HAD_HEADERS: on transition from HEADERS -> DATA state
+ * HAD_DATA : in DATA state, whenever there's new source data
+ * DONE : on transition from DATA -> COMPLETE state
+ */
+
+ /** \todo How are we going to handle errors here? */
+
+ for (user = object->users; user != NULL; user = user->next) {
+ /* Emit necessary events to bring the user up-to-date */
+ llcache_handle *handle = &user->handle;
+ llcache_fetch_state hstate = handle->state;
+ llcache_fetch_state objstate = object->fetch.state;
+
+ /* User: INIT, Obj: HEADERS, DATA, COMPLETE => User->HEADERS */
+ if (hstate == LLCACHE_FETCH_INIT &&
+ objstate > LLCACHE_FETCH_INIT) {
+ hstate = LLCACHE_FETCH_HEADERS;
+ }
+
+ /* User: HEADERS, Obj: DATA, COMPLETE => User->DATA */
+ if (hstate == LLCACHE_FETCH_HEADERS &&
+ objstate > LLCACHE_FETCH_HEADERS) {
+ /* Emit HAD_HEADERS event */
+ event.type = LLCACHE_EVENT_HAD_HEADERS;
+
+ error = handle->cb(handle, &event, handle->pw);
+ if (error != NSERROR_OK)
+ return error;
+
+ hstate = LLCACHE_FETCH_DATA;
+ }
+
+ /* User: DATA, Obj: DATA, COMPLETE, more source available */
+ if (hstate == LLCACHE_FETCH_DATA &&
+ objstate >= LLCACHE_FETCH_DATA &&
+ object->source_len > handle->bytes) {
+ /* Emit HAD_DATA event */
+ event.type = LLCACHE_EVENT_HAD_DATA;
+ event.data.buf = object->source_data + handle->bytes;
+ event.data.len = object->source_len - handle->bytes;
+
+ error = handle->cb(handle, &event, handle->pw);
+ if (error != NSERROR_OK)
+ return error;
+
+ /* Update record of last byte emitted */
+ handle->bytes = object->source_len;
+ }
+
+ /* User: DATA, Obj: COMPLETE => User->COMPLETE */
+ if (hstate == LLCACHE_FETCH_DATA &&
+ objstate > LLCACHE_FETCH_DATA) {
+ /* Emit DONE event */
+ event.type = LLCACHE_EVENT_DONE;
+
+ error = handle->cb(handle, &event, handle->pw);
+ if (error != NSERROR_OK)
+ return error;
+
+ hstate = LLCACHE_FETCH_COMPLETE;
+ }
+
+ /* Sync handle's state with reality */
+ handle->state = hstate;
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * Attempt to clean the cache
+ *
+ * \return NSERROR_OK.
+ */
+nserror llcache_clean(void)
+{
+ llcache_object *object, *next;
+
+ /* Candidates for cleaning are (in order of priority):
+ *
+ * 1) Uncacheable objects with no users
+ * 2) Stale cacheable objects with no users or pending fetches
+ * 3) Fresh cacheable objects with no users or pending fetches
+ */
+
+ /* 1) Uncacheable objects with no users */
+ for (object = llcache_uncached_objects; object != NULL; object = next) {
+ next = object->next;
+
+ /* The candidate count of uncacheable objects is always 0 */
+ if (object->users == NULL && object->candidate_count == 0) {
+ llcache_object_remove_from_list(object,
+ &llcache_uncached_objects);
+ llcache_object_destroy(object);
+ }
+ }
+
+ /* 2) Stale cacheable objects with no users or pending fetches */
+ for (object = llcache_cached_objects; object != NULL; object = next) {
+ next = object->next;
+
+ if (object->users == NULL && object->candidate_count == 0 &&
+ llcache_object_is_fresh(object) == false) {
+ llcache_object_remove_from_list(object,
+ &llcache_cached_objects);
+ llcache_object_destroy(object);
+ }
+ }
+
+ /* 3) Fresh cacheable objects with no users or pending fetches */
+ /** \todo This one only happens if the cache is too large */
+
+ return NSERROR_OK;
+}
+
+/**
+ * Handler for fetch events
+ *
+ * \param msg Type of fetch event
+ * \param p Our private data
+ * \param data Event data
+ * \param size Length of data in bytes
+ */
+void llcache_fetch_callback(fetch_msg msg, void *p, const void *data,
+ unsigned long size)
+{
+ nserror error;
+ llcache_object *object = p;
+
+ switch (msg) {
+ /* 3xx responses */
+ case FETCH_REDIRECT:
+ /* Request resulted in a redirect */
+ llcache_fetch_redirect(object, data, &object);
+ break;
+ case FETCH_NOTMODIFIED:
+ /* Conditional request determined that cached object is fresh */
+ llcache_fetch_notmodified(object, &object);
+ break;
+
+ /** \todo Handle the rest of the FETCH_ events */
+
+ /* Normal 2xx state machine */
+ /** \todo Merge FETCH_TYPE and FETCH_HEADER */
+ case FETCH_TYPE:
+ /* Received MIME type for object */
+ break;
+ case FETCH_HEADER:
+ /* Received a fetch header */
+ object->fetch.state = LLCACHE_FETCH_HEADERS;
+ break;
+ case FETCH_DATA:
+ /* Received some data */
+ object->fetch.state = LLCACHE_FETCH_DATA;
+ break;
+ case FETCH_FINISHED:
+ /* Finished fetching */
+ object->fetch.state = LLCACHE_FETCH_COMPLETE;
+ break;
+
+ /* Out-of-band progress information */
+ case FETCH_PROGRESS:
+ /* Progress update */
+ break;
+
+ /* Events requiring action */
+ case FETCH_ERROR:
+ /* An error occurred while fetching */
+ break;
+ case FETCH_AUTH:
+ /* Need Authentication */
+ break;
+ case FETCH_CERT_ERR:
+ /* Something went wrong when validating TLS certificates */
+ break;
+ }
+
+ /* Keep users in sync with reality */
+ error = llcache_object_notify_users(object);
+ if (error != NSERROR_OK) {
+ /** \todo Error handling */
+ }
+}
+
+/**
+ * Handle FETCH_REDIRECT event
+ *
+ * \param object Object being redirected
+ * \param target Target of redirect (may be relative)
+ * \param replacement Pointer to location to receive replacement object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_fetch_redirect(llcache_object *object, const char *target,
+ llcache_object **replacement)
+{
+ nserror error;
+ llcache_object *dest;
+ llcache_object_user *user, *next;
+ const llcache_post_data *post = object->fetch.post;
+ char *url, *absurl;
+ url_func_result result;
+ /* Extract HTTP response code from the fetch object */
+ long http_code = fetch_http_code(object->fetch.fetch);
+
+ /* Abort fetch for this object */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ /** \todo Limit redirect depth, or detect cycles */
+
+ /* Make target absolute */
+ result = url_join(target, object->url, &absurl);
+ if (result != URL_FUNC_OK) {
+ /** \todo handle error */
+ }
+
+ /* Ensure target is normalised */
+ result = url_normalize(absurl, &url);
+
+ /* No longer require absolute url */
+ free(absurl);
+
+ if (result != URL_FUNC_OK) {
+ /** \todo handle error */
+ }
+
+ /** \todo Ensure that redirects to file:/// don't happen? */
+
+ /** \todo What happens if we've no way of handling this URL? */
+
+ /** \todo All the magical processing for the various redirect types */
+ if (http_code == 301 || http_code == 302 || http_code == 303) {
+ /* 301, 302, 303 redirects are all unconditional GET requests */
+ post = NULL;
+ } else {
+ /** \todo 300, 305, 307 */
+ }
+
+ /* Attempt to fetch target URL */
+ error = llcache_object_retrieve(url, object->fetch.flags,
+ object->fetch.referer, object->fetch.post,
+ &dest);
+
+ /* No longer require url */
+ free(url);
+
+ if (error != NSERROR_OK) {
+ /** \todo handle error */
+ }
+
+ /* Move user(s) to replacement object */
+ for (user = object->users; user != NULL; user = next) {
+ next = user->next;
+
+ llcache_object_remove_user(object, user);
+ llcache_object_add_user(dest, user);
+ }
+
+ /* Dest is now our object */
+ *replacement = dest;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Handle FETCH_NOTMODIFIED event
+ *
+ * \param object Object to process
+ * \param replacement Pointer to location to receive replacement object
+ * \return NSERROR_OK.
+ */
+nserror llcache_fetch_notmodified(llcache_object *object,
+ llcache_object **replacement)
+{
+ llcache_object_user *user, *next;
+
+ /* Move user(s) to candidate content */
+ for (user = object->users; user != NULL; user = next) {
+ next = user->next;
+
+ llcache_object_remove_user(object, user);
+ llcache_object_add_user(object->candidate, user);
+ }
+
+ /* Candidate is no longer a candidate for us */
+ object->candidate->candidate_count--;
+
+ /* Clone our cache control data into the candidate */
+ llcache_object_clone_cache_data(object, object->candidate, false);
+ /* Bring candidate's cache data up to date */
+ llcache_object_cache_update(object->candidate);
+
+ /* Invalidate our cache-control data */
+ memset(&object->cache, 0, sizeof(llcache_cache_control));
+
+ /* Ensure fetch has stopped */
+ /** \todo Are there any other fields that need invalidating? */
+ fetch_abort(object->fetch.fetch);
+ object->fetch.fetch = NULL;
+
+ /* Candidate is now our object */
+ *replacement = object->candidate;
+
+ /** \todo Ensure that old object gets flushed from the cache */
+
+ return NSERROR_OK;
+}
13 years, 8 months
r9618 jmb - /branches/jmb/new-cache/content/llcache.h
by netsurf@semichrome.net
Author: jmb
Date: Sat Oct 10 07:00:49 2009
New Revision: 9618
URL: http://source.netsurf-browser.org?rev=9618&view=rev
Log:
Indirect access to cache objects through a handle.
Introduce llcache_poll()
Various constification changes.
Modified:
branches/jmb/new-cache/content/llcache.h
Modified: branches/jmb/new-cache/content/llcache.h
URL: http://source.netsurf-browser.org/branches/jmb/new-cache/content/llcache....
==============================================================================
--- branches/jmb/new-cache/content/llcache.h (original)
+++ branches/jmb/new-cache/content/llcache.h Sat Oct 10 07:00:49 2009
@@ -34,6 +34,9 @@
/** Type of low-level cache object */
typedef struct llcache_object llcache_object;
+
+/** Handle for low-level cache object */
+typedef struct llcache_handle llcache_handle;
/** POST data object for low-level cache requests */
typedef struct {
@@ -67,18 +70,19 @@
/**
* Client callback for low-level cache events
*
- * \param object Object for which event is issued
+ * \param handle Handle for which event is issued
* \param event Event data
* \param pw Pointer to client-specific data
* \return NSERROR_OK on success, appropriate error otherwise.
*/
-typedef nserror (*llcache_object_callback)(llcache_object *object,
- llcache_event *event, void *pw);
+typedef nserror (*llcache_handle_callback)(const llcache_handle *handle,
+ const llcache_event *event, void *pw);
/** Flags for low-level cache object retrieval */
#define LLCACHE_RETRIEVE_FORCE_FETCH (1 << 0) /* Force a new fetch */
#define LLCACHE_RETRIEVE_VERIFIABLE (1 << 1) /* Requested URL was verified */
#define LLCACHE_RETRIEVE_SNIFF_TYPE (1 << 2) /* Permit content-type sniffing */
+#define LLCACHE_RETRIEVE_NO_ERROR_PAGES (1 << 3) /* No error pages */
/** Low-level cache query types */
typedef enum {
@@ -131,7 +135,7 @@
* the query has been obtained, the provided response callback should be
* called. This is intended to be an entirely asynchronous process.
*/
-typedef nserror (*llcache_query_callback)(llcache_query *query, void *pw,
+typedef nserror (*llcache_query_callback)(const llcache_query *query, void *pw,
llcache_query_response cb, void *cbpw);
/**
@@ -144,7 +148,14 @@
nserror llcache_initialise(llcache_query_callback cb, void *pw);
/**
- * Retrieve a low-level cache object
+ * Poll the low-level cache
+ *
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+nserror llcache_poll(void);
+
+/**
+ * Retrieve a handle for a low-level cache object
*
* \param url URL of the object to fetch
* \param flags Object retrieval flags
@@ -152,24 +163,29 @@
* \param post POST data, or NULL for a GET request
* \param cb Client callback for events
* \param pw Pointer to client-specific data
- * \param result Pointer to location to recieve cache object
- * \return NSERROR_OK on success, appropriate error otherwise
- */
-nserror llcache_object_retrieve(const char *url, uint32_t flags,
- const char *referer, llcache_post_data *post,
- llcache_object_callback cb, void *pw,
- llcache_object **result);
-
-/**
- * Remove a callback from a low-level cache object
- *
- * \param object Object to remove callback from
- * \param cb Callback to remove
- * \param pw Pointer to client-specific data
- * \return NSERROR_OK on success, appropriate error otherwise
- */
-nserror llcache_object_remove_callback(llcache_object *object,
- llcache_object_callback cb, void *pw);
+ * \param result Pointer to location to recieve cache handle
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_retrieve(const char *url, uint32_t flags,
+ const char *referer, const llcache_post_data *post,
+ llcache_handle_callback cb, void *pw,
+ llcache_handle **result);
+
+/**
+ * Release a low-level cache handle
+ *
+ * \param handle Handle to release
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+nserror llcache_handle_release(llcache_handle *handle);
+
+/**
+ * Retrieve the low-level cache object associated with a handle
+ *
+ * \param handle Handle to dereference
+ * \return Pointer to low-level cache object
+ */
+const llcache_object *llcache_object_from_handle(const llcache_handle *handle);
/**
* Retrieve the post-redirect URL of a low-level cache object
@@ -177,7 +193,7 @@
* \param object Object to retrieve URL from
* \return Post-redirect URL of cache object
*/
-const char *llcache_object_get_url(llcache_object *object);
+const char *llcache_object_get_url(const llcache_object *object);
/**
* Retrieve a header value associated with a low-level cache object
@@ -191,6 +207,7 @@
* Better would be to return the actual value part and an array of
* key-value pairs for any additional parameters.
*/
-const char *llcache_object_get_header(llcache_object *object, const char *key);
+const char *llcache_object_get_header(const llcache_object *object,
+ const char *key);
#endif
13 years, 8 months
r9617 tlsa - in /trunk/netsurfweb/documentation: progress.en roinfo.en
by netsurf@semichrome.net
Author: tlsa
Date: Fri Oct 9 09:27:57 2009
New Revision: 9617
URL: http://source.netsurf-browser.org?rev=9617&view=rev
Log:
Update printing info and make separate section for it.
Modified:
trunk/netsurfweb/documentation/progress.en
trunk/netsurfweb/documentation/roinfo.en
Modified: trunk/netsurfweb/documentation/progress.en
URL: http://source.netsurf-browser.org/trunk/netsurfweb/documentation/progress...
==============================================================================
--- trunk/netsurfweb/documentation/progress.en (original)
+++ trunk/netsurfweb/documentation/progress.en Fri Oct 9 09:27:57 2009
@@ -261,7 +261,7 @@
<tr class="nearlydone"><td>Interactive Help (RISC OS)</td><td>Nearly done</td><td></td></tr>
<tr class="nearlydone"><td>Interactive Help (GTK)</td><td>Nearly done</td><td>Extensive tool tips for GUI but none for rendering area.</td></tr>
<tr class="complete"><td>Memory Cache</td><td>Complete</td><td></td></tr>
-<tr class="complete"><td>Printing (RISC OS)</td><td>Complete</td><td>Printing on RISC OS 5 doesn't work, due to lack of support in the Font Manager and printer drivers. Details on the <a href="roinfo#Unicode">RISC OS User Information</a> page.</td></tr>
+<tr class="complete"><td>Printing (RISC OS)</td><td>Complete</td><td>Details on the <a href="roinfo#Printing">RISC OS User Information</a> page.</td></tr>
<tr class="complete"><td>Printing (GTK)</td><td>Complete</td><td></td></tr>
<tr class="inprogress"><td>Proxy Support</td><td>In progress</td><td>Protocols other than HTTP are incorrectly proxied.</td></tr>
<tr class="nearlydone"><td>Save as Drawfile (RISC OS)</td><td>Nearly done</td><td>Embedded drawfiles aren't exported when saving page as Draw. (Draw is a RISC OS vector graphics format.)</td></tr>
Modified: trunk/netsurfweb/documentation/roinfo.en
URL: http://source.netsurf-browser.org/trunk/netsurfweb/documentation/roinfo.e...
==============================================================================
--- trunk/netsurfweb/documentation/roinfo.en (original)
+++ trunk/netsurfweb/documentation/roinfo.en Fri Oct 9 09:27:57 2009
@@ -69,6 +69,7 @@
<li><a href="#UnicodeInstallingFonts">Installing more fonts</a></li>
<li><a href="#UnicodeProblems">Problems and unimplemented features</a></li>
</ul></li>
+<li><a href="#Printing">Printing</a></li>
</ul>
<p class="updated">Last updated 10 November 2007</p>
@@ -150,11 +151,19 @@
<h3 id="UnicodeProblems">Problems and Unimplemented Features</h3>
<ul>
-<li>Printing on RISC OS 5 using the standard RISC OS 5 release of the printer drivers doesn't work due to lack of Unicode support in the RISC OS 5 printer drivers. However, a patch to fix printing to non-PostScript printers has been submitted to RISC OS Open Ltd and a binary release of the "Printers" application including this <a href="http://www.riscosopen.co.uk/content/downloads/desktop-zipfiles">fix is available on their website</a>. On the other hand, printing to PostScript printers with Unicode support is currently not possible but believed to be addressed in the near future by an <a href="http://www.drobe.co.uk/riscos/artifact1759.html">independant initiative</a>.</li>
<li>Substituted characters are taken from the first font that contains them, even if a character which matches the weight or slant better is available.</li>
<li>Unicode line breaking is not implemented.</li>
<li>Right-to-left text (Hebrew, Arabic) is not implemented.</li>
</ul>
+
+<h2 id="Printing">Printing</h2>
+
+<p>When NetSurf is run without the Unicode Font Manager, printing will work correctly. When printing with the Unicode Font Manager, as on RISC OS 5 or with a softloaded Unicode Font Manager, there are a couple of things you may need to install.</p>
+
+<ol>
+<li>Using the standard RISC OS 5 release of the printer drivers doesn't work due to lack of Unicode support in the RISC OS 5 printer drivers. However, a version of "<a href="http://www.riscosopen.co.uk/content/downloads/desktop-zipfiles">Printers</a>" that supports printing to non-PostScript printers is available from RISC OS Open Ltd.</li>
+<li>Printing to PostScript printers with Unicode support is not possible with the standard PostScript drivers. In order to print correctly, the more advanced <a href="http://www.mw-software.com/software/ps3/ps3.html">PostScript 3 printer driver</a> by John Tytgat and Martin Würthner is required.</li>
+</ol>
<div class="footer">
13 years, 8 months
r9616 jmb - /branches/jmb/new-cache/content/hlcache.h
by netsurf@semichrome.net
Author: jmb
Date: Thu Oct 8 17:31:42 2009
New Revision: 9616
URL: http://source.netsurf-browser.org?rev=9616&view=rev
Log:
A couple more changes. I'm not sure I like this API.
Modified:
branches/jmb/new-cache/content/hlcache.h
Modified: branches/jmb/new-cache/content/hlcache.h
URL: http://source.netsurf-browser.org/branches/jmb/new-cache/content/hlcache....
==============================================================================
--- branches/jmb/new-cache/content/hlcache.h (original)
+++ branches/jmb/new-cache/content/hlcache.h Thu Oct 8 17:31:42 2009
@@ -62,17 +62,19 @@
* \param flags Object retrieval flags
* \param referer Referring URL, or NULL if none
* \param post POST data, or NULL for a GET request
+ * \param width Available width for content
+ * \param height Available height for content
* \param cb Callback to handle object events
* \param pw Pointer to client-specific data for callback
* \param parent Parent cache handle, or NULL if none
* \param result Pointer to location to recieve cache handle
* \return NSERROR_OK on success, appropriate error otherwise
*
- * \todo fetchcache() has width and height parameters.
- * Do we really need to preserve those?
+ * \todo Is there any way to sensibly reduce the number of parameters here?
*/
nserror hlcache_handle_retrieve(const char *url, uint32_t flags,
const char *referer, llcache_post_data *post,
+ uint32_t width, uint32_t height,
hlcache_object_callback cb, void *pw,
hlcache_handle *parent, hlcache_handle **result);
@@ -92,7 +94,7 @@
* thing being that the client need not care about this possibility and can
* just call the functions with impugnity.
*/
-nserror hlcache_handle_to_content(const hlcache_handle *handle,
+nserror hlcache_handle_get_content(const hlcache_handle *handle,
struct content **result);
#endif
13 years, 8 months
r9615 jmb - /trunk/netsurf/content/content.c
by netsurf@semichrome.net
Author: jmb
Date: Thu Oct 8 07:03:44 2009
New Revision: 9615
URL: http://source.netsurf-browser.org?rev=9615&view=rev
Log:
svgtiny requires separate contents
Modified:
trunk/netsurf/content/content.c
Modified: trunk/netsurf/content/content.c
URL: http://source.netsurf-browser.org/trunk/netsurf/content/content.c?rev=961...
==============================================================================
--- trunk/netsurf/content/content.c (original)
+++ trunk/netsurf/content/content.c Thu Oct 8 07:03:44 2009
@@ -346,7 +346,7 @@
#endif
#ifdef WITH_NS_SVG
{svg_create, 0, svg_convert,
- 0, svg_destroy, 0, svg_redraw, 0, 0, 0, false},
+ 0, svg_destroy, 0, svg_redraw, 0, 0, 0, true},
#endif
#ifdef WITH_RSVG
{rsvg_create, rsvg_process_data, rsvg_convert,
13 years, 8 months