Don't crash when doing stuff to empty windows
[yelp.git] / libyelp / yelp-man-document.c
blob8d8031bd2320db4f2eb35496b8b0a0183ef126f8
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2007-2010 Shaun McCance <shaunm@gnome.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Shaun McCance <shaunm@gnome.org>
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30 #include <libxml/tree.h>
32 #include "yelp-error.h"
33 #include "yelp-man-document.h"
34 #include "yelp-man-parser.h"
35 #include "yelp-transform.h"
36 #include "yelp-settings.h"
38 #define STYLESHEET DATADIR"/yelp/xslt/man2html.xsl"
40 typedef enum {
41 MAN_STATE_BLANK, /* Brand new, run transform as needed */
42 MAN_STATE_PARSING, /* Parsing/transforming document, please wait */
43 MAN_STATE_PARSED, /* All done, if we ain't got it, it ain't here */
44 MAN_STATE_STOP /* Stop everything now, object to be disposed */
45 } ManState;
47 typedef struct _YelpManDocumentPrivate YelpManDocumentPrivate;
48 struct _YelpManDocumentPrivate {
49 YelpUri *uri;
50 ManState state;
51 gchar *page_id;
53 GMutex *mutex;
54 GThread *thread;
56 xmlDocPtr xmldoc;
58 gboolean process_running;
59 gboolean transform_running;
61 YelpTransform *transform;
62 guint chunk_ready;
63 guint finished;
64 guint error;
67 typedef struct _YelpLangEncodings YelpLangEncodings;
68 struct _YelpLangEncodings {
69 gchar *language;
70 gchar *encoding;
72 /* http://www.w3.org/International/O-charset-lang.html */
73 static const YelpLangEncodings langmap[] = {
74 { "C", "ISO-8859-1" },
75 { "af", "ISO-8859-1" },
76 { "ar", "ISO-8859-6" },
77 { "bg", "ISO-8859-5" },
78 { "be", "ISO-8859-5" },
79 { "ca", "ISO-8859-1" },
80 { "cs", "ISO-8859-2" },
81 { "da", "ISO-8859-1" },
82 { "de", "ISO-8859-1" },
83 { "el", "ISO-8859-7" },
84 { "en", "ISO-8859-1" },
85 { "eo", "ISO-8859-3" },
86 { "es", "ISO-8859-1" },
87 { "et", "ISO-8859-15" },
88 { "eu", "ISO-8859-1" },
89 { "fi", "ISO-8859-1" },
90 { "fo", "ISO-8859-1" },
91 { "fr", "ISO-8859-1" },
92 { "ga", "ISO-8859-1" },
93 { "gd", "ISO-8859-1" },
94 { "gl", "ISO-8859-1" },
95 { "hu", "ISO-8859-2" },
96 { "id", "ISO-8859-1" }, /* is this right */
97 { "mt", "ISO-8859-3" },
98 { "is", "ISO-8859-1" },
99 { "it", "ISO-8859-1" },
100 { "iw", "ISO-8859-8" },
101 { "ja", "EUC-JP" },
102 { "ko", "EUC-KR" },
103 { "lt", "ISO-8859-13" },
104 { "lv", "ISO-8859-13" },
105 { "mk", "ISO-8859-5" },
106 { "mt", "ISO-8859-3" },
107 { "no", "ISO-8859-1" },
108 { "pl", "ISO-8859-2" },
109 { "pt_BR", "ISO-8859-1" },
110 { "ro", "ISO-8859-2" },
111 { "ru", "KOI8-R" },
112 { "sl", "ISO-8859-2" },
113 { "sr", "ISO-8859-2" }, /* Latin, not cyrillic */
114 { "sk", "ISO-8859-2" },
115 { "sv", "ISO-8859-1" },
116 { "tr", "ISO-8859-9" },
117 { "uk", "ISO-8859-5" },
118 { "zh_CN", "BIG5" },
119 { "zh_TW", "BIG5" },
120 { NULL, NULL },
123 static void yelp_man_document_class_init (YelpManDocumentClass *klass);
124 static void yelp_man_document_init (YelpManDocument *man);
125 static void yelp_man_document_dispose (GObject *object);
126 static void yelp_man_document_finalize (GObject *object);
128 /* YelpDocument */
129 static gboolean man_request_page (YelpDocument *document,
130 const gchar *page_id,
131 GCancellable *cancellable,
132 YelpDocumentCallback callback,
133 gpointer user_data);
135 /* YelpTransform */
136 static void transform_chunk_ready (YelpTransform *transform,
137 gchar *chunk_id,
138 YelpManDocument *man);
139 static void transform_finished (YelpTransform *transform,
140 YelpManDocument *man);
141 static void transform_error (YelpTransform *transform,
142 YelpManDocument *man);
143 static void transform_finalized (YelpManDocument *man,
144 gpointer transform);
146 /* Threaded */
147 static void man_document_process (YelpManDocument *man);
149 static void man_document_disconnect (YelpManDocument *man);
152 G_DEFINE_TYPE (YelpManDocument, yelp_man_document, YELP_TYPE_DOCUMENT);
153 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_MAN_DOCUMENT, YelpManDocumentPrivate))
155 static void
156 yelp_man_document_class_init (YelpManDocumentClass *klass)
158 GObjectClass *object_class = G_OBJECT_CLASS (klass);
159 YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass);
161 object_class->dispose = yelp_man_document_dispose;
162 object_class->finalize = yelp_man_document_finalize;
164 document_class->request_page = man_request_page;
166 g_type_class_add_private (klass, sizeof (YelpManDocumentPrivate));
169 static void
170 yelp_man_document_init (YelpManDocument *man)
172 YelpManDocumentPrivate *priv = GET_PRIV (man);
174 priv->state = MAN_STATE_BLANK;
175 priv->mutex = g_mutex_new ();
178 static void
179 yelp_man_document_dispose (GObject *object)
181 YelpManDocumentPrivate *priv = GET_PRIV (object);
183 if (priv->uri) {
184 g_object_unref (priv->uri);
185 priv->uri = NULL;
188 G_OBJECT_CLASS (yelp_man_document_parent_class)->dispose (object);
191 static void
192 yelp_man_document_finalize (GObject *object)
194 YelpManDocumentPrivate *priv = GET_PRIV (object);
196 if (priv->xmldoc)
197 xmlFreeDoc (priv->xmldoc);
199 g_mutex_free (priv->mutex);
200 g_free (priv->page_id);
202 G_OBJECT_CLASS (yelp_man_document_parent_class)->finalize (object);
205 /******************************************************************************/
207 YelpDocument *
208 yelp_man_document_new (YelpUri *uri)
210 YelpManDocument *man;
211 YelpManDocumentPrivate *priv;
212 gchar *doc_uri;
214 g_return_val_if_fail (uri != NULL, NULL);
216 doc_uri = yelp_uri_get_document_uri (uri);
217 man = (YelpManDocument *) g_object_new (YELP_TYPE_MAN_DOCUMENT,
218 "document-uri", doc_uri,
219 NULL);
220 g_free (doc_uri);
221 priv = GET_PRIV (man);
223 priv->uri = g_object_ref (uri);
225 return (YelpDocument *) man;
229 /******************************************************************************/
230 /** YelpDocument **************************************************************/
232 static gboolean
233 man_request_page (YelpDocument *document,
234 const gchar *page_id,
235 GCancellable *cancellable,
236 YelpDocumentCallback callback,
237 gpointer user_data)
239 YelpManDocumentPrivate *priv = GET_PRIV (document);
240 gchar *docuri, *fulluri;
241 GError *error;
242 gboolean handled;
244 fulluri = yelp_uri_get_canonical_uri (priv->uri);
245 if (g_str_has_prefix (fulluri, "man:"))
246 priv->page_id = g_strdup (fulluri + 4);
247 else
248 priv->page_id = g_strdup ("//index");
249 g_free (fulluri);
251 handled =
252 YELP_DOCUMENT_CLASS (yelp_man_document_parent_class)->request_page (document,
253 page_id,
254 cancellable,
255 callback,
256 user_data);
257 if (handled) {
258 return handled;
261 g_mutex_lock (priv->mutex);
263 switch (priv->state) {
264 case MAN_STATE_BLANK:
265 priv->state = MAN_STATE_PARSING;
266 priv->process_running = TRUE;
267 g_object_ref (document);
268 yelp_document_set_page_id (document, page_id, priv->page_id);
269 yelp_document_set_page_id (document, NULL, priv->page_id);
270 yelp_document_set_page_id (document, "//index", priv->page_id);
271 yelp_document_set_page_id (document, priv->page_id, priv->page_id);
272 yelp_document_set_root_id (document, priv->page_id, priv->page_id);
273 priv->thread = g_thread_create ((GThreadFunc) man_document_process,
274 document, FALSE, NULL);
275 break;
276 case MAN_STATE_PARSING:
277 break;
278 case MAN_STATE_PARSED:
279 case MAN_STATE_STOP:
280 docuri = yelp_uri_get_document_uri (priv->uri);
281 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
282 _("The page ‘%s’ was not found in the document ‘%s’."),
283 page_id, docuri);
284 g_free (docuri);
285 yelp_document_signal (document, page_id,
286 YELP_DOCUMENT_SIGNAL_ERROR,
287 error);
288 g_error_free (error);
289 break;
292 g_mutex_unlock (priv->mutex);
293 return FALSE;
297 /******************************************************************************/
298 /** YelpTransform *************************************************************/
300 static void
301 transform_chunk_ready (YelpTransform *transform,
302 gchar *chunk_id,
303 YelpManDocument *man)
305 YelpManDocumentPrivate *priv = GET_PRIV (man);
306 gchar *content;
308 g_assert (transform == priv->transform);
310 if (priv->state == MAN_STATE_STOP) {
311 man_document_disconnect (man);
312 return;
315 content = yelp_transform_take_chunk (transform, chunk_id);
316 yelp_document_give_contents (YELP_DOCUMENT (man),
317 priv->page_id,
318 content,
319 "application/xhtml+xml");
321 yelp_document_signal (YELP_DOCUMENT (man),
322 priv->page_id,
323 YELP_DOCUMENT_SIGNAL_INFO,
324 NULL);
325 yelp_document_signal (YELP_DOCUMENT (man),
326 priv->page_id,
327 YELP_DOCUMENT_SIGNAL_CONTENTS,
328 NULL);
331 static void
332 transform_finished (YelpTransform *transform,
333 YelpManDocument *man)
335 YelpManDocumentPrivate *priv = GET_PRIV (man);
336 gchar *docuri;
337 GError *error;
339 g_assert (transform == priv->transform);
341 if (priv->state == MAN_STATE_STOP) {
342 man_document_disconnect (man);
343 return;
346 man_document_disconnect (man);
347 priv->state = MAN_STATE_PARSED;
349 /* We want to free priv->xmldoc, but we can't free it before transform
350 is finalized. Otherwise, we could crash when YelpTransform frees
351 its libxslt resources.
353 g_object_weak_ref ((GObject *) transform,
354 (GWeakNotify) transform_finalized,
355 man);
357 docuri = yelp_uri_get_document_uri (priv->uri);
358 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
359 _("The requested page was not found in the document ‘%s’."),
360 docuri);
361 g_free (docuri);
362 yelp_document_error_pending ((YelpDocument *) man, error);
363 g_error_free (error);
366 static void
367 transform_error (YelpTransform *transform,
368 YelpManDocument *man)
370 YelpManDocumentPrivate *priv = GET_PRIV (man);
371 GError *error;
373 g_assert (transform == priv->transform);
375 if (priv->state == MAN_STATE_STOP) {
376 man_document_disconnect (man);
377 return;
380 error = yelp_transform_get_error (transform);
381 yelp_document_error_pending ((YelpDocument *) man, error);
382 g_error_free (error);
384 man_document_disconnect (man);
387 static void
388 transform_finalized (YelpManDocument *man,
389 gpointer transform)
391 YelpManDocumentPrivate *priv = GET_PRIV (man);
393 if (priv->xmldoc)
394 xmlFreeDoc (priv->xmldoc);
395 priv->xmldoc = NULL;
399 /******************************************************************************/
400 /** Threaded ******************************************************************/
402 static void
403 man_document_process (YelpManDocument *man)
405 YelpManDocumentPrivate *priv = GET_PRIV (man);
406 GFile *file = NULL;
407 gchar *filepath = NULL;
408 GError *error;
409 gint params_i = 0;
410 gchar **params = NULL;
411 YelpManParser *parser;
412 const gchar *language, *encoding;
414 file = yelp_uri_get_file (priv->uri);
415 if (file == NULL) {
416 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
417 _("The file does not exist."));
418 yelp_document_error_pending ((YelpDocument *) man, error);
419 g_error_free (error);
420 goto done;
423 filepath = g_file_get_path (file);
424 g_object_unref (file);
425 if (!g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) {
426 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
427 _("The file ‘%s’ does not exist."),
428 filepath);
429 yelp_document_error_pending ((YelpDocument *) man, error);
430 g_error_free (error);
431 goto done;
434 /* FIXME: get the language */
435 language = "C";
437 /* default encoding if the language doesn't match below */
438 encoding = g_getenv("MAN_ENCODING");
439 if (encoding == NULL)
440 encoding = "ISO-8859-1";
442 if (language != NULL) {
443 gint i;
444 for (i = 0; langmap[i].language != NULL; i++) {
445 if (g_str_equal (language, langmap[i].language)) {
446 encoding = langmap[i].encoding;
447 break;
452 parser = yelp_man_parser_new ();
453 priv->xmldoc = yelp_man_parser_parse_file (parser, filepath, &error);
454 yelp_man_parser_free (parser);
456 if (priv->xmldoc == NULL) {
457 yelp_document_error_pending ((YelpDocument *) man, error);
460 g_mutex_lock (priv->mutex);
461 if (priv->state == MAN_STATE_STOP) {
462 g_mutex_unlock (priv->mutex);
463 goto done;
466 priv->transform = yelp_transform_new (STYLESHEET);
467 priv->chunk_ready =
468 g_signal_connect (priv->transform, "chunk-ready",
469 (GCallback) transform_chunk_ready,
470 man);
471 priv->finished =
472 g_signal_connect (priv->transform, "finished",
473 (GCallback) transform_finished,
474 man);
475 priv->error =
476 g_signal_connect (priv->transform, "error",
477 (GCallback) transform_error,
478 man);
480 params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, &params_i);
482 priv->transform_running = TRUE;
483 yelp_transform_start (priv->transform,
484 priv->xmldoc,
485 NULL,
486 (const gchar * const *) params);
487 g_strfreev (params);
488 g_mutex_unlock (priv->mutex);
490 done:
491 g_free (filepath);
492 priv->process_running = FALSE;
493 g_object_unref (man);
496 static void
497 man_document_disconnect (YelpManDocument *man)
499 YelpManDocumentPrivate *priv = GET_PRIV (man);
500 if (priv->chunk_ready) {
501 g_signal_handler_disconnect (priv->transform, priv->chunk_ready);
502 priv->chunk_ready = 0;
504 if (priv->finished) {
505 g_signal_handler_disconnect (priv->transform, priv->finished);
506 priv->finished = 0;
508 if (priv->error) {
509 g_signal_handler_disconnect (priv->transform, priv->error);
510 priv->error = 0;
512 yelp_transform_cancel (priv->transform);
513 g_object_unref (priv->transform);
514 priv->transform = NULL;
515 priv->transform_running = FALSE;