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> %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