1 /*
2 * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
3 *
4 * This file is part of NetSurf, http://www.netsurf-browser.org/
5 *
6 * NetSurf is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * NetSurf is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * \file
21 * High-level resource cache implementation.
22 */
23
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "utils/http.h"
29 #include "utils/log.h"
30 #include "utils/messages.h"
31 #include "utils/ring.h"
32 #include "utils/utils.h"
33 #include "netsurf/misc.h"
34 #include "netsurf/content.h"
35 #include "desktop/gui_internal.h"
36
37 #include "content/mimesniff.h"
38 #include "content/hlcache.h"
39 // Note, this is *ONLY* so that we can abort cleanly during shutdown of the cache
40 #include "content/content_protected.h"
41 #include "content/content_factory.h"
42
43 typedef struct hlcache_entry hlcache_entry;
44 typedef struct hlcache_retrieval_ctx hlcache_retrieval_ctx;
45
46 /** High-level cache retrieval context */
47 struct hlcache_retrieval_ctx {
48 struct hlcache_retrieval_ctx *r_prev; /**< Previous retrieval context in the ring */
49 struct hlcache_retrieval_ctx *r_next; /**< Next retrieval context in the ring */
50
51 llcache_handle *llcache; /**< Low-level cache handle */
52
53 hlcache_handle *handle; /**< High-level handle for object */
54
55 uint32_t flags; /**< Retrieval flags */
56
57 content_type accepted_types; /**< Accepted types */
58
59 hlcache_child_context child; /**< Child context */
60
61 bool migrate_target; /**< Whether this context is the migration target */
62 };
63
64 /** High-level cache handle */
65 struct hlcache_handle {
66 hlcache_entry *entry; /**< Pointer to cache entry */
67
68 hlcache_handle_callback cb; /**< Client callback */
69 void *pw; /**< Client data */
70 };
71
72 /** Entry in high-level cache */
73 struct hlcache_entry {
74 struct content *content; /**< Pointer to associated content */
75
76 hlcache_entry *next; /**< Next sibling */
77 hlcache_entry *prev; /**< Previous sibling */
78 };
79
80 /** Current state of the cache.
81 *
82 * Global state of the cache.
83 */
84 struct hlcache_s {
85 struct hlcache_parameters params;
86
87 /** List of cached content objects */
88 hlcache_entry *content_list;
89
90 /** Ring of retrieval contexts */
91 hlcache_retrieval_ctx *retrieval_ctx_ring;
92
93 /* statistics */
94 unsigned int hit_count;
95 unsigned int miss_count;
96 };
97
98 /** high level cache state */
99 static struct hlcache_s *hlcache = NULL;
100
101
102 /******************************************************************************
103 * High-level cache internals *
104 ******************************************************************************/
105
106
107 /**
108 * Attempt to clean the cache
109 */
hlcache_clean(void * force_clean_flag)110 static void hlcache_clean(void *force_clean_flag)
111 {
112 hlcache_entry *entry, *next;
113 bool force_clean = (force_clean_flag != NULL);
114
115 for (entry = hlcache->content_list; entry != NULL; entry = next) {
116 next = entry->next;
117
118 if (entry->content == NULL)
119 continue;
120
121 if (content_count_users(entry->content) != 0)
122 continue;
123
124 if (content__get_status(entry->content) == CONTENT_STATUS_LOADING) {
125 if (force_clean == false)
126 continue;
127 NSLOG(netsurf, DEBUG, "Forcing content cleanup during shutdown");
128 content_abort(entry->content);
129 content_set_error(entry->content);
130 }
131
132 /** \todo This is over-zealous: all unused contents
133 * will be immediately destroyed. Ideally, we want to
134 * purge all unused contents that are using stale
135 * source data, and enough fresh contents such that
136 * the cache fits in the configured cache size limit.
137 */
138
139 /* Remove entry from cache */
140 if (entry->prev == NULL)
141 hlcache->content_list = entry->next;
142 else
143 entry->prev->next = entry->next;
144
145 if (entry->next != NULL)
146 entry->next->prev = entry->prev;
147
148 /* Destroy content */
149 content_destroy(entry->content);
150
151 /* Destroy entry */
152 free(entry);
153 }
154
155 /* Attempt to clean the llcache */
156 llcache_clean(false);
157
158 /* Re-schedule ourselves */
159 guit->misc->schedule(hlcache->params.bg_clean_time, hlcache_clean, NULL);
160 }
161
162 /**
163 * Determine if the specified MIME type is acceptable
164 *
165 * \param mime_type MIME type to consider
166 * \param accepted_types Array of acceptable types, or NULL for any
167 * \param computed_type Pointer to location to receive computed type of object
168 * \return True if the type is acceptable, false otherwise
169 */
hlcache_type_is_acceptable(lwc_string * mime_type,content_type accepted_types,content_type * computed_type)170 static bool hlcache_type_is_acceptable(lwc_string *mime_type,
171 content_type accepted_types, content_type *computed_type)
172 {
173 content_type type;
174
175 type = content_factory_type_from_mime_type(mime_type);
176
177 *computed_type = type;
178
179 return ((accepted_types & type) != 0);
180 }
181
182 /**
183 * Veneer between content callback API and hlcache callback API
184 *
185 * \param c Content to emit message for
186 * \param msg Message to emit
187 * \param data Data for message
188 * \param pw Pointer to private data (hlcache_handle)
189 */
hlcache_content_callback(struct content * c,content_msg msg,const union content_msg_data * data,void * pw)190 static void hlcache_content_callback(struct content *c, content_msg msg,
191 const union content_msg_data *data, void *pw)
192 {
193 hlcache_handle *handle = pw;
194 nserror error = NSERROR_OK;
195 hlcache_event event = {
196 .type = msg,
197 };
198
199 if (data != NULL) {
200 event.data = *data;
201 }
202
203 if (handle->cb != NULL)
204 error = handle->cb(handle, &event, handle->pw);
205
206 if (error != NSERROR_OK)
207 NSLOG(netsurf, INFO, "Error in callback: %d", error);
208 }
209
210 /**
211 * Find a content for the high-level cache handle
212 *
213 * \param ctx High-level cache retrieval context
214 * \param effective_type Effective MIME type of content
215 * \return NSERROR_OK on success,
216 * NSERROR_NEED_DATA on success where data is needed,
217 * appropriate error otherwise
218 *
219 * \pre handle::state == HLCACHE_HANDLE_NEW
220 * \pre Headers must have been received for associated low-level handle
221 * \post Low-level handle is either released, or associated with new content
222 * \post High-level handle is registered with content
223 */
hlcache_find_content(hlcache_retrieval_ctx * ctx,lwc_string * effective_type)224 static nserror hlcache_find_content(hlcache_retrieval_ctx *ctx,
225 lwc_string *effective_type)
226 {
227 hlcache_entry *entry;
228 hlcache_event event;
229 nserror error = NSERROR_OK;
230
231 /* Search list of cached contents for a suitable one */
232 for (entry = hlcache->content_list; entry != NULL; entry = entry->next) {
233 hlcache_handle entry_handle = { entry, NULL, NULL };
234 const llcache_handle *entry_llcache;
235
236 if (entry->content == NULL)
237 continue;
238
239 /* Ignore contents in the error state */
240 if (content_get_status(&entry_handle) == CONTENT_STATUS_ERROR)
241 continue;
242
243 /* Ensure that content is shareable */
244 if (content_is_shareable(entry->content) == false)
245 continue;
246
247 /* Ensure that quirks mode is acceptable */
248 if (content_matches_quirks(entry->content,
249 ctx->child.quirks) == false)
250 continue;
251
252 /* Ensure that content uses same low-level object as
253 * low-level handle */
254 entry_llcache = content_get_llcache_handle(entry->content);
255
256 if (llcache_handle_references_same_object(entry_llcache,
257 ctx->llcache))
258 break;
259 }
260
261 if (entry == NULL) {
262 /* No existing entry, so need to create one */
263 entry = malloc(sizeof(hlcache_entry));
264 if (entry == NULL)
265 return NSERROR_NOMEM;
266
267 /* Create content using llhandle */
268 entry->content = content_factory_create_content(ctx->llcache,
269 ctx->child.charset, ctx->child.quirks,
270 effective_type);
271 if (entry->content == NULL) {
272 free(entry);
273 return NSERROR_NOMEM;
274 }
275
276 /* Insert into cache */
277 entry->prev = NULL;
278 entry->next = hlcache->content_list;
279 if (hlcache->content_list != NULL)
280 hlcache->content_list->prev = entry;
281 hlcache->content_list = entry;
282
283 /* Signal to caller that we created a content */
284 error = NSERROR_NEED_DATA;
285
286 hlcache->miss_count++;
287 } else {
288 /* Found a suitable content: no longer need low-level handle */
289 llcache_handle_release(ctx->llcache);
290 hlcache->hit_count++;
291 }
292
293 /* Associate handle with content */
294 if (content_add_user(entry->content,
295 hlcache_content_callback, ctx->handle) == false)
296 return NSERROR_NOMEM;
297
298 /* Associate cache entry with handle */
299 ctx->handle->entry = entry;
300
301 /* Catch handle up with state of content */
302 if (ctx->handle->cb != NULL) {
303 content_status status = content_get_status(ctx->handle);
304
305 if (status == CONTENT_STATUS_LOADING) {
306 event.type = CONTENT_MSG_LOADING;
307 ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
308 } else if (status == CONTENT_STATUS_READY) {
309 event.type = CONTENT_MSG_LOADING;
310 ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
311
312 if (ctx->handle->cb != NULL) {
313 event.type = CONTENT_MSG_READY;
314 ctx->handle->cb(ctx->handle, &event,
315 ctx->handle->pw);
316 }
317 } else if (status == CONTENT_STATUS_DONE) {
318 event.type = CONTENT_MSG_LOADING;
319 ctx->handle->cb(ctx->handle, &event, ctx->handle->pw);
320
321 if (ctx->handle->cb != NULL) {
322 event.type = CONTENT_MSG_READY;
323 ctx->handle->cb(ctx->handle, &event,
324 ctx->handle->pw);
325 }
326
327 if (ctx->handle->cb != NULL) {
328 event.type = CONTENT_MSG_DONE;
329 ctx->handle->cb(ctx->handle, &event,
330 ctx->handle->pw);
331 }
332 }
333 }
334
335 return error;
336 }
337
338 /**
339 * Migrate a retrieval context into its final destination content
340 *
341 * \param ctx Context to migrate
342 * \param effective_type The effective MIME type of the content, or NULL
343 * \return NSERROR_OK on success,
344 * NSERROR_NEED_DATA on success where data is needed,
345 * appropriate error otherwise
346 */
hlcache_migrate_ctx(hlcache_retrieval_ctx * ctx,lwc_string * effective_type)347 static nserror hlcache_migrate_ctx(hlcache_retrieval_ctx *ctx,
348 lwc_string *effective_type)
349 {
350 content_type type = CONTENT_NONE;
351 nserror error = NSERROR_OK;
352
353 ctx->migrate_target = true;
354
355 if ((effective_type != NULL) &&
356 hlcache_type_is_acceptable(effective_type,
357 ctx->accepted_types,
358 &type)) {
359 error = hlcache_find_content(ctx, effective_type);
360 if (error != NSERROR_OK && error != NSERROR_NEED_DATA) {
361 if (ctx->handle->cb != NULL) {
362 hlcache_event hlevent;
363
364 hlevent.type = CONTENT_MSG_ERROR;
365 hlevent.data.errordata.errorcode = NSERROR_UNKNOWN;
366 hlevent.data.errordata.errormsg = messages_get("MiscError");
367
368 ctx->handle->cb(ctx->handle, &hlevent,
369 ctx->handle->pw);
370 }
371
372 llcache_handle_abort(ctx->llcache);
373 llcache_handle_release(ctx->llcache);
374 }
375 } else if (type == CONTENT_NONE &&
376 (ctx->flags & HLCACHE_RETRIEVE_MAY_DOWNLOAD)) {
377 /* Unknown type, and we can download, so convert */
378 llcache_handle_force_stream(ctx->llcache);
379
380 if (ctx->handle->cb != NULL) {
381 hlcache_event hlevent;
382
383 hlevent.type = CONTENT_MSG_DOWNLOAD;
384 hlevent.data.download = ctx->llcache;
385
386 ctx->handle->cb(ctx->handle, &hlevent,
387 ctx->handle->pw);
388 }
389
390 /* Ensure caller knows we need data */
391 error = NSERROR_NEED_DATA;
392 } else {
393 /* Unacceptable type: report error */
394 if (ctx->handle->cb != NULL) {
395 hlcache_event hlevent;
396
397 hlevent.type = CONTENT_MSG_ERROR;
398 hlevent.data.errordata.errorcode = NSERROR_UNKNOWN;
399 hlevent.data.errordata.errormsg = messages_get("UnacceptableType");
400
401 ctx->handle->cb(ctx->handle, &hlevent,
402 ctx->handle->pw);
403 }
404
405 llcache_handle_abort(ctx->llcache);
406 llcache_handle_release(ctx->llcache);
407 }
408
409 ctx->migrate_target = false;
410
411 /* No longer require retrieval context */
412 RING_REMOVE(hlcache->retrieval_ctx_ring, ctx);
413 free((char *) ctx->child.charset);
414 free(ctx);
415
416 return error;
417 }
418
419 /**
420 * Handler for low-level cache events
421 *
422 * \param handle Handle for which event is issued
423 * \param event Event data
424 * \param pw Pointer to client-specific data
425 * \return NSERROR_OK on success, appropriate error otherwise
426 */
427 static nserror
hlcache_llcache_callback(llcache_handle * handle,const llcache_event * event,void * pw)428 hlcache_llcache_callback(llcache_handle *handle,
429 const llcache_event *event,
430 void *pw)
431 {
432 hlcache_retrieval_ctx *ctx = pw;
433 lwc_string *effective_type = NULL;
434 nserror error;
435
436 assert(ctx->llcache == handle);
437
438 switch (event->type) {
439 case LLCACHE_EVENT_GOT_CERTS:
440 /* Pass them on upward */
441 if (ctx->handle->cb != NULL) {
442 hlcache_event hlevent;
443
444 hlevent.type = CONTENT_MSG_SSL_CERTS;
445 hlevent.data.chain = event->data.chain;
446
447 ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
448 }
449 break;
450 case LLCACHE_EVENT_HAD_HEADERS:
451 error = mimesniff_compute_effective_type(llcache_handle_get_header(handle, "Content-Type"), NULL, 0,
452 ctx->flags & HLCACHE_RETRIEVE_SNIFF_TYPE,
453 ctx->accepted_types == CONTENT_IMAGE,
454 &effective_type);
455 if (error == NSERROR_OK || error == NSERROR_NOT_FOUND) {
456 /* If the sniffer was successful or failed to find
457 * a Content-Type header when sniffing was
458 * prohibited, we must migrate the retrieval context. */
459 error = hlcache_migrate_ctx(ctx, effective_type);
460
461 if (effective_type != NULL)
462 lwc_string_unref(effective_type);
463 }
464
465 /* No need to report that we need data:
466 * we'll get some anyway if there is any */
467 if (error == NSERROR_NEED_DATA)
468 error = NSERROR_OK;
469
470 return error;
471
472 break;
473 case LLCACHE_EVENT_HAD_DATA:
474 error = mimesniff_compute_effective_type(llcache_handle_get_header(handle, "Content-Type"),
475 event->data.data.buf, event->data.data.len,
476 ctx->flags & HLCACHE_RETRIEVE_SNIFF_TYPE,
477 ctx->accepted_types == CONTENT_IMAGE,
478 &effective_type);
479 if (error != NSERROR_OK) {
480 assert(0 && "MIME sniff failed with data");
481 }
482
483 error = hlcache_migrate_ctx(ctx, effective_type);
484
485 lwc_string_unref(effective_type);
486
487 return error;
488
489 break;
490 case LLCACHE_EVENT_DONE:
491 /* DONE event before we could determine the effective MIME type.
492 */
493 error = mimesniff_compute_effective_type(llcache_handle_get_header(handle, "Content-Type"),
494 NULL, 0, false, false, &effective_type);
495 if (error == NSERROR_OK || error == NSERROR_NOT_FOUND) {
496 error = hlcache_migrate_ctx(ctx, effective_type);
497
498 if (effective_type != NULL) {
499 lwc_string_unref(effective_type);
500 }
501
502 return error;
503 }
504
505 if (ctx->handle->cb != NULL) {
506 hlcache_event hlevent;
507
508 hlevent.type = CONTENT_MSG_ERROR;
509 hlevent.data.errordata.errorcode = error;
510 hlevent.data.errordata.errormsg = NULL;
511
512 ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
513 }
514 break;
515 case LLCACHE_EVENT_ERROR:
516 if (ctx->handle->cb != NULL) {
517 hlcache_event hlevent;
518
519 hlevent.type = CONTENT_MSG_ERROR;
520 hlevent.data.errordata.errorcode = event->data.error.code;
521 hlevent.data.errordata.errormsg = event->data.error.msg;
522
523 ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
524 }
525 break;
526 case LLCACHE_EVENT_PROGRESS:
527 break;
528 case LLCACHE_EVENT_REDIRECT:
529 if (ctx->handle->cb != NULL) {
530 hlcache_event hlevent;
531
532 hlevent.type = CONTENT_MSG_REDIRECT;
533 hlevent.data.redirect.from = event->data.redirect.from;
534 hlevent.data.redirect.to = event->data.redirect.to;
535
536 ctx->handle->cb(ctx->handle, &hlevent, ctx->handle->pw);
537 }
538 break;
539 }
540
541 return NSERROR_OK;
542 }
543
544
545 /******************************************************************************
546 * Public API *
547 ******************************************************************************/
548
549
550 nserror
hlcache_initialise(const struct hlcache_parameters * hlcache_parameters)551 hlcache_initialise(const struct hlcache_parameters *hlcache_parameters)
552 {
553 nserror ret;
554
555 hlcache = calloc(1, sizeof(struct hlcache_s));
556 if (hlcache == NULL) {
557 return NSERROR_NOMEM;
558 }
559
560 ret = llcache_initialise(&hlcache_parameters->llcache);
561 if (ret != NSERROR_OK) {
562 free(hlcache);
563 hlcache = NULL;
564 return ret;
565 }
566
567 hlcache->params = *hlcache_parameters;
568
569 /* Schedule the cache cleanup */
570 guit->misc->schedule(hlcache->params.bg_clean_time, hlcache_clean, NULL);
571
572 return NSERROR_OK;
573 }
574
575 /* See hlcache.h for documentation */
hlcache_stop(void)576 void hlcache_stop(void)
577 {
578 /* Remove the hlcache_clean schedule */
579 guit->misc->schedule(-1, hlcache_clean, NULL);
580 }
581
582 /* See hlcache.h for documentation */
hlcache_finalise(void)583 void hlcache_finalise(void)
584 {
585 uint32_t num_contents, prev_contents;
586 hlcache_entry *entry;
587 hlcache_retrieval_ctx *ctx, *next;
588
589 /* Obtain initial count of contents remaining */
590 for (num_contents = 0, entry = hlcache->content_list;
591 entry != NULL; entry = entry->next) {
592 num_contents++;
593 }
594
595 NSLOG(netsurf, INFO, "%d contents remain before cache drain",
596 num_contents);
597
598 /* Drain cache */
599 do {
600 prev_contents = num_contents;
601
602 hlcache_clean(NULL);
603
604 for (num_contents = 0, entry = hlcache->content_list;
605 entry != NULL; entry = entry->next) {
606 num_contents++;
607 }
608 } while (num_contents > 0 && num_contents != prev_contents);
609
610 NSLOG(netsurf, INFO, "%d contents remaining after being polite", num_contents);
611
612 /* Drain cache again, forcing the matter */
613 do {
614 prev_contents = num_contents;
615
616 hlcache_clean(&entry); // Any non-NULL pointer will do
617
618 for (num_contents = 0, entry = hlcache->content_list;
619 entry != NULL; entry = entry->next) {
620 num_contents++;
621 }
622 } while (num_contents > 0 && num_contents != prev_contents);
623
624 NSLOG(netsurf, INFO, "%d contents remaining:", num_contents);
625 for (entry = hlcache->content_list; entry != NULL; entry = entry->next) {
626 hlcache_handle entry_handle = { entry, NULL, NULL };
627
628 if (entry->content != NULL) {
629 NSLOG(netsurf, INFO, " %p : %s (%d users)",
630 entry,
631 nsurl_access(hlcache_handle_get_url(&entry_handle)),
632 content_count_users(entry->content));
633 } else {
634 NSLOG(netsurf, INFO, " %p", entry);
635 }
636 }
637
638 /* Clean up retrieval contexts */
639 if (hlcache->retrieval_ctx_ring != NULL) {
640 ctx = hlcache->retrieval_ctx_ring;
641
642 do {
643 next = ctx->r_next;
644
645 if (ctx->llcache != NULL)
646 llcache_handle_release(ctx->llcache);
647
648 if (ctx->handle != NULL)
649 free(ctx->handle);
650
651 if (ctx->child.charset != NULL)
652 free((char *) ctx->child.charset);
653
654 free(ctx);
655
656 ctx = next;
657 } while (ctx != hlcache->retrieval_ctx_ring);
658
659 hlcache->retrieval_ctx_ring = NULL;
660 }
661
662 NSLOG(netsurf, INFO, "hit/miss %d/%d", hlcache->hit_count,
663 hlcache->miss_count);
664
665 /* De-schedule ourselves */
666 guit->misc->schedule(-1, hlcache_clean, NULL);
667
668 free(hlcache);
669 hlcache = NULL;
670
671 NSLOG(netsurf, INFO, "Finalising low-level cache");
672 llcache_finalise();
673 }
674
675 /* See hlcache.h for documentation */
hlcache_handle_retrieve(nsurl * url,uint32_t flags,nsurl * referer,llcache_post_data * post,hlcache_handle_callback cb,void * pw,hlcache_child_context * child,content_type accepted_types,hlcache_handle ** result)676 nserror hlcache_handle_retrieve(nsurl *url, uint32_t flags,
677 nsurl *referer, llcache_post_data *post,
678 hlcache_handle_callback cb, void *pw,
679 hlcache_child_context *child,
680 content_type accepted_types, hlcache_handle **result)
681 {
682 hlcache_retrieval_ctx *ctx;
683 nserror error;
684
685 assert(cb != NULL);
686
687 ctx = calloc(1, sizeof(hlcache_retrieval_ctx));
688 if (ctx == NULL) {
689 return NSERROR_NOMEM;
690 }
691
692 ctx->handle = calloc(1, sizeof(hlcache_handle));
693 if (ctx->handle == NULL) {
694 free(ctx);
695 return NSERROR_NOMEM;
696 }
697
698 if (child != NULL) {
699 if (child->charset != NULL) {
700 ctx->child.charset = strdup(child->charset);
701 if (ctx->child.charset == NULL) {
702 free(ctx->handle);
703 free(ctx);
704 return NSERROR_NOMEM;
705 }
706 }
707 ctx->child.quirks = child->quirks;
708 }
709
710 ctx->flags = flags;
711 ctx->accepted_types = accepted_types;
712
713 ctx->handle->cb = cb;
714 ctx->handle->pw = pw;
715
716 error = llcache_handle_retrieve(url, flags, referer, post,
717 hlcache_llcache_callback, ctx,
718 &ctx->llcache);
719 if (error != NSERROR_OK) {
720 /* error retrieving handle so free context and return error */
721 free((char *) ctx->child.charset);
722 free(ctx->handle);
723 free(ctx);
724 } else {
725 /* successfully started fetch so add new context to list */
726 RING_INSERT(hlcache->retrieval_ctx_ring, ctx);
727
728 *result = ctx->handle;
729 }
730 return error;
731 }
732
733 /* See hlcache.h for documentation */
hlcache_handle_release(hlcache_handle * handle)734 nserror hlcache_handle_release(hlcache_handle *handle)
735 {
736 if (handle->entry != NULL) {
737 content_remove_user(handle->entry->content,
738 hlcache_content_callback, handle);
739 } else {
740 RING_ITERATE_START(struct hlcache_retrieval_ctx,
741 hlcache->retrieval_ctx_ring,
742 ictx) {
743 if (ictx->handle == handle &&
744 ictx->migrate_target == false) {
745 /* This is the nascent context for us,
746 * so abort the fetch */
747 llcache_handle_abort(ictx->llcache);
748 llcache_handle_release(ictx->llcache);
749 /* Remove us from the ring */
750 RING_REMOVE(hlcache->retrieval_ctx_ring, ictx);
751 /* Throw us away */
752 free((char *) ictx->child.charset);
753 free(ictx);
754 /* And stop */
755 RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
756 ictx);
757 }
758 } RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
759 }
760
761 handle->cb = NULL;
762 handle->pw = NULL;
763
764 free(handle);
765
766 return NSERROR_OK;
767 }
768
769 /* See hlcache.h for documentation */
hlcache_handle_get_content(const hlcache_handle * handle)770 struct content *hlcache_handle_get_content(const hlcache_handle *handle)
771 {
772 if ((handle != NULL) && (handle->entry != NULL)) {
773 return handle->entry->content;
774 }
775
776 return NULL;
777 }
778
779 /* See hlcache.h for documentation */
hlcache_handle_abort(hlcache_handle * handle)780 nserror hlcache_handle_abort(hlcache_handle *handle)
781 {
782 struct hlcache_entry *entry = handle->entry;
783 struct content *c;
784
785 if (entry == NULL) {
786 /* This handle is not yet associated with a cache entry.
787 * The implication is that the fetch for the handle has
788 * not progressed to the point where the entry can be
789 * created. */
790
791 RING_ITERATE_START(struct hlcache_retrieval_ctx,
792 hlcache->retrieval_ctx_ring,
793 ictx) {
794 if (ictx->handle == handle &&
795 ictx->migrate_target == false) {
796 /* This is the nascent context for us,
797 * so abort the fetch */
798 llcache_handle_abort(ictx->llcache);
799 llcache_handle_release(ictx->llcache);
800 /* Remove us from the ring */
801 RING_REMOVE(hlcache->retrieval_ctx_ring, ictx);
802 /* Throw us away */
803 free((char *) ictx->child.charset);
804 free(ictx);
805 /* And stop */
806 RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
807 ictx);
808 }
809 } RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
810
811 return NSERROR_OK;
812 }
813
814 c = entry->content;
815
816 if (content_count_users(c) > 1) {
817 /* We are not the only user of 'c' so clone it. */
818 struct content *clone = content_clone(c);
819
820 if (clone == NULL)
821 return NSERROR_NOMEM;
822
823 entry = calloc(sizeof(struct hlcache_entry), 1);
824
825 if (entry == NULL) {
826 content_destroy(clone);
827 return NSERROR_NOMEM;
828 }
829
830 if (content_add_user(clone,
831 hlcache_content_callback, handle) == false) {
832 content_destroy(clone);
833 free(entry);
834 return NSERROR_NOMEM;
835 }
836
837 content_remove_user(c, hlcache_content_callback, handle);
838
839 entry->content = clone;
840 handle->entry = entry;
841 entry->prev = NULL;
842 entry->next = hlcache->content_list;
843 if (hlcache->content_list != NULL)
844 hlcache->content_list->prev = entry;
845 hlcache->content_list = entry;
846
847 c = clone;
848 }
849
850 return content_abort(c);
851 }
852
853 /* See hlcache.h for documentation */
hlcache_handle_replace_callback(hlcache_handle * handle,hlcache_handle_callback cb,void * pw)854 nserror hlcache_handle_replace_callback(hlcache_handle *handle,
855 hlcache_handle_callback cb, void *pw)
856 {
857 handle->cb = cb;
858 handle->pw = pw;
859
860 return NSERROR_OK;
861 }
862
hlcache_handle_clone(hlcache_handle * handle,hlcache_handle ** result)863 nserror hlcache_handle_clone(hlcache_handle *handle, hlcache_handle **result)
864 {
865 *result = NULL;
866 return NSERROR_CLONE_FAILED;
867 }
868
869 /* See hlcache.h for documentation */
hlcache_handle_get_url(const hlcache_handle * handle)870 nsurl *hlcache_handle_get_url(const hlcache_handle *handle)
871 {
872 nsurl *result = NULL;
873
874 assert(handle != NULL);
875
876 if (handle->entry != NULL) {
877 result = content_get_url(handle->entry->content);
878 } else {
879 RING_ITERATE_START(struct hlcache_retrieval_ctx,
880 hlcache->retrieval_ctx_ring,
881 ictx) {
882 if (ictx->handle == handle) {
883 /* This is the nascent context for us */
884 result = llcache_handle_get_url(ictx->llcache);
885
886 /* And stop */
887 RING_ITERATE_STOP(hlcache->retrieval_ctx_ring,
888 ictx);
889 }
890 } RING_ITERATE_END(hlcache->retrieval_ctx_ring, ictx);
891 }
892
893 return result;
894 }
895