libyelp: Auto-reload Mallard documents
[yelp.git] / libyelp / yelp-document.c
blob83605ddff217289686afc6c70eeff53b10551889
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2003-2009 Shaun McCance <shaunm@gnome.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program 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 GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Shaun McCance <shaunm@gnome.org>
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
31 #include "yelp-debug.h"
32 #include "yelp-document.h"
33 #include "yelp-error.h"
34 #include "yelp-docbook-document.h"
35 #include "yelp-help-list.h"
36 #include "yelp-info-document.h"
37 #include "yelp-mallard-document.h"
38 #include "yelp-man-document.h"
39 #include "yelp-settings.h"
40 #include "yelp-simple-document.h"
41 #include "yelp-storage.h"
43 enum {
44 PROP_0,
45 PROP_URI,
46 PROP_INDEXED
49 typedef struct _Request Request;
50 struct _Request {
51 YelpDocument *document;
52 gchar *page_id;
53 GCancellable *cancellable;
54 YelpDocumentCallback callback;
55 gpointer user_data;
56 GError *error;
58 gint idle_funcs;
61 typedef struct _Hash Hash;
62 struct _Hash {
63 gpointer null;
64 GHashTable *hash;
65 GDestroyNotify destroy;
68 struct _YelpDocumentPriv {
69 GMutex *mutex;
71 GSList *reqs_all; /* Holds canonical refs, only free from here */
72 Hash *reqs_by_page_id; /* Indexed by page ID, contains GSList */
73 GSList *reqs_pending; /* List of requests that need a page */
75 GSList *reqs_search; /* Pending search requests, not in reqs_all */
76 gboolean indexed;
78 gchar *doc_uri;
80 /* Real page IDs map to themselves, so this list doubles
81 * as a list of all valid page IDs.
83 GHashTable *core_ids; /* Mapping of real IDs to themselves, for a set */
84 Hash *page_ids; /* Mapping of fragment IDs to real page IDs */
85 Hash *titles; /* Mapping of page IDs to titles */
86 Hash *descs; /* Mapping of page IDs to descs */
87 Hash *icons; /* Mapping of page IDs to icons */
88 Hash *mime_types; /* Mapping of page IDs to mime types */
89 Hash *contents; /* Mapping of page IDs to string content */
91 Hash *root_ids; /* Mapping of page IDs to "root page" IDs */
92 Hash *prev_ids; /* Mapping of page IDs to "previous page" IDs */
93 Hash *next_ids; /* Mapping of page IDs to "next page" IDs */
94 Hash *up_ids; /* Mapping of page IDs to "up page" IDs */
96 GError *idle_error;
99 G_DEFINE_TYPE (YelpDocument, yelp_document, G_TYPE_OBJECT);
101 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_DOCUMENT, YelpDocumentPriv))
103 static void yelp_document_class_init (YelpDocumentClass *klass);
104 static void yelp_document_init (YelpDocument *document);
105 static void yelp_document_dispose (GObject *object);
106 static void yelp_document_finalize (GObject *object);
107 static void document_get_property (GObject *object,
108 guint prop_id,
109 GValue *value,
110 GParamSpec *pspec);
111 static void document_set_property (GObject *object,
112 guint prop_id,
113 const GValue *value,
114 GParamSpec *pspec);
115 static gboolean document_request_page (YelpDocument *document,
116 const gchar *page_id,
117 GCancellable *cancellable,
118 YelpDocumentCallback callback,
119 gpointer user_data);
120 static void document_indexed (YelpDocument *document);
121 static const gchar * document_read_contents (YelpDocument *document,
122 const gchar *page_id);
123 static void document_finish_read (YelpDocument *document,
124 const gchar *contents);
125 static gchar * document_get_mime_type (YelpDocument *document,
126 const gchar *mime_type);
127 static void document_index (YelpDocument *document);
129 static Hash * hash_new (GDestroyNotify destroy);
130 static void hash_free (Hash *hash);
131 static gpointer hash_lookup (Hash *hash,
132 const gchar *key);
133 static void hash_replace (Hash *hash,
134 const gchar *key,
135 gpointer value);
136 static void hash_remove (Hash *hash,
137 const gchar *key);
138 static void hash_slist_insert (Hash *hash,
139 const gchar *key,
140 gpointer value);
141 static void hash_slist_remove (Hash *hash,
142 const gchar *key,
143 gpointer value);
145 static void request_cancel (GCancellable *cancellable,
146 Request *request);
147 static gboolean request_idle_contents (Request *request);
148 static gboolean request_idle_info (Request *request);
149 static gboolean request_idle_error (Request *request);
150 static gboolean request_try_free (Request *request);
151 static void request_free (Request *request);
153 static const gchar * str_ref (const gchar *str);
154 static void str_unref (const gchar *str);
156 GStaticMutex str_mutex = G_STATIC_MUTEX_INIT;
157 GHashTable *str_refs = NULL;
159 /******************************************************************************/
161 YelpDocument *
162 yelp_document_get_for_uri (YelpUri *uri)
164 static GHashTable *documents = NULL;
165 gchar *docuri = NULL;
166 gchar *page_id, *tmp;
167 YelpDocument *document = NULL;
169 if (documents == NULL)
170 documents = g_hash_table_new_full (g_str_hash, g_str_equal,
171 g_free, g_object_unref);
173 g_return_val_if_fail (yelp_uri_is_resolved (uri), NULL);
175 switch (yelp_uri_get_document_type (uri)) {
176 case YELP_URI_DOCUMENT_TYPE_TEXT:
177 case YELP_URI_DOCUMENT_TYPE_HTML:
178 case YELP_URI_DOCUMENT_TYPE_XHTML:
179 /* We use YelpSimpleDocument for these, which is a single-file
180 * responder. But the document URI may be set to the directory
181 * holding the file, to allow a directory of HTML files to act
182 * as a single document. So we cache these by a fuller URI.
184 docuri = yelp_uri_get_document_uri (uri);
185 page_id = yelp_uri_get_page_id (uri);
186 tmp = g_strconcat (docuri, "/", page_id, NULL);
187 g_free (docuri);
188 g_free (page_id);
189 docuri = tmp;
190 break;
191 case YELP_URI_DOCUMENT_TYPE_MAN:
192 /* The document URI for man pages is just man:, so we use the
193 * full canonical URI to look these up.
195 docuri = yelp_uri_get_canonical_uri (uri);
196 break;
197 default:
198 docuri = yelp_uri_get_document_uri (uri);
199 break;
202 if (docuri == NULL)
203 return NULL;
204 document = g_hash_table_lookup (documents, docuri);
206 if (document != NULL) {
207 g_free (docuri);
208 return g_object_ref (document);
211 switch (yelp_uri_get_document_type (uri)) {
212 case YELP_URI_DOCUMENT_TYPE_TEXT:
213 case YELP_URI_DOCUMENT_TYPE_HTML:
214 case YELP_URI_DOCUMENT_TYPE_XHTML:
215 document = yelp_simple_document_new (uri);
216 break;
217 case YELP_URI_DOCUMENT_TYPE_DOCBOOK:
218 document = yelp_docbook_document_new (uri);
219 break;
220 case YELP_URI_DOCUMENT_TYPE_MALLARD:
221 document = yelp_mallard_document_new (uri);
222 break;
223 case YELP_URI_DOCUMENT_TYPE_MAN:
224 document = yelp_man_document_new (uri);
225 break;
226 case YELP_URI_DOCUMENT_TYPE_INFO:
227 document = yelp_info_document_new (uri);
228 break;
229 case YELP_URI_DOCUMENT_TYPE_HELP_LIST:
230 document = yelp_help_list_new (uri);
231 break;
232 case YELP_URI_DOCUMENT_TYPE_NOT_FOUND:
233 case YELP_URI_DOCUMENT_TYPE_EXTERNAL:
234 case YELP_URI_DOCUMENT_TYPE_ERROR:
235 break;
238 if (document != NULL) {
239 g_hash_table_insert (documents, docuri, document);
240 return g_object_ref (document);
243 g_free (docuri);
244 return NULL;
247 /******************************************************************************/
249 static void
250 yelp_document_class_init (YelpDocumentClass *klass)
252 GObjectClass *object_class = G_OBJECT_CLASS (klass);
254 object_class->dispose = yelp_document_dispose;
255 object_class->finalize = yelp_document_finalize;
256 object_class->get_property = document_get_property;
257 object_class->set_property = document_set_property;
259 klass->request_page = document_request_page;
260 klass->read_contents = document_read_contents;
261 klass->finish_read = document_finish_read;
262 klass->get_mime_type = document_get_mime_type;
263 klass->index = document_index;
265 g_object_class_install_property (object_class,
266 PROP_INDEXED,
267 g_param_spec_boolean ("indexed",
268 N_("Indexed"),
269 N_("Whether the document content has been indexed"),
270 FALSE,
271 G_PARAM_CONSTRUCT | G_PARAM_READWRITE |
272 G_PARAM_STATIC_STRINGS));
274 g_object_class_install_property (object_class,
275 PROP_URI,
276 g_param_spec_string ("document-uri",
277 N_("Document URI"),
278 N_("The URI which identifies the document"),
279 NULL,
280 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
281 G_PARAM_STATIC_STRINGS));
283 g_type_class_add_private (klass, sizeof (YelpDocumentPriv));
286 static void
287 yelp_document_init (YelpDocument *document)
289 YelpDocumentPriv *priv;
291 document->priv = priv = GET_PRIV (document);
293 priv->mutex = g_mutex_new ();
295 priv->reqs_by_page_id = hash_new ((GDestroyNotify) g_slist_free);
296 priv->reqs_all = NULL;
297 priv->reqs_pending = NULL;
298 priv->reqs_search = NULL;
300 priv->core_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
301 priv->page_ids = hash_new (g_free );
302 priv->titles = hash_new (g_free);
303 priv->descs = hash_new (g_free);
304 priv->icons = hash_new (g_free);
305 priv->mime_types = hash_new (g_free);
306 priv->contents = hash_new ((GDestroyNotify) str_unref);
308 priv->root_ids = hash_new (g_free);
309 priv->prev_ids = hash_new (g_free);
310 priv->next_ids = hash_new (g_free);
311 priv->up_ids = hash_new (g_free);
314 static void
315 yelp_document_dispose (GObject *object)
317 YelpDocument *document = YELP_DOCUMENT (object);
319 if (document->priv->reqs_all) {
320 g_slist_foreach (document->priv->reqs_all,
321 (GFunc) request_try_free,
322 NULL);
323 g_slist_free (document->priv->reqs_all);
324 document->priv->reqs_all = NULL;
327 if (document->priv->reqs_search) {
328 g_slist_foreach (document->priv->reqs_search,
329 (GFunc) request_try_free,
330 NULL);
331 g_slist_free (document->priv->reqs_search);
332 document->priv->reqs_search = NULL;
335 G_OBJECT_CLASS (yelp_document_parent_class)->dispose (object);
338 static void
339 yelp_document_finalize (GObject *object)
341 YelpDocument *document = YELP_DOCUMENT (object);
343 g_slist_free (document->priv->reqs_pending);
344 hash_free (document->priv->reqs_by_page_id);
346 hash_free (document->priv->page_ids);
347 hash_free (document->priv->titles);
348 hash_free (document->priv->descs);
349 hash_free (document->priv->icons);
350 hash_free (document->priv->mime_types);
352 hash_free (document->priv->contents);
354 hash_free (document->priv->root_ids);
355 hash_free (document->priv->prev_ids);
356 hash_free (document->priv->next_ids);
357 hash_free (document->priv->up_ids);
359 g_hash_table_destroy (document->priv->core_ids);
361 g_mutex_free (document->priv->mutex);
363 G_OBJECT_CLASS (yelp_document_parent_class)->finalize (object);
366 static void
367 document_get_property (GObject *object,
368 guint prop_id,
369 GValue *value,
370 GParamSpec *pspec)
372 YelpDocument *document = YELP_DOCUMENT (object);
374 switch (prop_id) {
375 case PROP_INDEXED:
376 g_value_set_boolean (value, document->priv->indexed);
377 break;
378 case PROP_URI:
379 g_value_set_string (value, document->priv->doc_uri);
380 break;
381 default:
382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
383 break;
387 static void
388 document_set_property (GObject *object,
389 guint prop_id,
390 const GValue *value,
391 GParamSpec *pspec)
393 YelpDocument *document = YELP_DOCUMENT (object);
395 switch (prop_id) {
396 case PROP_INDEXED:
397 document->priv->indexed = g_value_get_boolean (value);
398 if (document->priv->indexed)
399 g_idle_add ((GSourceFunc) document_indexed, document);
400 break;
401 case PROP_URI:
402 if (document->priv->doc_uri != NULL)
403 g_free (document->priv->doc_uri);
404 document->priv->doc_uri = g_value_dup_string (value);
405 break;
406 default:
407 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
408 break;
412 /******************************************************************************/
414 gchar **
415 yelp_document_list_page_ids (YelpDocument *document)
417 GList *lst, *cur;
418 gint i;
419 gchar **ret = NULL;
421 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
423 g_mutex_lock (document->priv->mutex);
425 lst = g_hash_table_get_keys (document->priv->core_ids);
426 ret = g_new0 (gchar *, g_list_length (lst) + 1);
427 i = 0;
428 for (cur = lst; cur != NULL; cur = cur->next) {
429 ret[i] = g_strdup ((gchar *) cur->data);
430 i++;
432 g_list_free (lst);
434 g_mutex_unlock (document->priv->mutex);
436 return ret;
439 gchar *
440 yelp_document_get_page_id (YelpDocument *document,
441 const gchar *id)
443 gchar *ret = NULL;
445 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
447 if (id != NULL && g_str_has_prefix (id, "search="))
448 return g_strdup (id);
450 g_mutex_lock (document->priv->mutex);
451 ret = hash_lookup (document->priv->page_ids, id);
452 if (ret)
453 ret = g_strdup (ret);
455 g_mutex_unlock (document->priv->mutex);
457 return ret;
460 void
461 yelp_document_set_page_id (YelpDocument *document,
462 const gchar *id,
463 const gchar *page_id)
465 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
467 g_mutex_lock (document->priv->mutex);
469 hash_replace (document->priv->page_ids, id, g_strdup (page_id));
471 if (id == NULL || !g_str_equal (id, page_id)) {
472 GSList *reqs, *cur;
473 reqs = hash_lookup (document->priv->reqs_by_page_id, id);
474 for (cur = reqs; cur != NULL; cur = cur->next) {
475 Request *request = (Request *) cur->data;
476 if (request == NULL)
477 continue;
478 g_free (request->page_id);
479 request->page_id = g_strdup (page_id);
480 hash_slist_insert (document->priv->reqs_by_page_id, page_id, request);
482 if (reqs) {
483 hash_remove (document->priv->reqs_by_page_id, id);
487 if (page_id != NULL) {
488 if (g_hash_table_lookup (document->priv->core_ids, page_id) == NULL) {
489 gchar *ins = g_strdup (page_id);
490 g_hash_table_insert (document->priv->core_ids, ins, ins);
494 g_mutex_unlock (document->priv->mutex);
497 gchar *
498 yelp_document_get_root_id (YelpDocument *document,
499 const gchar *page_id)
501 gchar *real, *ret = NULL;
503 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
505 g_mutex_lock (document->priv->mutex);
506 if (page_id != NULL && g_str_has_prefix (page_id, "search="))
507 real = hash_lookup (document->priv->page_ids, NULL);
508 else
509 real = hash_lookup (document->priv->page_ids, page_id);
510 if (real) {
511 ret = hash_lookup (document->priv->root_ids, real);
512 if (ret)
513 ret = g_strdup (ret);
515 g_mutex_unlock (document->priv->mutex);
517 return ret;
520 void
521 yelp_document_set_root_id (YelpDocument *document,
522 const gchar *page_id,
523 const gchar *root_id)
525 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
527 g_mutex_lock (document->priv->mutex);
528 hash_replace (document->priv->root_ids, page_id, g_strdup (root_id));
529 g_mutex_unlock (document->priv->mutex);
532 gchar *
533 yelp_document_get_prev_id (YelpDocument *document,
534 const gchar *page_id)
536 gchar *real, *ret = NULL;
538 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
540 g_mutex_lock (document->priv->mutex);
541 real = hash_lookup (document->priv->page_ids, page_id);
542 if (real) {
543 ret = hash_lookup (document->priv->prev_ids, real);
544 if (ret)
545 ret = g_strdup (ret);
547 g_mutex_unlock (document->priv->mutex);
549 return ret;
552 void
553 yelp_document_set_prev_id (YelpDocument *document,
554 const gchar *page_id,
555 const gchar *prev_id)
557 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
559 g_mutex_lock (document->priv->mutex);
560 hash_replace (document->priv->prev_ids, page_id, g_strdup (prev_id));
561 g_mutex_unlock (document->priv->mutex);
564 gchar *
565 yelp_document_get_next_id (YelpDocument *document,
566 const gchar *page_id)
568 gchar *real, *ret = NULL;
570 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
572 g_mutex_lock (document->priv->mutex);
573 real = hash_lookup (document->priv->page_ids, page_id);
574 if (real) {
575 ret = hash_lookup (document->priv->next_ids, real);
576 if (ret)
577 ret = g_strdup (ret);
579 g_mutex_unlock (document->priv->mutex);
581 return ret;
584 void
585 yelp_document_set_next_id (YelpDocument *document,
586 const gchar *page_id,
587 const gchar *next_id)
589 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
591 g_mutex_lock (document->priv->mutex);
592 hash_replace (document->priv->next_ids, page_id, g_strdup (next_id));
593 g_mutex_unlock (document->priv->mutex);
596 gchar *
597 yelp_document_get_up_id (YelpDocument *document,
598 const gchar *page_id)
600 gchar *real, *ret = NULL;
602 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
604 g_mutex_lock (document->priv->mutex);
605 real = hash_lookup (document->priv->page_ids, page_id);
606 if (real) {
607 ret = hash_lookup (document->priv->up_ids, real);
608 if (ret)
609 ret = g_strdup (ret);
611 g_mutex_unlock (document->priv->mutex);
613 return ret;
616 void
617 yelp_document_set_up_id (YelpDocument *document,
618 const gchar *page_id,
619 const gchar *up_id)
621 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
623 g_mutex_lock (document->priv->mutex);
624 hash_replace (document->priv->up_ids, page_id, g_strdup (up_id));
625 g_mutex_unlock (document->priv->mutex);
628 gchar *
629 yelp_document_get_root_title (YelpDocument *document,
630 const gchar *page_id)
632 gchar *real, *root, *ret = NULL;
634 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
636 g_mutex_lock (document->priv->mutex);
637 if (page_id != NULL && g_str_has_prefix (page_id, "search=")) {
638 ret = yelp_storage_get_root_title (yelp_storage_get_default (),
639 document->priv->doc_uri);
641 else {
642 real = hash_lookup (document->priv->page_ids, page_id);
643 if (real) {
644 root = hash_lookup (document->priv->root_ids, real);
645 if (root) {
646 ret = hash_lookup (document->priv->titles, root);
647 if (ret)
648 ret = g_strdup (ret);
653 g_mutex_unlock (document->priv->mutex);
655 return ret;
658 gchar *
659 yelp_document_get_page_title (YelpDocument *document,
660 const gchar *page_id)
662 gchar *real, *ret = NULL;
664 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
666 if (page_id != NULL && g_str_has_prefix (page_id, "search=")) {
667 ret = g_uri_unescape_string (page_id + 7, NULL);
668 return ret;
671 g_mutex_lock (document->priv->mutex);
672 real = hash_lookup (document->priv->page_ids, page_id);
673 if (real) {
674 ret = hash_lookup (document->priv->titles, real);
675 if (ret)
676 ret = g_strdup (ret);
678 g_mutex_unlock (document->priv->mutex);
680 return ret;
683 void
684 yelp_document_set_page_title (YelpDocument *document,
685 const gchar *page_id,
686 const gchar *title)
688 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
690 g_mutex_lock (document->priv->mutex);
691 hash_replace (document->priv->titles, page_id, g_strdup (title));
692 g_mutex_unlock (document->priv->mutex);
695 gchar *
696 yelp_document_get_page_desc (YelpDocument *document,
697 const gchar *page_id)
699 gchar *real, *ret = NULL;
701 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
703 if (page_id != NULL && g_str_has_prefix (page_id, "search="))
704 return yelp_document_get_root_title (document, page_id);
706 g_mutex_lock (document->priv->mutex);
707 real = hash_lookup (document->priv->page_ids, page_id);
708 if (real) {
709 ret = hash_lookup (document->priv->descs, real);
710 if (ret)
711 ret = g_strdup (ret);
713 g_mutex_unlock (document->priv->mutex);
715 return ret;
718 void
719 yelp_document_set_page_desc (YelpDocument *document,
720 const gchar *page_id,
721 const gchar *desc)
723 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
725 g_mutex_lock (document->priv->mutex);
726 hash_replace (document->priv->descs, page_id, g_strdup (desc));
727 g_mutex_unlock (document->priv->mutex);
730 gchar *
731 yelp_document_get_page_icon (YelpDocument *document,
732 const gchar *page_id)
734 gchar *real, *ret = NULL;
736 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
738 if (page_id != NULL && g_str_has_prefix (page_id, "search="))
739 return g_strdup ("yelp-page-search-symbolic");
741 g_mutex_lock (document->priv->mutex);
742 real = hash_lookup (document->priv->page_ids, page_id);
743 if (real) {
744 ret = hash_lookup (document->priv->icons, real);
745 if (ret)
746 ret = g_strdup (ret);
748 g_mutex_unlock (document->priv->mutex);
750 if (ret == NULL)
751 ret = g_strdup ("yelp-page-symbolic");
753 return ret;
756 void
757 yelp_document_set_page_icon (YelpDocument *document,
758 const gchar *page_id,
759 const gchar *icon)
761 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
763 g_mutex_lock (document->priv->mutex);
764 hash_replace (document->priv->icons, page_id, g_strdup (icon));
765 g_mutex_unlock (document->priv->mutex);
768 static void
769 document_indexed (YelpDocument *document)
771 g_mutex_lock (document->priv->mutex);
772 while (document->priv->reqs_search != NULL) {
773 Request *request = (Request *) document->priv->reqs_search->data;
774 request->idle_funcs++;
775 g_idle_add ((GSourceFunc) request_idle_info, request);
776 g_idle_add ((GSourceFunc) request_idle_contents, request);
777 document->priv->reqs_search = g_slist_delete_link (document->priv->reqs_search,
778 document->priv->reqs_search);
780 g_mutex_unlock (document->priv->mutex);
783 /******************************************************************************/
785 gboolean
786 yelp_document_request_page (YelpDocument *document,
787 const gchar *page_id,
788 GCancellable *cancellable,
789 YelpDocumentCallback callback,
790 gpointer user_data)
792 g_return_val_if_fail (YELP_IS_DOCUMENT (document), FALSE);
793 g_return_val_if_fail (YELP_DOCUMENT_GET_CLASS (document)->request_page != NULL, FALSE);
795 debug_print (DB_FUNCTION, "entering\n");
797 return YELP_DOCUMENT_GET_CLASS (document)->request_page (document,
798 page_id,
799 cancellable,
800 callback,
801 user_data);
804 static gboolean
805 document_request_page (YelpDocument *document,
806 const gchar *page_id,
807 GCancellable *cancellable,
808 YelpDocumentCallback callback,
809 gpointer user_data)
811 Request *request;
812 gchar *real_id;
813 gboolean ret = FALSE;
815 request = g_slice_new0 (Request);
816 request->document = g_object_ref (document);
818 real_id = hash_lookup (document->priv->page_ids, page_id);
819 if (real_id)
820 request->page_id = g_strdup (real_id);
821 else
822 request->page_id = g_strdup (page_id);
824 request->cancellable = g_object_ref (cancellable);
825 g_signal_connect (cancellable, "cancelled",
826 G_CALLBACK (request_cancel), request);
828 request->callback = callback;
829 request->user_data = user_data;
830 request->idle_funcs = 0;
832 g_mutex_lock (document->priv->mutex);
834 if (g_str_has_prefix (page_id, "search=")) {
835 document->priv->reqs_search = g_slist_prepend (document->priv->reqs_search, request);
836 if (document->priv->indexed)
837 g_idle_add ((GSourceFunc) document_indexed, document);
838 else
839 yelp_document_index (document);
840 g_mutex_unlock (document->priv->mutex);
841 return TRUE;
844 hash_slist_insert (document->priv->reqs_by_page_id,
845 request->page_id,
846 request);
848 document->priv->reqs_all = g_slist_prepend (document->priv->reqs_all, request);
849 document->priv->reqs_pending = g_slist_prepend (document->priv->reqs_pending, request);
851 if (hash_lookup (document->priv->titles, request->page_id)) {
852 request->idle_funcs++;
853 g_idle_add ((GSourceFunc) request_idle_info, request);
856 if (hash_lookup (document->priv->contents, request->page_id)) {
857 request->idle_funcs++;
858 g_idle_add ((GSourceFunc) request_idle_contents, request);
859 ret = TRUE;
862 g_mutex_unlock (document->priv->mutex);
864 return ret;
867 void
868 yelp_document_clear_contents (YelpDocument *document)
870 g_mutex_lock (document->priv->mutex);
872 if (document->priv->contents->null) {
873 str_unref (document->priv->contents->null);
874 document->priv->contents->null = NULL;
876 g_hash_table_remove_all (document->priv->contents->hash);
878 g_mutex_unlock (document->priv->mutex);
881 gchar **
882 yelp_document_get_requests (YelpDocument *document)
884 GList *reqs, *cur;
885 gchar **ret;
886 gint i;
888 g_mutex_lock (document->priv->mutex);
890 reqs = g_hash_table_get_keys (document->priv->reqs_by_page_id->hash);
891 ret = g_new0 (gchar*, g_list_length (reqs) + 1);
892 for (cur = reqs, i = 0; cur; cur = cur->next, i++) {
893 ret[i] = g_strdup ((gchar *) cur->data);
895 g_list_free (reqs);
897 g_mutex_unlock (document->priv->mutex);
899 return ret;
902 /******************************************************************************/
904 const gchar *
905 yelp_document_read_contents (YelpDocument *document,
906 const gchar *page_id)
908 g_return_val_if_fail (YELP_IS_DOCUMENT (document), NULL);
909 g_return_val_if_fail (YELP_DOCUMENT_GET_CLASS (document)->read_contents != NULL, NULL);
911 return YELP_DOCUMENT_GET_CLASS (document)->read_contents (document, page_id);
914 static const gchar *
915 document_read_contents (YelpDocument *document,
916 const gchar *page_id)
918 gchar *real, *str, **colors;
920 g_mutex_lock (document->priv->mutex);
922 if (page_id != NULL && g_str_has_prefix (page_id, "search=")) {
923 gchar *tmp, *tmp2, *txt;
924 GVariant *value;
925 GVariantIter *iter;
926 gchar *url, *title, *desc, *icon; /* do not free */
927 gchar *index_title;
928 GString *ret = g_string_new ("<html><head><style type='text/css'>");
930 colors = yelp_settings_get_colors (yelp_settings_get_default ());
931 g_string_append_printf (ret,
932 "html { height: 100%; } "
933 "body { margin: 0; padding: 0;"
934 " background-color: %s; color: %s;"
935 " direction: %s; } "
936 "div.header { margin-bottom: 1em; } "
937 "div.trails { "
938 " margin: 0; padding: 0.2em 12px 0 12px;"
939 " background-color: %s;"
940 " border-bottom: solid 1px %s; } "
941 "div.trail { text-indent: -1em;"
942 " margin: 0 1em 0.2em 1em; padding: 0; color: %s; } "
943 "div.body { margin: 0 12px 0 12px; padding: 0 0 12px 0; max-width: 60em; } "
944 "div, p { margin: 1em 0 0 0; padding: 0; } "
945 "div:first-child, p:first-child { margin-top: 0; } "
946 "h1 { margin: 0; padding: 0; color: %s; font-size: 1.44em; } "
947 "a { color: %s; text-decoration: none; } "
948 "a.linkdiv { display: block; } "
949 "div.linkdiv { margin: 0; padding: 0.5em; }"
950 "a:hover div.linkdiv {"
951 " outline: solid 1px %s;"
952 " background: -webkit-gradient(linear, left top, left 80, from(%s), to(%s)); } "
953 "div.title { margin-bottom: 0.2em; font-weight: bold; } "
954 "div.desc { margin: 0; color: %s; } "
955 "</style></head><body><div class='header'>",
956 colors[YELP_SETTINGS_COLOR_BASE],
957 colors[YELP_SETTINGS_COLOR_TEXT],
958 (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL ? "rtl" : "ltr"),
959 colors[YELP_SETTINGS_COLOR_GRAY_BASE],
960 colors[YELP_SETTINGS_COLOR_GRAY_BORDER],
961 colors[YELP_SETTINGS_COLOR_TEXT_LIGHT],
962 colors[YELP_SETTINGS_COLOR_TEXT_LIGHT],
963 colors[YELP_SETTINGS_COLOR_LINK],
964 colors[YELP_SETTINGS_COLOR_BLUE_BASE],
965 colors[YELP_SETTINGS_COLOR_BLUE_BASE],
966 colors[YELP_SETTINGS_COLOR_BASE],
967 colors[YELP_SETTINGS_COLOR_TEXT_LIGHT]
970 index_title = yelp_storage_get_root_title (yelp_storage_get_default (),
971 document->priv->doc_uri);
972 if (index_title != NULL) {
973 tmp = g_markup_printf_escaped ("<div class='trails'><div class='trail'>"
974 "<a href='xref:'>%s</a>&#x00A0;%s "
975 "</div></div>",
976 index_title,
977 (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL ? "«" : "»")
979 g_string_append (ret, tmp);
980 g_free (tmp);
983 g_string_append (ret, "</div><div class='body'>");
984 g_strfreev (colors);
986 str = hash_lookup (document->priv->contents, real);
987 if (str) {
988 str_ref (str);
989 g_mutex_unlock (document->priv->mutex);
990 return (const gchar *) str;
993 txt = g_uri_unescape_string (page_id + 7, NULL);
994 tmp2 = g_strdup_printf (_("Search results for “%s”"), txt);
995 tmp = g_markup_printf_escaped ("<h1>%s</h1>", tmp2);
996 g_string_append (ret, tmp);
997 g_free (tmp2);
998 g_free (tmp);
1000 value = yelp_storage_search (yelp_storage_get_default (),
1001 document->priv->doc_uri,
1002 txt);
1003 iter = g_variant_iter_new (value);
1004 if (g_variant_iter_n_children (iter) == 0) {
1005 if (index_title != NULL) {
1006 gchar *t = g_strdup_printf (_("No matching help pages found in “%s”."), index_title);
1007 tmp = g_markup_printf_escaped ("<p>%s</p>", t);
1008 g_free (t);
1010 else {
1011 tmp = g_markup_printf_escaped ("<p>%s</p>",
1012 _("No matching help pages found."));
1014 g_string_append (ret, tmp);
1015 g_free (tmp);
1017 else {
1018 while (g_variant_iter_loop (iter, "(&s&s&s&s)", &url, &title, &desc, &icon)) {
1019 tmp = g_markup_printf_escaped ("<div><a class='linkdiv' href='%s'><div class='linkdiv'>"
1020 "<div class='title'>%s</div>"
1021 "<div class='desc'>%s</div>"
1022 "</div></a></div>",
1023 url, title, desc);
1024 g_string_append (ret, tmp);
1025 g_free (tmp);
1028 g_variant_iter_free (iter);
1029 g_variant_unref (value);
1031 if (index_title != NULL)
1032 g_free (index_title);
1033 g_free (txt);
1034 g_string_append (ret, "</div></body></html>");
1035 g_mutex_unlock (document->priv->mutex);
1037 hash_replace (document->priv->contents, page_id, g_string_free (ret, FALSE));
1038 str = hash_lookup (document->priv->contents, page_id);
1039 str_ref (str);
1040 g_mutex_unlock (document->priv->mutex);
1041 return (const gchar *) str;
1044 real = hash_lookup (document->priv->page_ids, page_id);
1045 str = hash_lookup (document->priv->contents, real);
1046 if (str)
1047 str_ref (str);
1049 g_mutex_unlock (document->priv->mutex);
1051 return (const gchar *) str;
1054 void
1055 yelp_document_finish_read (YelpDocument *document,
1056 const gchar *contents)
1058 g_return_if_fail (YELP_IS_DOCUMENT (document));
1059 g_return_if_fail (YELP_DOCUMENT_GET_CLASS (document)->finish_read != NULL);
1061 YELP_DOCUMENT_GET_CLASS (document)->finish_read (document, contents);
1064 static void
1065 document_finish_read (YelpDocument *document,
1066 const gchar *contents)
1068 str_unref (contents);
1071 void
1072 yelp_document_give_contents (YelpDocument *document,
1073 const gchar *page_id,
1074 gchar *contents,
1075 const gchar *mime)
1077 g_return_if_fail (YELP_IS_DOCUMENT (document));
1079 debug_print (DB_FUNCTION, "entering\n");
1080 debug_print (DB_ARG, " page_id = \"%s\"\n", page_id);
1082 g_mutex_lock (document->priv->mutex);
1084 hash_replace (document->priv->contents,
1085 page_id,
1086 (gpointer) str_ref (contents));
1088 hash_replace (document->priv->mime_types,
1089 page_id,
1090 g_strdup (mime));
1092 g_mutex_unlock (document->priv->mutex);
1095 gchar *
1096 yelp_document_get_mime_type (YelpDocument *document,
1097 const gchar *page_id)
1099 g_return_val_if_fail (YELP_IS_DOCUMENT (document), NULL);
1100 g_return_val_if_fail (YELP_DOCUMENT_GET_CLASS (document)->get_mime_type != NULL, NULL);
1102 return YELP_DOCUMENT_GET_CLASS (document)->get_mime_type (document, page_id);
1105 static gchar *
1106 document_get_mime_type (YelpDocument *document,
1107 const gchar *page_id)
1109 gchar *real, *ret = NULL;
1111 g_mutex_lock (document->priv->mutex);
1112 real = hash_lookup (document->priv->page_ids, page_id);
1113 if (real) {
1114 ret = hash_lookup (document->priv->mime_types, real);
1115 if (ret)
1116 ret = g_strdup (ret);
1118 g_mutex_unlock (document->priv->mutex);
1120 return ret;
1123 /******************************************************************************/
1125 void
1126 yelp_document_index (YelpDocument *document)
1128 g_return_if_fail (YELP_IS_DOCUMENT (document));
1129 g_return_if_fail (YELP_DOCUMENT_GET_CLASS (document)->index != NULL);
1131 YELP_DOCUMENT_GET_CLASS (document)->index (document);
1134 static void
1135 document_index (YelpDocument *document)
1137 g_object_set (document, "indexed", TRUE, NULL);
1140 /******************************************************************************/
1142 void
1143 yelp_document_signal (YelpDocument *document,
1144 const gchar *page_id,
1145 YelpDocumentSignal signal,
1146 const GError *error)
1148 GSList *reqs, *cur;
1150 g_return_if_fail (YELP_IS_DOCUMENT (document));
1152 g_mutex_lock (document->priv->mutex);
1154 reqs = hash_lookup (document->priv->reqs_by_page_id, page_id);
1155 for (cur = reqs; cur != NULL; cur = cur->next) {
1156 Request *request = (Request *) cur->data;
1157 if (!request)
1158 continue;
1159 switch (signal) {
1160 case YELP_DOCUMENT_SIGNAL_CONTENTS:
1161 request->idle_funcs++;
1162 g_idle_add ((GSourceFunc) request_idle_contents, request);
1163 break;
1164 case YELP_DOCUMENT_SIGNAL_INFO:
1165 request->idle_funcs++;
1166 g_idle_add ((GSourceFunc) request_idle_info, request);
1167 break;
1168 case YELP_DOCUMENT_SIGNAL_ERROR:
1169 request->idle_funcs++;
1170 request->error = yelp_error_copy ((GError *) error);
1171 g_idle_add ((GSourceFunc) request_idle_error, request);
1172 break;
1173 default:
1174 break;
1178 g_mutex_unlock (document->priv->mutex);
1181 static gboolean
1182 yelp_document_error_pending_idle (YelpDocument *document)
1184 YelpDocumentPriv *priv = GET_PRIV (document);
1185 GSList *cur;
1186 Request *request;
1188 g_mutex_lock (priv->mutex);
1190 if (priv->reqs_pending) {
1191 for (cur = priv->reqs_pending; cur; cur = cur->next) {
1192 request = cur->data;
1193 request->error = yelp_error_copy ((GError *) priv->idle_error);
1194 request->idle_funcs++;
1195 g_idle_add ((GSourceFunc) request_idle_error, request);
1198 g_slist_free (priv->reqs_pending);
1199 priv->reqs_pending = NULL;
1202 g_mutex_unlock (priv->mutex);
1204 g_object_unref (document);
1205 return FALSE;
1208 void
1209 yelp_document_error_pending (YelpDocument *document,
1210 const GError *error)
1212 YelpDocumentPriv *priv = GET_PRIV (document);
1214 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
1216 g_object_ref (document);
1217 priv->idle_error = g_error_copy (error);
1218 g_idle_add ((GSourceFunc) yelp_document_error_pending_idle, document);
1221 /******************************************************************************/
1223 static Hash *
1224 hash_new (GDestroyNotify destroy)
1226 Hash *hash = g_new0 (Hash, 1);
1227 hash->destroy = destroy;
1228 hash->hash = g_hash_table_new_full (g_str_hash, g_str_equal,
1229 g_free, destroy);
1230 return hash;
1233 static void
1234 hash_free (Hash *hash)
1236 if (hash->null)
1237 hash->destroy (hash->null);
1238 g_hash_table_destroy (hash->hash);
1239 g_free (hash);
1242 static gpointer
1243 hash_lookup (Hash *hash, const gchar *key)
1245 if (key == NULL)
1246 return hash->null;
1247 else
1248 return g_hash_table_lookup (hash->hash, key);
1251 static void
1252 hash_replace (Hash *hash,
1253 const gchar *key,
1254 gpointer value)
1256 if (key == NULL) {
1257 if (hash->null)
1258 hash->destroy (hash->null);
1259 hash->null = value;
1261 else
1262 g_hash_table_replace (hash->hash, g_strdup (key), value);
1265 static void
1266 hash_remove (Hash *hash,
1267 const gchar *key)
1269 if (key == NULL) {
1270 if (hash->null) {
1271 hash->destroy (hash->null);
1272 hash->null = NULL;
1275 else
1276 g_hash_table_remove (hash->hash, key);
1279 static void
1280 hash_slist_insert (Hash *hash,
1281 const gchar *key,
1282 gpointer value)
1284 GSList *list;
1285 list = hash_lookup (hash, key);
1286 if (list) {
1287 list->next = g_slist_prepend (list->next, value);
1288 } else {
1289 list = g_slist_prepend (NULL, value);
1290 list = g_slist_prepend (list, NULL);
1291 if (key == NULL)
1292 hash->null = list;
1293 else
1294 g_hash_table_insert (hash->hash, g_strdup (key), list);
1298 static void
1299 hash_slist_remove (Hash *hash,
1300 const gchar *key,
1301 gpointer value)
1303 GSList *list;
1304 list = hash_lookup (hash, key);
1305 if (list) {
1306 list = g_slist_remove (list, value);
1307 if (list->next == NULL)
1308 hash_remove (hash, key);
1312 /******************************************************************************/
1314 static void
1315 request_cancel (GCancellable *cancellable, Request *request)
1317 GSList *cur;
1318 YelpDocument *document = request->document;
1319 gboolean found = FALSE;
1321 g_assert (document != NULL && YELP_IS_DOCUMENT (document));
1323 g_mutex_lock (document->priv->mutex);
1325 document->priv->reqs_pending = g_slist_remove (document->priv->reqs_pending,
1326 (gconstpointer) request);
1327 hash_slist_remove (document->priv->reqs_by_page_id,
1328 request->page_id,
1329 request);
1330 for (cur = document->priv->reqs_all; cur != NULL; cur = cur->next) {
1331 if (cur->data == request) {
1332 document->priv->reqs_all = g_slist_delete_link (document->priv->reqs_all, cur);
1333 found = TRUE;
1334 break;
1337 if (!found) {
1338 for (cur = document->priv->reqs_search; cur != NULL; cur = cur->next) {
1339 if (cur->data == request) {
1340 document->priv->reqs_search = g_slist_delete_link (document->priv->reqs_search, cur);
1341 break;
1345 request_try_free (request);
1347 g_mutex_unlock (document->priv->mutex);
1350 static gboolean
1351 request_idle_contents (Request *request)
1353 YelpDocument *document;
1354 YelpDocumentPriv *priv;
1355 YelpDocumentCallback callback = NULL;
1356 gpointer user_data = user_data;
1358 g_assert (request != NULL && YELP_IS_DOCUMENT (request->document));
1360 if (g_cancellable_is_cancelled (request->cancellable)) {
1361 request->idle_funcs--;
1362 return FALSE;
1365 document = g_object_ref (request->document);
1366 priv = GET_PRIV (document);
1368 g_mutex_lock (document->priv->mutex);
1370 priv->reqs_pending = g_slist_remove (priv->reqs_pending, request);
1372 callback = request->callback;
1373 user_data = request->user_data;
1374 request->idle_funcs--;
1376 g_mutex_unlock (document->priv->mutex);
1378 if (callback)
1379 callback (document, YELP_DOCUMENT_SIGNAL_CONTENTS, user_data, NULL);
1381 g_object_unref (document);
1382 return FALSE;
1385 static gboolean
1386 request_idle_info (Request *request)
1388 YelpDocument *document;
1389 YelpDocumentCallback callback = NULL;
1390 gpointer user_data;
1392 g_assert (request != NULL && YELP_IS_DOCUMENT (request->document));
1394 if (g_cancellable_is_cancelled (request->cancellable)) {
1395 request->idle_funcs--;
1396 return FALSE;
1399 document = g_object_ref (request->document);
1401 g_mutex_lock (document->priv->mutex);
1403 callback = request->callback;
1404 user_data = request->user_data;
1405 request->idle_funcs--;
1407 g_mutex_unlock (document->priv->mutex);
1409 if (callback)
1410 callback (document, YELP_DOCUMENT_SIGNAL_INFO, user_data, NULL);
1412 g_object_unref (document);
1413 return FALSE;
1416 static gboolean
1417 request_idle_error (Request *request)
1419 YelpDocument *document;
1420 YelpDocumentPriv *priv;
1421 YelpDocumentCallback callback = NULL;
1422 GError *error = NULL;
1423 gpointer user_data = user_data;
1425 g_assert (request != NULL && YELP_IS_DOCUMENT (request->document));
1427 if (g_cancellable_is_cancelled (request->cancellable)) {
1428 request->idle_funcs--;
1429 return FALSE;
1432 document = g_object_ref (request->document);
1433 priv = GET_PRIV (document);
1435 g_mutex_lock (priv->mutex);
1437 if (request->error) {
1438 callback = request->callback;
1439 user_data = request->user_data;
1440 error = request->error;
1441 request->error = NULL;
1442 priv->reqs_pending = g_slist_remove (priv->reqs_pending, request);
1445 request->idle_funcs--;
1446 g_mutex_unlock (priv->mutex);
1448 if (callback)
1449 callback (document,
1450 YELP_DOCUMENT_SIGNAL_ERROR,
1451 user_data,
1452 error);
1454 g_object_unref (document);
1455 return FALSE;
1458 static gboolean
1459 request_try_free (Request *request)
1461 if (!g_cancellable_is_cancelled (request->cancellable))
1462 g_cancellable_cancel (request->cancellable);
1464 if (request->idle_funcs == 0)
1465 request_free (request);
1466 else
1467 g_idle_add ((GSourceFunc) request_try_free, request);
1469 return FALSE;
1472 static void
1473 request_free (Request *request)
1475 g_object_unref (request->document);
1476 g_free (request->page_id);
1477 g_object_unref (request->cancellable);
1479 if (request->error)
1480 g_error_free (request->error);
1482 g_slice_free (Request, request);
1485 /******************************************************************************/
1487 static const gchar *
1488 str_ref (const gchar *str)
1490 gpointer p;
1491 guint i;
1493 g_static_mutex_lock (&str_mutex);
1494 if (str_refs == NULL)
1495 str_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
1496 p = g_hash_table_lookup (str_refs, str);
1498 i = GPOINTER_TO_UINT (p);
1499 i++;
1500 p = GUINT_TO_POINTER (i);
1502 g_hash_table_insert (str_refs, (gpointer) str, p);
1503 g_static_mutex_unlock (&str_mutex);
1505 return str;
1508 static void
1509 str_unref (const gchar *str)
1511 gpointer p;
1512 guint i;
1514 g_static_mutex_lock (&str_mutex);
1515 p = g_hash_table_lookup (str_refs, str);
1517 i = GPOINTER_TO_UINT (p);
1518 i--;
1519 p = GUINT_TO_POINTER (i);
1521 if (i > 0) {
1522 g_hash_table_insert (str_refs, (gpointer) str, p);
1524 else {
1525 g_hash_table_remove (str_refs, (gpointer) str);
1526 g_free ((gchar *) str);
1529 g_static_mutex_unlock (&str_mutex);