From 270ef59a98d34fef418fb6cd27e46f3edc912948 Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sun, 28 Mar 2010 12:56:39 +0000 Subject: Merge jmb/new-cache; r=dsilvers,rs=vince svn path=/trunk/netsurf/; revision=10180 --- content/hlcache.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 content/hlcache.c (limited to 'content/hlcache.c') diff --git a/content/hlcache.c b/content/hlcache.c new file mode 100644 index 000000000..94d5f0036 --- /dev/null +++ b/content/hlcache.c @@ -0,0 +1,362 @@ +/* + * Copyright 2009 John-Mark Bell + * + * 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 . + */ + +/** \file + * High-level resource cache (implementation) + */ + +#include +#include +#include + +#include "content/content.h" +#include "content/hlcache.h" +#include "utils/log.h" +#include "utils/url.h" + +typedef struct hlcache_entry hlcache_entry; +typedef struct hlcache_retrieval_ctx hlcache_retrieval_ctx; + +/** High-level cache retrieval context */ +struct hlcache_retrieval_ctx { + llcache_handle *llcache; /**< Low-level cache handle */ + + hlcache_handle *handle; /**< High-level handle for object */ + + /* The following are only used if a child content is requested */ + const char *charset; /**< Fallback charset, or NULL */ + bool quirks; /**< Whether object should be quirky */ +}; + +/** High-level cache handle */ +struct hlcache_handle { + hlcache_entry *entry; /**< Pointer to cache entry */ + + hlcache_handle_callback cb; /**< Client callback */ + void *pw; /**< Client data */ +}; + +/** Entry in high-level cache */ +struct hlcache_entry { + struct content *content; /**< Pointer to associated content */ + + hlcache_entry *next; /**< Next sibling */ + hlcache_entry *prev; /**< Previous sibling */ +}; + +/** List of cached content objects */ +static hlcache_entry *hlcache_content_list; + +static nserror hlcache_llcache_callback(llcache_handle *handle, + const llcache_event *event, void *pw); +static nserror hlcache_find_content(hlcache_retrieval_ctx *ctx); +static void hlcache_content_callback(struct content *c, + content_msg msg, union content_msg_data data, void *pw); + +/****************************************************************************** + * Public API * + ******************************************************************************/ + +/** + * Retrieve a high-level cache handle for an object + * + * \param url URL of the object to retrieve handle for + * \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 child Child retrieval context, or NULL for top-level content + * \param result Pointer to location to recieve cache handle + * \return NSERROR_OK on success, appropriate error otherwise + * + * \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_handle_callback cb, void *pw, + hlcache_child_context *child, hlcache_handle **result) +{ + hlcache_retrieval_ctx *ctx; + nserror error; + + assert(cb != NULL); + + ctx = calloc(1, sizeof(hlcache_retrieval_ctx)); + if (ctx == NULL) + return NSERROR_NOMEM; + + ctx->handle = calloc(1, sizeof(hlcache_handle)); + if (ctx->handle == NULL) { + free(ctx); + return NSERROR_NOMEM; + } + + if (child != NULL) { + /** \todo Is the charset guaranteed to exist during fetch? */ + ctx->charset = child->charset; + ctx->quirks = child->quirks; + } + + /** \todo What happens with width/height? */ + + ctx->handle->cb = cb; + ctx->handle->pw = pw; + + error = llcache_handle_retrieve(url, flags, referer, post, + hlcache_llcache_callback, ctx, + &ctx->llcache); + if (error != NSERROR_OK) { + free(ctx->handle); + free(ctx); + return error; + } + + *result = ctx->handle; + + return NSERROR_OK; +} + +/** + * Release a high-level cache handle + * + * \param handle Handle to release + * \return NSERROR_OK on success, appropriate error otherwise + */ +nserror hlcache_handle_release(hlcache_handle *handle) +{ + /** \todo What if this is called during fetch? */ + + if (handle->entry != NULL) { + content_remove_user(handle->entry->content, + hlcache_content_callback, handle); + } + + handle->cb = NULL; + handle->pw = NULL; + + /** \todo Provide hlcache_poll() to perform cache maintenance */ + + return NSERROR_OK; +} + +/** + * Retrieve a content object from a cache handle + * + * \param handle Cache handle to dereference + * \return Pointer to content object, or NULL if there is none + * + * \todo This may not be correct. Ideally, the client should never need to + * directly access a content object. It may, therefore, be better to provide a + * bunch of veneers here that take a hlcache_handle and invoke the + * corresponding content_ API. If there's no content object associated with the + * hlcache_handle (e.g. because the source data is still being fetched, so it + * doesn't exist yet), then these veneers would behave as a NOP. The important + * thing being that the client need not care about this possibility and can + * just call the functions with impugnity. + */ +struct content *hlcache_handle_get_content(const hlcache_handle *handle) +{ + assert(handle != NULL); + + if (handle->entry != NULL) + return handle->entry->content; + + return NULL; +} + +/****************************************************************************** + * High-level cache internals * + ******************************************************************************/ + +/** + * Handler for low-level cache events + * + * \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 + */ +nserror hlcache_llcache_callback(llcache_handle *handle, + const llcache_event *event, void *pw) +{ + hlcache_retrieval_ctx *ctx = pw; + nserror error; + + assert(ctx->llcache == handle); + + switch (event->type) { + case LLCACHE_EVENT_HAD_HEADERS: + error = hlcache_find_content(ctx); + if (error != NSERROR_OK) + return error; + /* No longer require retrieval context */ + free(ctx); + break; + case LLCACHE_EVENT_HAD_DATA: + /* fall through */ + case LLCACHE_EVENT_DONE: + /* should never happen: the handler must be changed */ + break; + case LLCACHE_EVENT_ERROR: + /** \todo handle errors */ + break; + case LLCACHE_EVENT_PROGRESS: + break; + } + + return NSERROR_OK; +} + +/** + * Find a content for the high-level cache handle + * + * \param ctx High-level cache retrieval context + * \return NSERROR_OK on success, appropriate error otherwise + * + * \pre handle::state == HLCACHE_HANDLE_NEW + * \pre Headers must have been received for associated low-level handle + * \post Low-level handle is either released, or associated with new content + * \post High-level handle is registered with content + */ +nserror hlcache_find_content(hlcache_retrieval_ctx *ctx) +{ + hlcache_entry *entry; + hlcache_event event; + + /* Search list of cached contents for a suitable one */ + for (entry = hlcache_content_list; entry != NULL; entry = entry->next) { + const llcache_handle *entry_llcache; + + /** \todo Need to ensure that quirks mode matches */ + /** \todo Need to ensure that content is shareable */ + /** \todo Need to ensure that content can be reused */ + if (entry->content == NULL) + continue; + + /* Ensure that content uses same low-level object as + * low-level handle */ + entry_llcache = content_get_llcache_handle(entry->content); + + if (llcache_handle_references_same_object(entry_llcache, + ctx->llcache)) + break; + } + + if (entry == NULL) { + /* No existing entry, so need to create one */ + entry = malloc(sizeof(hlcache_entry)); + if (entry == NULL) + return NSERROR_NOMEM; + + /* Create content using llhandle */ + entry->content = content_create(ctx->llcache, + ctx->charset, ctx->quirks); + if (entry->content == NULL) { + free(entry); + return NSERROR_NOMEM; + } + + /* Insert into cache */ + entry->prev = NULL; + entry->next = hlcache_content_list; + if (hlcache_content_list != NULL) + hlcache_content_list->prev = entry; + hlcache_content_list = entry; + } else { + /* Found a suitable content: no longer need low-level handle */ + llcache_handle_release(ctx->llcache); + } + + /* Associate handle with content */ + if (content_add_user(entry->content, + hlcache_content_callback, ctx->handle) == false) + return NSERROR_NOMEM; + + /* Associate cache entry with handle */ + ctx->handle->entry = entry; + + /* Catch handle up with state of content */ + if (ctx->handle->cb != NULL) { + content_status status = content_get_status(ctx->handle); + + if (status == CONTENT_STATUS_LOADING) { + event.type = CONTENT_MSG_LOADING; + ctx->handle->cb(ctx->handle, &event, ctx->handle->pw); + } else if (status == CONTENT_STATUS_READY) { + event.type = CONTENT_MSG_LOADING; + ctx->handle->cb(ctx->handle, &event, ctx->handle->pw); + + if (ctx->handle->cb != NULL) { + event.type = CONTENT_MSG_READY; + ctx->handle->cb(ctx->handle, &event, + ctx->handle->pw); + } + } else if (status == CONTENT_STATUS_DONE) { + event.type = CONTENT_MSG_LOADING; + ctx->handle->cb(ctx->handle, &event, ctx->handle->pw); + + /** \todo Reflow content to new width + if (content_get_available_width(ctx->handle) != width) + content_reformat(ctx->handle, width, height); + */ + + if (ctx->handle->cb != NULL) { + event.type = CONTENT_MSG_READY; + ctx->handle->cb(ctx->handle, &event, + ctx->handle->pw); + } + + if (ctx->handle->cb != NULL) { + event.type = CONTENT_MSG_DONE; + ctx->handle->cb(ctx->handle, &event, + ctx->handle->pw); + } + } + } + + return NSERROR_OK; +} + +/** + * Veneer between content callback API and hlcache callback API + * + * \param c Content to emit message for + * \param msg Message to emit + * \param data Data for message + * \param pw Pointer to private data (hlcache_handle) + */ +void hlcache_content_callback(struct content *c, content_msg msg, + union content_msg_data data, void *pw) +{ + hlcache_handle *handle = pw; + hlcache_event event; + nserror error; + + event.type = msg; + event.data = data; + + + error = handle->cb(handle, &event, handle->pw); + if (error != NSERROR_OK) + LOG(("Error in callback: %d", error)); +} + -- cgit v1.2.3