From da65b5d0c12c744a1194d5a88d5b364e144f17a3 Mon Sep 17 00:00:00 2001 From: Christian Dywan Date: Mon, 19 Oct 2009 23:50:50 +0200 Subject: [PATCH] Implement a primitive web cache extension (unstable) Incoming files are cached and saved to disk, as well as looked up when files are requested. Only images are considered at the moment and there is no epxiration handling or updating at all. Plus it crashes in certain cases. --- extensions/web-cache.c | 293 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 extensions/web-cache.c diff --git a/extensions/web-cache.c b/extensions/web-cache.c new file mode 100644 index 00000000..946fa7a4 --- /dev/null +++ b/extensions/web-cache.c @@ -0,0 +1,293 @@ +/* + Copyright (C) 2009 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +#include + +#include +#include "config.h" + +#include +#if HAVE_UNISTD_H + #include +#endif + +#define HAVE_WEBKIT_RESOURCE_REQUEST 0 /* WEBKIT_CHECK_VERSION (1, 1, 14) */ + +static gchar* +web_cache_get_cached_path (const gchar* cache_path, + const gchar* uri) +{ + gchar* checksum; + gchar* extension; + gchar* cached_filename; + gchar* cached_path; + + g_mkdir_with_parents (cache_path, 0700); + checksum = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1); + + extension = g_strrstr (uri, "."); + cached_filename = g_strdup_printf ("%s%s", checksum, + extension ? extension : ""); + g_free (checksum); + cached_path = g_build_filename (cache_path, cached_filename, NULL); + g_free (cached_filename); + return cached_path; +} + +#if HAVE_WEBKIT_RESOURCE_REQUEST +static void +web_cache_resource_request_starting_cb (WebKitWebView* web_view, + WebKitWebFrame* web_frame, + WebKitWebResource* web_resource, + WebKitNetworkRequest* request, + WebKitNetworkResponse* response, + MidoriExtension* extension) +{ + const gchar* uri; + const gchar* cache_path; + gchar* filename; + + uri = webkit_network_request_get_uri (request); + if (!(uri && g_str_has_prefix (uri, "http://"))) + return; + + cache_path = midori_extension_get_string (extension, "path"); + filename = web_cache_get_cached_path (cache_path, uri); + /* g_debug ("cache lookup: %s => %s", uri, filename); */ + + g_free (filename); +} +#else +static void +web_cache_mesage_got_headers_cb (SoupMessage* msg, + MidoriExtension* extension) +{ + const gchar* cache_path = midori_extension_get_string (extension, "path"); + SoupURI* soup_uri = soup_message_get_uri (msg); + gchar* uri = soup_uri ? soup_uri_to_string (soup_uri, FALSE) : g_strdup (""); + gchar* filename = web_cache_get_cached_path (cache_path, uri); + gchar* data; + gsize length; + + g_debug ("cache serve: %s", filename); + + /* FIXME: Inspect headers and decide whether we need to update the cache */ + + g_file_get_contents (filename, &data, &length, NULL); + /* soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE, data, length); */ + /* FIXME: MIME type */ + soup_message_set_response (msg, "image/jpeg", SOUP_MEMORY_TAKE, data, length); + soup_message_body_complete (msg->response_body); + soup_message_finished (msg); + /* soup_message_cancel (msg); */ + + g_free (filename); + g_free (uri); +} + +static void +web_cache_mesage_got_chunk_cb (SoupMessage* msg, + SoupBuffer* chunk, + MidoriExtension* extension) +{ + /* Fill the body in manually for later use, even if WebKitGTK+ + disables accumulation. We should probably do this differently. */ + if (!soup_message_body_get_accumulate (msg->response_body)) + soup_message_body_append_buffer (msg->response_body, chunk); +} + +static void +web_cache_session_request_queued_cb (SoupSession* session, + SoupMessage* msg, + MidoriExtension* extension) +{ + SoupURI* soup_uri = soup_message_get_uri (msg); + gchar* uri = soup_uri ? soup_uri_to_string (soup_uri, FALSE) : g_strdup (""); + + if (g_str_has_prefix (uri, "http")) + { + const gchar* cache_path = midori_extension_get_string (extension, "path"); + gchar* filename = web_cache_get_cached_path (cache_path, uri); + + /* g_debug ("cache lookup: %s => %s", uri, filename); */ + + if (g_file_test (filename, G_FILE_TEST_EXISTS)) + g_signal_connect (msg, "got-headers", + G_CALLBACK (web_cache_mesage_got_chunk_cb), extension); + else + g_signal_connect (msg, "got-chunk", + G_CALLBACK (web_cache_mesage_got_chunk_cb), extension); + + g_free (filename); + } + + g_free (uri); +} +#endif + +static void +web_cache_session_request_unqueued_cb (SoupSession* session, + SoupMessage* msg, + MidoriExtension* extension) +{ + SoupURI* soup_uri = soup_message_get_uri (msg); + gchar* uri = soup_uri ? soup_uri_to_string (soup_uri, FALSE) : NULL; + + /* g_debug ("request unqueued: %d", msg->status_code); */ + + if (uri && msg->status_code != SOUP_STATUS_OK && g_str_has_prefix (uri, "http")) + { + SoupMessageHeaders* hdrs = msg->response_headers; + const gchar* mime_type = soup_message_headers_get_content_type (hdrs, NULL); + + /* Only images are cached */ + if (mime_type && g_str_has_prefix (mime_type, "image/")) + { + const gchar* cache_path = midori_extension_get_string (extension, "path"); + gchar* filename = web_cache_get_cached_path (cache_path, uri); + SoupMessageBody* body = msg->response_body; + SoupBuffer* buffer = NULL; + + /* We fed the buffer manually before, so this actually works. */ + soup_message_body_set_accumulate (body, TRUE); + buffer = soup_message_body_flatten (body); + + /* g_debug ("cache store: %s => %s", uri, filename); */ + + /* FIXME: Update sensibly */ + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) + g_file_set_contents (filename, body->data, body->length, NULL); + + if (buffer) + soup_buffer_free (buffer); + } + /* else + g_debug ("cache skip: %s", mime_type); */ + } + + g_free (uri); +} + +static void +web_cache_add_tab_cb (MidoriBrowser* browser, + MidoriView* view, + MidoriExtension* extension) +{ + GtkWidget* web_view = gtk_bin_get_child (GTK_BIN (view)); + #if HAVE_WEBKIT_RESOURCE_REQUEST + g_signal_connect (web_view, "resource-request-starting", + G_CALLBACK (web_cache_resource_request_starting_cb), extension); + #endif +} + +static void +web_cache_deactivate_cb (MidoriExtension* extension, + MidoriBrowser* browser); + +static void +web_cache_add_tab_foreach_cb (MidoriView* view, + MidoriBrowser* browser, + MidoriExtension* extension) +{ + web_cache_add_tab_cb (browser, view, extension); +} + +static void +web_cache_app_add_browser_cb (MidoriApp* app, + MidoriBrowser* browser, + MidoriExtension* extension) +{ + midori_browser_foreach (browser, + (GtkCallback)web_cache_add_tab_foreach_cb, extension); + g_signal_connect (browser, "add-tab", + G_CALLBACK (web_cache_add_tab_cb), extension); + g_signal_connect (extension, "deactivate", + G_CALLBACK (web_cache_deactivate_cb), browser); +} + +static void +web_cache_deactivate_tabs (MidoriView* view, + MidoriExtension* extension) +{ + GtkWidget* web_view = gtk_bin_get_child (GTK_BIN (view)); + MidoriBrowser* browser = midori_browser_get_for_widget (web_view); + + g_signal_handlers_disconnect_by_func ( + browser, web_cache_add_tab_cb, 0); + #if HAVE_WEBKIT_RESOURCE_REQUEST + g_signal_handlers_disconnect_by_func ( + web_view, web_cache_resource_request_starting_cb, extension); + #else + g_signal_handlers_disconnect_by_func ( + webkit_get_default_session (), web_cache_session_request_queued_cb, extension); + #endif +} + +static void +web_cache_deactivate_cb (MidoriExtension* extension, + MidoriBrowser* browser) +{ + MidoriApp* app = midori_extension_get_app (extension); + + g_signal_handlers_disconnect_by_func ( + extension, web_cache_deactivate_cb, browser); + g_signal_handlers_disconnect_by_func ( + app, web_cache_app_add_browser_cb, extension); + midori_browser_foreach (browser, (GtkCallback)web_cache_deactivate_tabs, extension); +} + +static void +web_cache_activate_cb (MidoriExtension* extension, + MidoriApp* app) +{ + KatzeArray* browsers; + MidoriBrowser* browser; + guint i; + SoupSession* session = webkit_get_default_session (); + + #if !HAVE_WEBKIT_RESOURCE_REQUEST + g_signal_connect (session, "request-queued", + G_CALLBACK (web_cache_session_request_queued_cb), extension); + #endif + g_signal_connect (session, "request-unqueued", + G_CALLBACK (web_cache_session_request_unqueued_cb), extension); + + browsers = katze_object_get_object (app, "browsers"); + i = 0; + while ((browser = katze_array_get_nth_item (browsers, i++))) + web_cache_app_add_browser_cb (app, browser, extension); + g_signal_connect (app, "add-browser", + G_CALLBACK (web_cache_app_add_browser_cb), extension); + + g_object_unref (browsers); +} + +MidoriExtension* +extension_init (void) +{ + gchar* cache_path = g_build_filename (g_get_user_cache_dir (), + PACKAGE_NAME, "web", NULL); + MidoriExtension* extension = g_object_new (MIDORI_TYPE_EXTENSION, + "name", _("Web Cache"), + "description", _("Cache HTTP communication on disk"), + "version", "0.1", + "authors", "Christian Dywan ", + NULL); + midori_extension_install_string (extension, "path", cache_path); + midori_extension_install_integer (extension, "size", 50); + + g_free (cache_path); + + g_signal_connect (extension, "activate", + G_CALLBACK (web_cache_activate_cb), NULL); + + return extension; +}