1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * Copyright (C) 2003-2020 Shaun McCance  <shaunm@gnome.org>
4  *
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.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Shaun McCance  <shaunm@gnome.org>
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <gtk/gtk.h>
28 
29 #include "yelp-debug.h"
30 #include "yelp-document.h"
31 #include "yelp-error.h"
32 #include "yelp-docbook-document.h"
33 #include "yelp-help-list.h"
34 #include "yelp-info-document.h"
35 #include "yelp-mallard-document.h"
36 #include "yelp-man-document.h"
37 #include "yelp-settings.h"
38 #include "yelp-simple-document.h"
39 #include "yelp-storage.h"
40 
41 enum {
42     PROP_0,
43     PROP_URI,
44     PROP_INDEXED
45 };
46 
47 typedef struct _Request Request;
48 struct _Request {
49     YelpDocument         *document;
50     gchar                *page_id;
51     GCancellable         *cancellable;
52     YelpDocumentCallback  callback;
53     gpointer              user_data;
54     GDestroyNotify        notify;
55     GError               *error;
56 
57     gint                  idle_funcs;
58 };
59 
60 typedef struct _Hash Hash;
61 struct _Hash {
62     gpointer        null;
63     GHashTable     *hash;
64     GDestroyNotify  destroy;
65 };
66 
67 struct _YelpDocumentPrivate {
68     GMutex  mutex;
69 
70     GSList *reqs_all;         /* Holds canonical refs, only free from here */
71     Hash   *reqs_by_page_id;  /* Indexed by page ID, contains GSList */
72     GSList *reqs_pending;     /* List of requests that need a page */
73 
74     GSList *reqs_search;      /* Pending search requests, not in reqs_all */
75     gboolean indexed;
76 
77     YelpUri *uri;
78     gchar   *doc_uri;
79 
80     /* Real page IDs map to themselves, so this list doubles
81      * as a list of all valid page IDs.
82      */
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   *keywords;      /* Mapping of page IDs to keywords */
88     Hash   *icons;         /* Mapping of page IDs to icons */
89     Hash   *mime_types;    /* Mapping of page IDs to mime types */
90     Hash   *contents;      /* Mapping of page IDs to string content */
91 
92     Hash   *root_ids;      /* Mapping of page IDs to "root page" IDs */
93     Hash   *prev_ids;      /* Mapping of page IDs to "previous page" IDs */
94     Hash   *next_ids;      /* Mapping of page IDs to "next page" IDs */
95     Hash   *up_ids;        /* Mapping of page IDs to "up page" IDs */
96 
97     GError *idle_error;
98 };
99 
100 G_DEFINE_TYPE_WITH_PRIVATE (YelpDocument, yelp_document, G_TYPE_OBJECT)
101 
102 static void           yelp_document_dispose     (GObject              *object);
103 static void           yelp_document_finalize    (GObject              *object);
104 static void           document_get_property     (GObject              *object,
105                                                  guint                 prop_id,
106                                                  GValue               *value,
107                                                  GParamSpec           *pspec);
108 static void           document_set_property     (GObject              *object,
109                                                  guint                 prop_id,
110                                                  const GValue         *value,
111                                                  GParamSpec           *pspec);
112 static gboolean       document_request_page     (YelpDocument         *document,
113                                                  const gchar          *page_id,
114                                                  GCancellable         *cancellable,
115                                                  YelpDocumentCallback  callback,
116                                                  gpointer              user_data,
117                                                  GDestroyNotify        notify);
118 static gboolean       document_indexed          (YelpDocument         *document);
119 static const gchar *  document_read_contents    (YelpDocument         *document,
120                                                  const gchar          *page_id);
121 static void           document_finish_read      (YelpDocument         *document,
122                                                  const gchar          *contents);
123 static gchar *        document_get_mime_type    (YelpDocument         *document,
124                                                  const gchar          *mime_type);
125 static void           document_index            (YelpDocument         *document);
126 
127 static Hash *         hash_new                  (GDestroyNotify        destroy);
128 static void           hash_free                 (Hash                 *hash);
129 static gpointer       hash_lookup               (Hash                 *hash,
130                                                  const gchar          *key);
131 static void           hash_replace              (Hash                 *hash,
132                                                  const gchar          *key,
133                                                  gpointer              value);
134 static void           hash_remove               (Hash                 *hash,
135                                                  const gchar          *key);
136 static void           hash_slist_insert         (Hash                 *hash,
137                                                  const gchar          *key,
138                                                  gpointer              value);
139 static void           hash_slist_remove         (Hash                 *hash,
140                                                  const gchar          *key,
141                                                  gpointer              value);
142 
143 static void           request_cancel            (GCancellable         *cancellable,
144                                                  Request              *request);
145 static gboolean       request_idle_contents     (Request              *request);
146 static gboolean       request_idle_info         (Request              *request);
147 static gboolean       request_idle_error        (Request              *request);
148 static gboolean       request_try_free          (Request              *request);
149 static void           request_free              (Request              *request);
150 
151 static const gchar *  str_ref                   (const gchar          *str);
152 static void           str_unref                 (const gchar          *str);
153 
154 static GMutex str_mutex;
155 static GHashTable  *str_refs  = NULL;
156 static GHashTable *documents = NULL;
157 
158 /******************************************************************************/
159 
160 YelpDocument *
yelp_document_lookup_document_uri(const gchar * docuri)161 yelp_document_lookup_document_uri (const gchar *docuri)
162 {
163     if (!documents)
164         return NULL;
165 
166     return g_hash_table_lookup (documents, docuri);
167 }
168 
169 YelpDocument *
yelp_document_get_for_uri(YelpUri * uri)170 yelp_document_get_for_uri (YelpUri *uri)
171 {
172     YelpUriDocumentType doctype;
173     gchar *docuri = NULL;
174     gchar *page_id, *tmp;
175     YelpDocument *document = NULL;
176 
177     if (documents == NULL)
178         documents = g_hash_table_new_full (g_str_hash, g_str_equal,
179                                            g_free, g_object_unref);
180 
181     g_return_val_if_fail (yelp_uri_is_resolved (uri), NULL);
182 
183     doctype = yelp_uri_get_document_type (uri);
184 
185     if (doctype == YELP_URI_DOCUMENT_TYPE_TEXT ||
186         doctype == YELP_URI_DOCUMENT_TYPE_HTML ||
187         doctype == YELP_URI_DOCUMENT_TYPE_XHTML) {
188         /* We use YelpSimpleDocument for these, which is a single-file
189          * responder. But the document URI may be set to the directory
190          * holding the file, to allow a directory of HTML files to act
191          * as a single document. So we cache these by a fuller URI.
192          */
193         docuri = yelp_uri_get_document_uri (uri);
194         page_id = yelp_uri_get_page_id (uri);
195         tmp = g_strconcat (docuri, "/", page_id, NULL);
196         g_free (docuri);
197         g_free (page_id);
198         docuri = tmp;
199     }
200     else if (doctype == YELP_URI_DOCUMENT_TYPE_MAN) {
201         /* The document URI for man pages is just man:, so we use the
202          * full canonical URI to look these up.
203          */
204         docuri = yelp_uri_get_canonical_uri (uri);
205     }
206     else {
207         docuri = yelp_uri_get_document_uri (uri);
208     }
209 
210     if (docuri == NULL)
211         return NULL;
212     document = g_hash_table_lookup (documents, docuri);
213 
214     if (document != NULL) {
215         g_free (docuri);
216         return g_object_ref (document);
217     }
218 
219     switch (yelp_uri_get_document_type (uri)) {
220     case YELP_URI_DOCUMENT_TYPE_TEXT:
221     case YELP_URI_DOCUMENT_TYPE_HTML:
222     case YELP_URI_DOCUMENT_TYPE_XHTML:
223         document = yelp_simple_document_new (uri);
224         break;
225     case YELP_URI_DOCUMENT_TYPE_DOCBOOK:
226         document = yelp_docbook_document_new (uri);
227         break;
228     case YELP_URI_DOCUMENT_TYPE_MALLARD:
229         document = yelp_mallard_document_new (uri);
230         break;
231     case YELP_URI_DOCUMENT_TYPE_MAN:
232         document = yelp_man_document_new (uri);
233         break;
234     case YELP_URI_DOCUMENT_TYPE_INFO:
235         document = yelp_info_document_new (uri);
236         break;
237     case YELP_URI_DOCUMENT_TYPE_HELP_LIST:
238         document = yelp_help_list_new (uri);
239         break;
240     case YELP_URI_DOCUMENT_TYPE_NOT_FOUND:
241     case YELP_URI_DOCUMENT_TYPE_EXTERNAL:
242     case YELP_URI_DOCUMENT_TYPE_ERROR:
243         break;
244     case YELP_URI_DOCUMENT_TYPE_UNRESOLVED:
245     default:
246         g_assert_not_reached ();
247     }
248 
249     if (document != NULL) {
250 	g_hash_table_insert (documents, docuri, document);
251 	return g_object_ref (document);
252     }
253 
254     g_free (docuri);
255     return NULL;
256 }
257 
258 /******************************************************************************/
259 
260 static void
yelp_document_class_init(YelpDocumentClass * klass)261 yelp_document_class_init (YelpDocumentClass *klass)
262 {
263     GObjectClass   *object_class = G_OBJECT_CLASS (klass);
264 
265     object_class->dispose  = yelp_document_dispose;
266     object_class->finalize = yelp_document_finalize;
267     object_class->get_property = document_get_property;
268     object_class->set_property = document_set_property;
269 
270     klass->request_page =   document_request_page;
271     klass->read_contents =  document_read_contents;
272     klass->finish_read =    document_finish_read;
273     klass->get_mime_type =  document_get_mime_type;
274     klass->index =          document_index;
275 
276     g_object_class_install_property (object_class,
277                                      PROP_INDEXED,
278                                      g_param_spec_boolean ("indexed",
279                                                            "Indexed",
280                                                            "Whether the document content has been indexed",
281                                                            FALSE,
282                                                            G_PARAM_CONSTRUCT | G_PARAM_READWRITE |
283                                                            G_PARAM_STATIC_STRINGS));
284 
285     g_object_class_install_property (object_class,
286                                      PROP_URI,
287                                      g_param_spec_object ("document-uri",
288                                                           "Document URI",
289                                                           "The URI which identifies the document",
290                                                           YELP_TYPE_URI,
291                                                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
292                                                           G_PARAM_STATIC_STRINGS));
293 }
294 
295 static void
yelp_document_init(YelpDocument * document)296 yelp_document_init (YelpDocument *document)
297 {
298     YelpDocumentPrivate *priv;
299 
300     document->priv = priv = yelp_document_get_instance_private (document);
301 
302     g_mutex_init (&priv->mutex);
303 
304     priv->reqs_by_page_id = hash_new ((GDestroyNotify) g_slist_free);
305     priv->reqs_all = NULL;
306     priv->reqs_pending = NULL;
307     priv->reqs_search = NULL;
308 
309     priv->core_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
310     priv->page_ids = hash_new (g_free );
311     priv->titles = hash_new (g_free);
312     priv->descs = hash_new (g_free);
313     priv->keywords = hash_new (g_free);
314     priv->icons = hash_new (g_free);
315     priv->mime_types = hash_new (g_free);
316     priv->contents = hash_new ((GDestroyNotify) str_unref);
317 
318     priv->root_ids = hash_new (g_free);
319     priv->prev_ids = hash_new (g_free);
320     priv->next_ids = hash_new (g_free);
321     priv->up_ids = hash_new (g_free);
322 }
323 
324 static void
yelp_document_dispose(GObject * object)325 yelp_document_dispose (GObject *object)
326 {
327     YelpDocument *document = YELP_DOCUMENT (object);
328 
329     if (document->priv->reqs_all) {
330 	g_slist_foreach (document->priv->reqs_all,
331 			 (GFunc)(GCallback) request_try_free,
332 			 NULL);
333 	g_slist_free (document->priv->reqs_all);
334 	document->priv->reqs_all = NULL;
335     }
336 
337     if (document->priv->reqs_search) {
338 	g_slist_foreach (document->priv->reqs_search,
339 			 (GFunc)(GCallback) request_try_free,
340 			 NULL);
341 	g_slist_free (document->priv->reqs_search);
342 	document->priv->reqs_search = NULL;
343     }
344 
345     G_OBJECT_CLASS (yelp_document_parent_class)->dispose (object);
346 }
347 
348 static void
yelp_document_finalize(GObject * object)349 yelp_document_finalize (GObject *object)
350 {
351     YelpDocument *document = YELP_DOCUMENT (object);
352 
353     g_clear_object (&document->priv->uri);
354     g_free (document->priv->doc_uri);
355 
356     g_slist_free (document->priv->reqs_pending);
357     hash_free (document->priv->reqs_by_page_id);
358 
359     hash_free (document->priv->page_ids);
360     hash_free (document->priv->titles);
361     hash_free (document->priv->descs);
362     hash_free (document->priv->keywords);
363     hash_free (document->priv->icons);
364     hash_free (document->priv->mime_types);
365 
366     hash_free (document->priv->contents);
367 
368     hash_free (document->priv->root_ids);
369     hash_free (document->priv->prev_ids);
370     hash_free (document->priv->next_ids);
371     hash_free (document->priv->up_ids);
372 
373     g_hash_table_destroy (document->priv->core_ids);
374 
375     g_mutex_clear (&document->priv->mutex);
376 
377     G_OBJECT_CLASS (yelp_document_parent_class)->finalize (object);
378 }
379 
380 static void
document_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)381 document_get_property (GObject      *object,
382                        guint         prop_id,
383                        GValue       *value,
384                        GParamSpec   *pspec)
385 {
386     YelpDocument *document = YELP_DOCUMENT (object);
387 
388     switch (prop_id) {
389     case PROP_INDEXED:
390         g_value_set_boolean (value, document->priv->indexed);
391         break;
392     case PROP_URI:
393         g_value_set_object (value, document->priv->uri);
394         break;
395     default:
396         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
397         break;
398     }
399 }
400 
401 static void
document_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)402 document_set_property (GObject      *object,
403                        guint         prop_id,
404                        const GValue *value,
405                        GParamSpec   *pspec)
406 {
407     YelpDocument *document = YELP_DOCUMENT (object);
408 
409     switch (prop_id) {
410     case PROP_INDEXED:
411         document->priv->indexed = g_value_get_boolean (value);
412         if (document->priv->indexed)
413             g_idle_add ((GSourceFunc) document_indexed, document);
414         break;
415     case PROP_URI:
416         document->priv->uri = g_value_dup_object (value);
417         if (document->priv->uri)
418             document->priv->doc_uri = yelp_uri_get_document_uri (document->priv->uri);
419         break;
420     default:
421         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
422         break;
423     }
424 }
425 
426 /******************************************************************************/
427 
428 YelpUri *
yelp_document_get_uri(YelpDocument * document)429 yelp_document_get_uri (YelpDocument *document)
430 {
431     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
432 
433     return document->priv->uri;
434 }
435 
436 gchar **
yelp_document_list_page_ids(YelpDocument * document)437 yelp_document_list_page_ids (YelpDocument *document)
438 {
439     GList *lst, *cur;
440     gint i;
441     gchar **ret = NULL;
442 
443     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
444 
445     g_mutex_lock (&document->priv->mutex);
446 
447     lst = g_hash_table_get_keys (document->priv->core_ids);
448     ret = g_new0 (gchar *, g_list_length (lst) + 1);
449     i = 0;
450     for (cur = lst; cur != NULL; cur = cur->next) {
451         ret[i] = g_strdup ((gchar *) cur->data);
452         i++;
453     }
454     g_list_free (lst);
455 
456     g_mutex_unlock (&document->priv->mutex);
457 
458     return ret;
459 }
460 
461 gchar *
yelp_document_get_page_id(YelpDocument * document,const gchar * id)462 yelp_document_get_page_id (YelpDocument *document,
463 			   const gchar  *id)
464 {
465     gchar *ret = NULL;
466 
467     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
468 
469     if (id != NULL && g_str_has_prefix (id, "search="))
470         return g_strdup (id);
471 
472     g_mutex_lock (&document->priv->mutex);
473     ret = hash_lookup (document->priv->page_ids, id);
474     if (ret)
475 	ret = g_strdup (ret);
476 
477     g_mutex_unlock (&document->priv->mutex);
478 
479     return ret;
480 }
481 
482 void
yelp_document_set_page_id(YelpDocument * document,const gchar * id,const gchar * page_id)483 yelp_document_set_page_id (YelpDocument *document,
484 			   const gchar  *id,
485 			   const gchar  *page_id)
486 {
487     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
488 
489     g_mutex_lock (&document->priv->mutex);
490 
491     hash_replace (document->priv->page_ids, id, g_strdup (page_id));
492 
493     if (id == NULL || !g_str_equal (id, page_id)) {
494 	GSList *reqs, *cur;
495 	reqs = hash_lookup (document->priv->reqs_by_page_id, id);
496 	for (cur = reqs; cur != NULL; cur = cur->next) {
497 	    Request *request = (Request *) cur->data;
498             if (request == NULL)
499                 continue;
500 	    g_free (request->page_id);
501 	    request->page_id = g_strdup (page_id);
502 	    hash_slist_insert (document->priv->reqs_by_page_id, page_id, request);
503 	}
504 	if (reqs) {
505 	    hash_remove (document->priv->reqs_by_page_id, id);
506         }
507     }
508 
509     if (page_id != NULL) {
510         if (g_hash_table_lookup (document->priv->core_ids, page_id) == NULL) {
511             gchar *ins = g_strdup (page_id);
512             g_hash_table_insert (document->priv->core_ids, ins, ins);
513         }
514     }
515 
516     g_mutex_unlock (&document->priv->mutex);
517 }
518 
519 gchar *
yelp_document_get_root_id(YelpDocument * document,const gchar * page_id)520 yelp_document_get_root_id (YelpDocument *document,
521 			   const gchar  *page_id)
522 {
523     gchar *real, *ret = NULL;
524 
525     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
526 
527     g_mutex_lock (&document->priv->mutex);
528     if (page_id != NULL && g_str_has_prefix (page_id, "search="))
529         real = hash_lookup (document->priv->page_ids, NULL);
530     else
531         real = hash_lookup (document->priv->page_ids, page_id);
532     if (real) {
533 	ret = hash_lookup (document->priv->root_ids, real);
534 	if (ret)
535 	    ret = g_strdup (ret);
536     }
537     g_mutex_unlock (&document->priv->mutex);
538 
539     return ret;
540 }
541 
542 void
yelp_document_set_root_id(YelpDocument * document,const gchar * page_id,const gchar * root_id)543 yelp_document_set_root_id (YelpDocument *document,
544 			   const gchar  *page_id,
545 			   const gchar  *root_id)
546 {
547     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
548 
549     g_mutex_lock (&document->priv->mutex);
550     hash_replace (document->priv->root_ids, page_id, g_strdup (root_id));
551     g_mutex_unlock (&document->priv->mutex);
552 }
553 
554 gchar *
yelp_document_get_prev_id(YelpDocument * document,const gchar * page_id)555 yelp_document_get_prev_id (YelpDocument *document,
556 			   const gchar  *page_id)
557 {
558     gchar *real, *ret = NULL;
559 
560     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
561 
562     g_mutex_lock (&document->priv->mutex);
563     real = hash_lookup (document->priv->page_ids, page_id);
564     if (real) {
565 	ret = hash_lookup (document->priv->prev_ids, real);
566 	if (ret)
567 	    ret = g_strdup (ret);
568     }
569     g_mutex_unlock (&document->priv->mutex);
570 
571     return ret;
572 }
573 
574 void
yelp_document_set_prev_id(YelpDocument * document,const gchar * page_id,const gchar * prev_id)575 yelp_document_set_prev_id (YelpDocument *document,
576 			   const gchar  *page_id,
577 			   const gchar  *prev_id)
578 {
579     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
580 
581     g_mutex_lock (&document->priv->mutex);
582     hash_replace (document->priv->prev_ids, page_id, g_strdup (prev_id));
583     g_mutex_unlock (&document->priv->mutex);
584 }
585 
586 gchar *
yelp_document_get_next_id(YelpDocument * document,const gchar * page_id)587 yelp_document_get_next_id (YelpDocument *document,
588 			   const gchar  *page_id)
589 {
590     gchar *real, *ret = NULL;
591 
592     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
593 
594     g_mutex_lock (&document->priv->mutex);
595     real = hash_lookup (document->priv->page_ids, page_id);
596     if (real) {
597 	ret = hash_lookup (document->priv->next_ids, real);
598 	if (ret)
599 	    ret = g_strdup (ret);
600     }
601     g_mutex_unlock (&document->priv->mutex);
602 
603     return ret;
604 }
605 
606 void
yelp_document_set_next_id(YelpDocument * document,const gchar * page_id,const gchar * next_id)607 yelp_document_set_next_id (YelpDocument *document,
608 			   const gchar  *page_id,
609 			   const gchar  *next_id)
610 {
611     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
612 
613     g_mutex_lock (&document->priv->mutex);
614     hash_replace (document->priv->next_ids, page_id, g_strdup (next_id));
615     g_mutex_unlock (&document->priv->mutex);
616 }
617 
618 gchar *
yelp_document_get_up_id(YelpDocument * document,const gchar * page_id)619 yelp_document_get_up_id (YelpDocument *document,
620 			 const gchar  *page_id)
621 {
622     gchar *real, *ret = NULL;
623 
624     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
625 
626     g_mutex_lock (&document->priv->mutex);
627     real = hash_lookup (document->priv->page_ids, page_id);
628     if (real) {
629 	ret = hash_lookup (document->priv->up_ids, real);
630 	if (ret)
631 	    ret = g_strdup (ret);
632     }
633     g_mutex_unlock (&document->priv->mutex);
634 
635     return ret;
636 }
637 
638 void
yelp_document_set_up_id(YelpDocument * document,const gchar * page_id,const gchar * up_id)639 yelp_document_set_up_id (YelpDocument *document,
640 			 const gchar  *page_id,
641 			 const gchar  *up_id)
642 {
643     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
644 
645     g_mutex_lock (&document->priv->mutex);
646     hash_replace (document->priv->up_ids, page_id, g_strdup (up_id));
647     g_mutex_unlock (&document->priv->mutex);
648 }
649 
650 gchar *
yelp_document_get_root_title(YelpDocument * document,const gchar * page_id)651 yelp_document_get_root_title (YelpDocument *document,
652                               const gchar  *page_id)
653 {
654     gchar *real, *root, *ret = NULL;
655 
656     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
657 
658     g_mutex_lock (&document->priv->mutex);
659     if (page_id != NULL && g_str_has_prefix (page_id, "search=")) {
660         ret = yelp_storage_get_root_title (yelp_storage_get_default (),
661                                            document->priv->doc_uri);
662     }
663     else {
664         real = hash_lookup (document->priv->page_ids, page_id);
665         if (real) {
666             root = hash_lookup (document->priv->root_ids, real);
667             if (root) {
668                 ret = hash_lookup (document->priv->titles, root);
669                 if (ret)
670                     ret = g_strdup (ret);
671             }
672         }
673     }
674 
675     g_mutex_unlock (&document->priv->mutex);
676 
677     return ret;
678 }
679 
680 gchar *
yelp_document_get_page_title(YelpDocument * document,const gchar * page_id)681 yelp_document_get_page_title (YelpDocument *document,
682                               const gchar  *page_id)
683 {
684     gchar *real, *ret = NULL;
685 
686     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
687 
688     if (page_id != NULL && g_str_has_prefix (page_id, "search=")) {
689         ret = g_uri_unescape_string (page_id + 7, NULL);
690         return ret;
691     }
692 
693     g_mutex_lock (&document->priv->mutex);
694     real = hash_lookup (document->priv->page_ids, page_id);
695     if (real) {
696 	ret = hash_lookup (document->priv->titles, real);
697 	if (ret)
698 	    ret = g_strdup (ret);
699     }
700     g_mutex_unlock (&document->priv->mutex);
701 
702     return ret;
703 }
704 
705 void
yelp_document_set_page_title(YelpDocument * document,const gchar * page_id,const gchar * title)706 yelp_document_set_page_title (YelpDocument *document,
707                               const gchar  *page_id,
708                               const gchar  *title)
709 {
710     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
711 
712     g_mutex_lock (&document->priv->mutex);
713     hash_replace (document->priv->titles, page_id, g_strdup (title));
714     g_mutex_unlock (&document->priv->mutex);
715 }
716 
717 gchar *
yelp_document_get_page_desc(YelpDocument * document,const gchar * page_id)718 yelp_document_get_page_desc (YelpDocument *document,
719                              const gchar  *page_id)
720 {
721     gchar *real, *ret = NULL;
722 
723     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
724 
725     if (page_id != NULL && g_str_has_prefix (page_id, "search="))
726         return yelp_document_get_root_title (document, page_id);
727 
728     g_mutex_lock (&document->priv->mutex);
729     real = hash_lookup (document->priv->page_ids, page_id);
730     if (real) {
731 	ret = hash_lookup (document->priv->descs, real);
732 	if (ret)
733 	    ret = g_strdup (ret);
734     }
735     g_mutex_unlock (&document->priv->mutex);
736 
737     return ret;
738 }
739 
740 void
yelp_document_set_page_desc(YelpDocument * document,const gchar * page_id,const gchar * desc)741 yelp_document_set_page_desc (YelpDocument *document,
742                              const gchar  *page_id,
743                              const gchar  *desc)
744 {
745     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
746 
747     g_mutex_lock (&document->priv->mutex);
748     hash_replace (document->priv->descs, page_id, g_strdup (desc));
749     g_mutex_unlock (&document->priv->mutex);
750 }
751 
752 gchar *
yelp_document_get_page_keywords(YelpDocument * document,const gchar * page_id)753 yelp_document_get_page_keywords (YelpDocument *document,
754                                  const gchar  *page_id)
755 {
756     gchar *real, *ret = NULL;
757 
758     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
759 
760     if (page_id != NULL && g_str_has_prefix (page_id, "search="))
761         return NULL;
762 
763     g_mutex_lock (&document->priv->mutex);
764     real = hash_lookup (document->priv->page_ids, page_id);
765     if (real) {
766 	ret = hash_lookup (document->priv->keywords, real);
767 	if (ret)
768 	    ret = g_strdup (ret);
769     }
770     g_mutex_unlock (&document->priv->mutex);
771 
772     return ret;
773 }
774 
775 void
yelp_document_set_page_keywords(YelpDocument * document,const gchar * page_id,const gchar * keywords)776 yelp_document_set_page_keywords (YelpDocument *document,
777                                  const gchar  *page_id,
778                                  const gchar  *keywords)
779 {
780     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
781 
782     g_mutex_lock (&document->priv->mutex);
783     hash_replace (document->priv->keywords, page_id, g_strdup (keywords));
784     g_mutex_unlock (&document->priv->mutex);
785 }
786 
787 gchar *
yelp_document_get_page_icon(YelpDocument * document,const gchar * page_id)788 yelp_document_get_page_icon (YelpDocument *document,
789                              const gchar  *page_id)
790 {
791     gchar *real, *ret = NULL;
792 
793     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
794 
795     if (page_id != NULL && g_str_has_prefix (page_id, "search="))
796         return g_strdup ("yelp-page-search-symbolic");
797 
798     g_mutex_lock (&document->priv->mutex);
799     real = hash_lookup (document->priv->page_ids, page_id);
800     if (real) {
801 	ret = hash_lookup (document->priv->icons, real);
802 	if (ret)
803 	    ret = g_strdup (ret);
804     }
805     g_mutex_unlock (&document->priv->mutex);
806 
807     if (ret == NULL)
808         ret = g_strdup ("yelp-page-symbolic");
809 
810     return ret;
811 }
812 
813 void
yelp_document_set_page_icon(YelpDocument * document,const gchar * page_id,const gchar * icon)814 yelp_document_set_page_icon (YelpDocument *document,
815                              const gchar  *page_id,
816                              const gchar  *icon)
817 {
818     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
819 
820     g_mutex_lock (&document->priv->mutex);
821     hash_replace (document->priv->icons, page_id, g_strdup (icon));
822     g_mutex_unlock (&document->priv->mutex);
823 }
824 
825 static gboolean
document_indexed(YelpDocument * document)826 document_indexed (YelpDocument *document)
827 {
828     g_mutex_lock (&document->priv->mutex);
829     while (document->priv->reqs_search != NULL) {
830         Request *request = (Request *) document->priv->reqs_search->data;
831         request->idle_funcs++;
832         g_idle_add ((GSourceFunc) request_idle_info, request);
833         g_idle_add ((GSourceFunc) request_idle_contents, request);
834         document->priv->reqs_search = g_slist_delete_link (document->priv->reqs_search,
835                                                            document->priv->reqs_search);
836     }
837     g_mutex_unlock (&document->priv->mutex);
838 
839     return FALSE;
840 }
841 
842 /******************************************************************************/
843 
844 gboolean
yelp_document_request_page(YelpDocument * document,const gchar * page_id,GCancellable * cancellable,YelpDocumentCallback callback,gpointer user_data,GDestroyNotify notify)845 yelp_document_request_page (YelpDocument         *document,
846 			    const gchar          *page_id,
847 			    GCancellable         *cancellable,
848 			    YelpDocumentCallback  callback,
849 			    gpointer              user_data,
850 			    GDestroyNotify        notify)
851 {
852     g_return_val_if_fail (YELP_IS_DOCUMENT (document), FALSE);
853     g_return_val_if_fail (YELP_DOCUMENT_GET_CLASS (document)->request_page != NULL, FALSE);
854 
855     debug_print (DB_FUNCTION, "entering\n");
856 
857     return YELP_DOCUMENT_GET_CLASS (document)->request_page (document,
858 							     page_id,
859 							     cancellable,
860 							     callback,
861 							     user_data,
862 							     notify);
863 }
864 
865 static gboolean
document_request_page(YelpDocument * document,const gchar * page_id,GCancellable * cancellable,YelpDocumentCallback callback,gpointer user_data,GDestroyNotify notify)866 document_request_page (YelpDocument         *document,
867 		       const gchar          *page_id,
868 		       GCancellable         *cancellable,
869 		       YelpDocumentCallback  callback,
870 		       gpointer              user_data,
871 		       GDestroyNotify        notify)
872 {
873     Request *request;
874     gchar *real_id;
875     gboolean ret = FALSE;
876 
877     request = g_slice_new0 (Request);
878     request->document = g_object_ref (document);
879 
880     real_id = hash_lookup (document->priv->page_ids, page_id);
881     if (real_id)
882 	request->page_id = g_strdup (real_id);
883     else
884 	request->page_id = g_strdup (page_id);
885 
886     if (cancellable) {
887       request->cancellable = g_object_ref (cancellable);
888       g_signal_connect (cancellable, "cancelled",
889               G_CALLBACK (request_cancel), request);
890     }
891     else {
892       request->cancellable = NULL;
893     }
894 
895     request->callback = callback;
896     request->user_data = user_data;
897     request->notify = notify;
898     request->idle_funcs = 0;
899 
900     g_mutex_lock (&document->priv->mutex);
901 
902     if (page_id && g_str_has_prefix (page_id, "search=")) {
903         document->priv->reqs_search = g_slist_prepend (document->priv->reqs_search, request);
904         if (document->priv->indexed)
905             g_idle_add ((GSourceFunc) document_indexed, document);
906         else
907             yelp_document_index (document);
908         g_mutex_unlock (&document->priv->mutex);
909         return TRUE;
910     }
911 
912     hash_slist_insert (document->priv->reqs_by_page_id,
913 		       request->page_id,
914 		       request);
915 
916     document->priv->reqs_all = g_slist_prepend (document->priv->reqs_all, request);
917     document->priv->reqs_pending = g_slist_prepend (document->priv->reqs_pending, request);
918 
919     if (hash_lookup (document->priv->titles, request->page_id)) {
920 	request->idle_funcs++;
921 	g_idle_add ((GSourceFunc) request_idle_info, request);
922     }
923 
924     if (hash_lookup (document->priv->contents, request->page_id)) {
925 	request->idle_funcs++;
926 	g_idle_add ((GSourceFunc) request_idle_contents, request);
927 	ret = TRUE;
928     }
929 
930     g_mutex_unlock (&document->priv->mutex);
931 
932     return ret;
933 }
934 
935 void
yelp_document_clear_contents(YelpDocument * document)936 yelp_document_clear_contents (YelpDocument *document)
937 {
938     g_mutex_lock (&document->priv->mutex);
939 
940     if (document->priv->contents->null) {
941         str_unref (document->priv->contents->null);
942         document->priv->contents->null = NULL;
943     }
944     g_hash_table_remove_all (document->priv->contents->hash);
945 
946     g_mutex_unlock (&document->priv->mutex);
947 }
948 
949 gchar **
yelp_document_get_requests(YelpDocument * document)950 yelp_document_get_requests (YelpDocument *document)
951 {
952     GList *reqs, *cur;
953     gchar **ret;
954     gint i;
955 
956     g_mutex_lock (&document->priv->mutex);
957 
958     reqs = g_hash_table_get_keys (document->priv->reqs_by_page_id->hash);
959     ret = g_new0 (gchar*, g_list_length (reqs) + 1);
960     for (cur = reqs, i = 0; cur; cur = cur->next, i++) {
961         ret[i] = g_strdup ((gchar *) cur->data);
962     }
963     g_list_free (reqs);
964 
965     g_mutex_unlock (&document->priv->mutex);
966 
967     return ret;
968 }
969 
970 /******************************************************************************/
971 
972 const gchar *
yelp_document_read_contents(YelpDocument * document,const gchar * page_id)973 yelp_document_read_contents (YelpDocument *document,
974 			     const gchar  *page_id)
975 {
976     g_return_val_if_fail (YELP_IS_DOCUMENT (document), NULL);
977     g_return_val_if_fail (YELP_DOCUMENT_GET_CLASS (document)->read_contents != NULL, NULL);
978 
979     return YELP_DOCUMENT_GET_CLASS (document)->read_contents (document, page_id);
980 }
981 
982 static const gchar *
document_read_contents(YelpDocument * document,const gchar * page_id)983 document_read_contents (YelpDocument *document,
984 			const gchar  *page_id)
985 {
986     gchar *real, *str, **colors;
987 
988     g_mutex_lock (&document->priv->mutex);
989 
990     real = hash_lookup (document->priv->page_ids, page_id);
991 
992     if (page_id != NULL && g_str_has_prefix (page_id, "search=")) {
993         gchar *tmp, *tmp2, *txt;
994         GVariant *value;
995         GVariantIter *iter;
996         gchar *url, *title, *desc, *icon; /* do not free */
997         gchar *index_title;
998         GString *ret = g_string_new ("<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><style type='text/css'>");
999 
1000         colors = yelp_settings_get_colors (yelp_settings_get_default ());
1001         g_string_append_printf (ret,
1002                                 "html { height: 100%%; } "
1003                                 "body { margin: 0; padding: 0;"
1004                                 " background-color: %s; color: %s;"
1005                                 " direction: %s; } "
1006                                 "div.header { margin-bottom: 1em; } "
1007                                 "div.trails { "
1008                                 " margin: 0; padding: 0.2em 12px 0 12px;"
1009                                 " background-color: %s;"
1010                                 " border-bottom: solid 1px %s; } "
1011                                 "div.trail { text-indent: -1em;"
1012                                 " margin: 0 1em 0.2em 1em; padding: 0; color: %s; } "
1013                                 "div.body { margin: 0 12px 0 12px; padding: 0 0 12px 0; max-width: 60em; } "
1014                                 "div, p { margin: 1em 0 0 0; padding: 0; } "
1015                                 "div:first-child, p:first-child { margin-top: 0; } "
1016                                 "h1 { margin: 0; padding: 0; color: %s; font-size: 1.44em; } "
1017                                 "a { color: %s; text-decoration: none; } "
1018                                 "a.linkdiv { display: block; } "
1019                                 "div.linkdiv { margin: 0; padding: 0.5em; }"
1020                                 "a:hover div.linkdiv {"
1021                                 " outline: solid 1px %s;"
1022                                 " background: -webkit-gradient(linear, left top, left 80, from(%s), to(%s)); } "
1023                                 "div.title { margin-bottom: 0.2em; font-weight: bold; } "
1024                                 "div.desc { margin: 0; color: %s; } "
1025                                 "</style></head><body><div class='header'>",
1026                                 colors[YELP_SETTINGS_COLOR_BASE],
1027                                 colors[YELP_SETTINGS_COLOR_TEXT],
1028                                 (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL ? "rtl" : "ltr"),
1029                                 colors[YELP_SETTINGS_COLOR_GRAY_BASE],
1030                                 colors[YELP_SETTINGS_COLOR_GRAY_BORDER],
1031                                 colors[YELP_SETTINGS_COLOR_TEXT_LIGHT],
1032                                 colors[YELP_SETTINGS_COLOR_TEXT_LIGHT],
1033                                 colors[YELP_SETTINGS_COLOR_LINK],
1034                                 colors[YELP_SETTINGS_COLOR_BLUE_BASE],
1035                                 colors[YELP_SETTINGS_COLOR_BLUE_BASE],
1036                                 colors[YELP_SETTINGS_COLOR_BASE],
1037                                 colors[YELP_SETTINGS_COLOR_TEXT_LIGHT]
1038                                 );
1039 
1040         index_title = yelp_storage_get_root_title (yelp_storage_get_default (),
1041                                                    document->priv->doc_uri);
1042         if (index_title != NULL) {
1043             tmp = g_markup_printf_escaped ("<div class='trails'><div class='trail'>"
1044                                            "<a href='xref:'>%s</a>&#x00A0;%s "
1045                                            "</div></div>",
1046                                            index_title,
1047                                            (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL ? "«" : "»")
1048                                            );
1049             g_string_append (ret, tmp);
1050             g_free (tmp);
1051         }
1052 
1053         g_string_append (ret, "</div><div class='body'>");
1054         g_strfreev (colors);
1055 
1056         str = hash_lookup (document->priv->contents, real);
1057         if (str) {
1058             str_ref (str);
1059             g_mutex_unlock (&document->priv->mutex);
1060             return (const gchar *) str;
1061         }
1062 
1063         txt = g_uri_unescape_string (page_id + 7, NULL);
1064         tmp2 = g_strdup_printf (_("Search results for “%s”"), txt);
1065         tmp = g_markup_printf_escaped ("<h1>%s</h1>", tmp2);
1066         g_string_append (ret, tmp);
1067         g_free (tmp2);
1068         g_free (tmp);
1069 
1070         value = yelp_storage_search (yelp_storage_get_default (),
1071                                      document->priv->doc_uri,
1072                                      txt);
1073         iter = g_variant_iter_new (value);
1074         if (g_variant_iter_n_children (iter) == 0) {
1075             if (index_title != NULL) {
1076                 gchar *t = g_strdup_printf (_("No matching help pages found in “%s”."), index_title);
1077                 tmp = g_markup_printf_escaped ("<p>%s</p>", t);
1078                 g_free (t);
1079             }
1080             else {
1081                 tmp = g_markup_printf_escaped ("<p>%s</p>",
1082                                                _("No matching help pages found."));
1083             }
1084             g_string_append (ret, tmp);
1085             g_free (tmp);
1086         }
1087         else {
1088             while (g_variant_iter_loop (iter, "(&s&s&s&s)", &url, &title, &desc, &icon)) {
1089                 gchar *xref_uri = NULL;
1090 
1091                 if (g_str_has_prefix (url, document->priv->doc_uri)) {
1092                     gchar *urloffset = url + strlen(document->priv->doc_uri) + 1; /* do not free */
1093                     if (urloffset[0] == '?')
1094                         urloffset += 1; /* handle oddity of old ghelp URIs */
1095                     xref_uri = g_strdup_printf ("xref:%s", urloffset);
1096                 }
1097 
1098                 tmp = g_markup_printf_escaped ("<div><a class='linkdiv' href='%s'><div class='linkdiv'>"
1099                                                "<div class='title'>%s</div>"
1100                                                "<div class='desc'>%s</div>"
1101                                                "</div></a></div>",
1102                                                xref_uri && xref_uri[0] != '\0' ? xref_uri : url,
1103                                                title, desc);
1104                 g_string_append (ret, tmp);
1105                 g_free (xref_uri);
1106                 g_free (tmp);
1107             }
1108         }
1109         g_variant_iter_free (iter);
1110         g_variant_unref (value);
1111 
1112         if (index_title != NULL)
1113             g_free (index_title);
1114         g_free (txt);
1115         g_string_append (ret, "</div></body></html>");
1116 
1117         hash_replace (document->priv->contents, page_id, g_string_free (ret, FALSE));
1118         str = hash_lookup (document->priv->contents, page_id);
1119         str_ref (str);
1120         g_mutex_unlock (&document->priv->mutex);
1121         return (const gchar *) str;
1122     }
1123 
1124     str = hash_lookup (document->priv->contents, real);
1125     if (str)
1126 	str_ref (str);
1127 
1128     g_mutex_unlock (&document->priv->mutex);
1129 
1130     return (const gchar *) str;
1131 }
1132 
1133 void
yelp_document_finish_read(YelpDocument * document,const gchar * contents)1134 yelp_document_finish_read (YelpDocument *document,
1135 			   const gchar  *contents)
1136 {
1137     g_return_if_fail (YELP_IS_DOCUMENT (document));
1138     g_return_if_fail (YELP_DOCUMENT_GET_CLASS (document)->finish_read != NULL);
1139 
1140     YELP_DOCUMENT_GET_CLASS (document)->finish_read (document, contents);
1141 }
1142 
1143 static void
document_finish_read(YelpDocument * document,const gchar * contents)1144 document_finish_read (YelpDocument *document,
1145 		      const gchar  *contents)
1146 {
1147     str_unref (contents);
1148 }
1149 
1150 void
yelp_document_give_contents(YelpDocument * document,const gchar * page_id,gchar * contents,const gchar * mime)1151 yelp_document_give_contents (YelpDocument *document,
1152 			     const gchar  *page_id,
1153 			     gchar        *contents,
1154 			     const gchar  *mime)
1155 {
1156     g_return_if_fail (YELP_IS_DOCUMENT (document));
1157 
1158     debug_print (DB_FUNCTION, "entering\n");
1159     debug_print (DB_ARG, "    page_id = \"%s\"\n", page_id);
1160 
1161     g_mutex_lock (&document->priv->mutex);
1162 
1163     hash_replace (document->priv->contents,
1164                   page_id,
1165                   (gpointer) str_ref (contents));
1166 
1167     hash_replace (document->priv->mime_types,
1168                   page_id,
1169                   g_strdup (mime));
1170 
1171     g_mutex_unlock (&document->priv->mutex);
1172 }
1173 
1174 gchar *
yelp_document_get_mime_type(YelpDocument * document,const gchar * page_id)1175 yelp_document_get_mime_type (YelpDocument *document,
1176 			     const gchar  *page_id)
1177 {
1178     g_return_val_if_fail (YELP_IS_DOCUMENT (document), NULL);
1179     g_return_val_if_fail (YELP_DOCUMENT_GET_CLASS (document)->get_mime_type != NULL, NULL);
1180 
1181     return YELP_DOCUMENT_GET_CLASS (document)->get_mime_type (document, page_id);
1182 }
1183 
1184 static gchar *
document_get_mime_type(YelpDocument * document,const gchar * page_id)1185 document_get_mime_type (YelpDocument *document,
1186 			const gchar  *page_id)
1187 {
1188     gchar *real, *ret = NULL;
1189 
1190     if (page_id != NULL && g_str_has_prefix (page_id, "search="))
1191       return g_strdup ("application/xhtml+xml");
1192 
1193     g_mutex_lock (&document->priv->mutex);
1194     real = hash_lookup (document->priv->page_ids, page_id);
1195     if (real) {
1196 	ret = hash_lookup (document->priv->mime_types, real);
1197 	if (ret)
1198 	    ret = g_strdup (ret);
1199     }
1200     g_mutex_unlock (&document->priv->mutex);
1201 
1202     return ret;
1203 }
1204 
1205 /******************************************************************************/
1206 
1207 void
yelp_document_index(YelpDocument * document)1208 yelp_document_index (YelpDocument *document)
1209 {
1210     g_return_if_fail (YELP_IS_DOCUMENT (document));
1211     g_return_if_fail (YELP_DOCUMENT_GET_CLASS (document)->index != NULL);
1212 
1213     YELP_DOCUMENT_GET_CLASS (document)->index (document);
1214 }
1215 
1216 static void
document_index(YelpDocument * document)1217 document_index (YelpDocument *document)
1218 {
1219     g_object_set (document, "indexed", TRUE, NULL);
1220 }
1221 
1222 /******************************************************************************/
1223 
1224 void
yelp_document_signal(YelpDocument * document,const gchar * page_id,YelpDocumentSignal signal,const GError * error)1225 yelp_document_signal (YelpDocument       *document,
1226 		      const gchar        *page_id,
1227 		      YelpDocumentSignal  signal,
1228 		      const GError       *error)
1229 {
1230     GSList *reqs, *cur;
1231 
1232     g_return_if_fail (YELP_IS_DOCUMENT (document));
1233 
1234     g_mutex_lock (&document->priv->mutex);
1235 
1236     reqs = hash_lookup (document->priv->reqs_by_page_id, page_id);
1237     for (cur = reqs; cur != NULL; cur = cur->next) {
1238 	Request *request = (Request *) cur->data;
1239 	if (!request)
1240 	    continue;
1241 	switch (signal) {
1242 	case YELP_DOCUMENT_SIGNAL_CONTENTS:
1243 	    request->idle_funcs++;
1244 	    g_idle_add ((GSourceFunc) request_idle_contents, request);
1245 	    break;
1246 	case YELP_DOCUMENT_SIGNAL_INFO:
1247 	    request->idle_funcs++;
1248 	    g_idle_add ((GSourceFunc) request_idle_info, request);
1249 	    break;
1250 	case YELP_DOCUMENT_SIGNAL_ERROR:
1251 	    request->idle_funcs++;
1252 	    request->error = yelp_error_copy ((GError *) error);
1253 	    g_idle_add ((GSourceFunc) request_idle_error, request);
1254             break;
1255 	default:
1256 	    break;
1257 	}
1258     }
1259 
1260     g_mutex_unlock (&document->priv->mutex);
1261 }
1262 
1263 static gboolean
yelp_document_error_pending_idle(YelpDocument * document)1264 yelp_document_error_pending_idle (YelpDocument *document)
1265 {
1266     YelpDocumentPrivate *priv = yelp_document_get_instance_private (document);
1267     GSList *cur;
1268     Request *request;
1269 
1270     g_mutex_lock (&priv->mutex);
1271 
1272     if (priv->reqs_pending) {
1273 	for (cur = priv->reqs_pending; cur; cur = cur->next) {
1274 	    request = cur->data;
1275 	    request->error = yelp_error_copy ((GError *) priv->idle_error);
1276 	    request->idle_funcs++;
1277 	    g_idle_add ((GSourceFunc) request_idle_error, request);
1278 	}
1279 
1280 	g_slist_free (priv->reqs_pending);
1281 	priv->reqs_pending = NULL;
1282     }
1283 
1284     g_mutex_unlock (&priv->mutex);
1285 
1286     g_object_unref (document);
1287     return FALSE;
1288 }
1289 
1290 void
yelp_document_error_pending(YelpDocument * document,const GError * error)1291 yelp_document_error_pending (YelpDocument *document,
1292 			     const GError *error)
1293 {
1294     YelpDocumentPrivate *priv = yelp_document_get_instance_private (document);
1295 
1296     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
1297 
1298     g_object_ref (document);
1299     priv->idle_error = g_error_copy (error);
1300     g_idle_add ((GSourceFunc) yelp_document_error_pending_idle, document);
1301 }
1302 
1303 /******************************************************************************/
1304 
1305 static Hash *
hash_new(GDestroyNotify destroy)1306 hash_new (GDestroyNotify destroy)
1307 {
1308     Hash *hash = g_new0 (Hash, 1);
1309     hash->destroy = destroy;
1310     hash->hash = g_hash_table_new_full (g_str_hash, g_str_equal,
1311 					g_free, destroy);
1312     return hash;
1313 }
1314 
1315 static void
hash_free(Hash * hash)1316 hash_free (Hash *hash)
1317 {
1318     if (hash->null)
1319 	hash->destroy (hash->null);
1320     g_hash_table_destroy (hash->hash);
1321     g_free (hash);
1322 }
1323 
1324 static gpointer
hash_lookup(Hash * hash,const gchar * key)1325 hash_lookup (Hash *hash, const gchar *key)
1326 {
1327     if (key == NULL)
1328 	return hash->null;
1329     else
1330 	return g_hash_table_lookup (hash->hash, key);
1331 }
1332 
1333 static void
hash_replace(Hash * hash,const gchar * key,gpointer value)1334 hash_replace (Hash        *hash,
1335               const gchar *key,
1336               gpointer     value)
1337 {
1338     if (key == NULL) {
1339         if (hash->null)
1340             hash->destroy (hash->null);
1341         hash->null = value;
1342     }
1343     else
1344         g_hash_table_replace (hash->hash, g_strdup (key), value);
1345 }
1346 
1347 static void
hash_remove(Hash * hash,const gchar * key)1348 hash_remove (Hash        *hash,
1349              const gchar *key)
1350 {
1351     if (key == NULL) {
1352         if (hash->null) {
1353             hash->destroy (hash->null);
1354             hash->null = NULL;
1355         }
1356     }
1357     else
1358         g_hash_table_remove (hash->hash, key);
1359 }
1360 
1361 static void
hash_slist_insert(Hash * hash,const gchar * key,gpointer value)1362 hash_slist_insert (Hash        *hash,
1363                    const gchar *key,
1364                    gpointer     value)
1365 {
1366     GSList *list;
1367     list = hash_lookup (hash, key);
1368     if (list) {
1369         list->next = g_slist_prepend (list->next, value);
1370     } else {
1371         list = g_slist_prepend (NULL, value);
1372         list = g_slist_prepend (list, NULL);
1373         if (key == NULL)
1374             hash->null = list;
1375         else
1376             g_hash_table_insert (hash->hash, g_strdup (key), list);
1377     }
1378 }
1379 
1380 static void
hash_slist_remove(Hash * hash,const gchar * key,gpointer value)1381 hash_slist_remove (Hash        *hash,
1382                    const gchar *key,
1383                    gpointer     value)
1384 {
1385     GSList *list;
1386     list = hash_lookup (hash, key);
1387     if (list) {
1388         list = g_slist_remove (list, value);
1389         if (list->next == NULL)
1390             hash_remove (hash, key);
1391     }
1392 }
1393 
1394 /******************************************************************************/
1395 
1396 static void
request_cancel(GCancellable * cancellable,Request * request)1397 request_cancel (GCancellable *cancellable, Request *request)
1398 {
1399     GSList *cur;
1400     YelpDocument *document = request->document;
1401     gboolean found = FALSE;
1402 
1403     g_assert (document != NULL && YELP_IS_DOCUMENT (document));
1404 
1405     g_mutex_lock (&document->priv->mutex);
1406 
1407     document->priv->reqs_pending = g_slist_remove (document->priv->reqs_pending,
1408 						   (gconstpointer) request);
1409     hash_slist_remove (document->priv->reqs_by_page_id,
1410 		       request->page_id,
1411 		       request);
1412     for (cur = document->priv->reqs_all; cur != NULL; cur = cur->next) {
1413 	if (cur->data == request) {
1414 	    document->priv->reqs_all = g_slist_delete_link (document->priv->reqs_all, cur);
1415             found = TRUE;
1416 	    break;
1417 	}
1418     }
1419     if (!found) {
1420         for (cur = document->priv->reqs_search; cur != NULL; cur = cur->next) {
1421             if (cur->data == request) {
1422                 document->priv->reqs_search = g_slist_delete_link (document->priv->reqs_search, cur);
1423                 break;
1424             }
1425         }
1426     }
1427     request_try_free (request);
1428 
1429     g_mutex_unlock (&document->priv->mutex);
1430 }
1431 
1432 static gboolean
request_idle_contents(Request * request)1433 request_idle_contents (Request *request)
1434 {
1435     YelpDocument *document;
1436     YelpDocumentPrivate *priv;
1437     YelpDocumentCallback callback = NULL;
1438     gpointer user_data;
1439 
1440     g_assert (request != NULL && YELP_IS_DOCUMENT (request->document));
1441 
1442     if (g_cancellable_is_cancelled (request->cancellable)) {
1443 	request->idle_funcs--;
1444 	return FALSE;
1445     }
1446 
1447     document = g_object_ref (request->document);
1448     priv = yelp_document_get_instance_private (document);
1449 
1450     g_mutex_lock (&document->priv->mutex);
1451 
1452     priv->reqs_pending = g_slist_remove (priv->reqs_pending, request);
1453 
1454     callback = request->callback;
1455     user_data = request->user_data;
1456     request->idle_funcs--;
1457 
1458     g_mutex_unlock (&document->priv->mutex);
1459 
1460     if (callback)
1461 	callback (document, YELP_DOCUMENT_SIGNAL_CONTENTS, user_data, NULL);
1462 
1463     g_object_unref (document);
1464     return FALSE;
1465 }
1466 
1467 static gboolean
request_idle_info(Request * request)1468 request_idle_info (Request *request)
1469 {
1470     YelpDocument *document;
1471     YelpDocumentCallback callback = NULL;
1472     gpointer user_data;
1473 
1474     g_assert (request != NULL && YELP_IS_DOCUMENT (request->document));
1475 
1476     if (g_cancellable_is_cancelled (request->cancellable)) {
1477 	request->idle_funcs--;
1478 	return FALSE;
1479     }
1480 
1481     document = g_object_ref (request->document);
1482 
1483     g_mutex_lock (&document->priv->mutex);
1484 
1485     callback = request->callback;
1486     user_data = request->user_data;
1487     request->idle_funcs--;
1488 
1489     g_mutex_unlock (&document->priv->mutex);
1490 
1491     if (callback)
1492 	callback (document, YELP_DOCUMENT_SIGNAL_INFO, user_data, NULL);
1493 
1494     g_object_unref (document);
1495     return FALSE;
1496 }
1497 
1498 static gboolean
request_idle_error(Request * request)1499 request_idle_error (Request *request)
1500 {
1501     YelpDocument *document;
1502     YelpDocumentPrivate *priv;
1503     YelpDocumentCallback callback = NULL;
1504     GError *error = NULL;
1505     gpointer user_data;
1506 
1507     g_assert (request != NULL && YELP_IS_DOCUMENT (request->document));
1508 
1509     if (g_cancellable_is_cancelled (request->cancellable)) {
1510 	request->idle_funcs--;
1511 	return FALSE;
1512     }
1513 
1514     document = g_object_ref (request->document);
1515     priv = yelp_document_get_instance_private (document);
1516 
1517     g_mutex_lock (&priv->mutex);
1518 
1519     if (request->error) {
1520 	callback = request->callback;
1521 	user_data = request->user_data;
1522 	error = request->error;
1523 	priv->reqs_pending = g_slist_remove (priv->reqs_pending, request);
1524     }
1525 
1526     request->idle_funcs--;
1527     g_mutex_unlock (&priv->mutex);
1528 
1529     if (callback)
1530 	callback (document,
1531 		  YELP_DOCUMENT_SIGNAL_ERROR,
1532 		  user_data,
1533 		  error);
1534 
1535     g_clear_error (&request->error);
1536     g_object_unref (document);
1537     return FALSE;
1538 }
1539 
1540 static gboolean
request_try_free(Request * request)1541 request_try_free (Request *request)
1542 {
1543     if (!g_cancellable_is_cancelled (request->cancellable))
1544 	g_cancellable_cancel (request->cancellable);
1545 
1546     if (request->idle_funcs == 0)
1547 	request_free (request);
1548     else
1549 	g_idle_add ((GSourceFunc) request_try_free, request);
1550 
1551     return FALSE;
1552 }
1553 
1554 static void
request_free(Request * request)1555 request_free (Request *request)
1556 {
1557     if (request->notify)
1558         request->notify (request->user_data);
1559 
1560     g_object_unref (request->document);
1561     g_free (request->page_id);
1562     g_object_unref (request->cancellable);
1563 
1564     if (request->error)
1565 	g_error_free (request->error);
1566 
1567     g_slice_free (Request, request);
1568 }
1569 
1570 /******************************************************************************/
1571 
1572 static const gchar *
str_ref(const gchar * str)1573 str_ref (const gchar *str)
1574 {
1575     gpointer p;
1576     guint i;
1577 
1578     g_mutex_lock (&str_mutex);
1579     if (str_refs == NULL)
1580 	str_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
1581     p = g_hash_table_lookup (str_refs, str);
1582 
1583     i = GPOINTER_TO_UINT (p);
1584     i++;
1585     p = GUINT_TO_POINTER (i);
1586 
1587     g_hash_table_insert (str_refs, (gpointer) str, p);
1588     g_mutex_unlock (&str_mutex);
1589 
1590     return str;
1591 }
1592 
1593 static void
str_unref(const gchar * str)1594 str_unref (const gchar *str)
1595 {
1596     gpointer p;
1597     guint i;
1598 
1599     g_mutex_lock (&str_mutex);
1600     p = g_hash_table_lookup (str_refs, str);
1601 
1602     i = GPOINTER_TO_UINT (p);
1603     i--;
1604     p = GUINT_TO_POINTER (i);
1605 
1606     if (i > 0) {
1607 	g_hash_table_insert (str_refs, (gpointer) str, p);
1608     }
1609     else {
1610 	g_hash_table_remove (str_refs, (gpointer) str);
1611 	g_free ((gchar *) str);
1612     }
1613 
1614     g_mutex_unlock (&str_mutex);
1615 }
1616