yelp-document: Fix return type of document_indexed
[yelp.git] / libyelp / yelp-info-document.c
blobb6f50771301bcb39054e16e6123963b1bdd0d71d
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2007 Don Scorgie <dscorgie@svn.gnome.org>
4 * Copyright (C) 2010 Shaun McCance <shaunm@gnome.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
19 * Author: Don Scorgie <dscorgie@svn.gnome.org>
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29 #include <libxml/tree.h>
31 #include "yelp-error.h"
32 #include "yelp-info-document.h"
33 #include "yelp-info-parser.h"
34 #include "yelp-transform.h"
35 #include "yelp-debug.h"
36 #include "yelp-settings.h"
38 #define STYLESHEET DATADIR"/yelp/xslt/info2html.xsl"
40 typedef enum {
41 INFO_STATE_BLANK, /* Brand new, run transform as needed */
42 INFO_STATE_PARSING, /* Parsing/transforming document, please wait */
43 INFO_STATE_PARSED, /* All done, if we ain't got it, it ain't here */
44 INFO_STATE_STOP /* Stop everything now, object to be disposed */
45 } InfoState;
47 typedef struct _YelpInfoDocumentPrivate YelpInfoDocumentPrivate;
48 struct _YelpInfoDocumentPrivate {
49 InfoState state;
51 GMutex mutex;
52 GThread *thread;
54 xmlDocPtr xmldoc;
55 GtkTreeModel *sections;
57 gboolean process_running;
58 gboolean transform_running;
60 YelpTransform *transform;
61 guint chunk_ready;
62 guint finished;
63 guint error;
65 gchar *root_id;
66 gchar *visit_prev_id;
70 static void yelp_info_document_dispose (GObject *object);
71 static void yelp_info_document_finalize (GObject *object);
73 /* YelpDocument */
74 static gboolean info_request_page (YelpDocument *document,
75 const gchar *page_id,
76 GCancellable *cancellable,
77 YelpDocumentCallback callback,
78 gpointer user_data);
80 /* YelpTransform */
81 static void transform_chunk_ready (YelpTransform *transform,
82 gchar *chunk_id,
83 YelpInfoDocument *info);
84 static void transform_finished (YelpTransform *transform,
85 YelpInfoDocument *info);
86 static void transform_error (YelpTransform *transform,
87 YelpInfoDocument *info);
88 static void transform_finalized (YelpInfoDocument *info,
89 gpointer transform);
91 static void info_document_process (YelpInfoDocument *info);
92 static gboolean info_sections_visit (GtkTreeModel *model,
93 GtkTreePath *path,
94 GtkTreeIter *iter,
95 YelpInfoDocument *info);
96 static void info_document_disconnect (YelpInfoDocument *info);
99 G_DEFINE_TYPE (YelpInfoDocument, yelp_info_document, YELP_TYPE_DOCUMENT)
100 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_INFO_DOCUMENT, YelpInfoDocumentPrivate))
102 static void
103 yelp_info_document_class_init (YelpInfoDocumentClass *klass)
105 GObjectClass *object_class = G_OBJECT_CLASS (klass);
106 YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass);
108 object_class->dispose = yelp_info_document_dispose;
109 object_class->finalize = yelp_info_document_finalize;
111 document_class->request_page = info_request_page;
113 g_type_class_add_private (klass, sizeof (YelpInfoDocumentPrivate));
116 static void
117 yelp_info_document_init (YelpInfoDocument *info)
119 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
121 priv->state = INFO_STATE_BLANK;
122 priv->xmldoc = NULL;
123 g_mutex_init (&priv->mutex);
126 static void
127 yelp_info_document_dispose (GObject *object)
129 YelpInfoDocumentPrivate *priv = GET_PRIV (object);
131 if (priv->sections) {
132 g_object_unref (priv->sections);
133 priv->sections = NULL;
136 if (priv->transform) {
137 g_object_unref (priv->transform);
138 priv->transform = NULL;
141 G_OBJECT_CLASS (yelp_info_document_parent_class)->dispose (object);
144 static void
145 yelp_info_document_finalize (GObject *object)
147 YelpInfoDocumentPrivate *priv = GET_PRIV (object);
149 if (priv->xmldoc)
150 xmlFreeDoc (priv->xmldoc);
152 g_free (priv->root_id);
153 g_free (priv->visit_prev_id);
155 g_mutex_clear (&priv->mutex);
157 G_OBJECT_CLASS (yelp_info_document_parent_class)->finalize (object);
160 /******************************************************************************/
162 YelpDocument *
163 yelp_info_document_new (YelpUri *uri)
165 g_return_val_if_fail (uri != NULL, NULL);
167 return (YelpDocument *) g_object_new (YELP_TYPE_INFO_DOCUMENT,
168 "document-uri", uri,
169 NULL);
173 /******************************************************************************/
174 /** YelpDocument **************************************************************/
176 static gboolean
177 info_request_page (YelpDocument *document,
178 const gchar *page_id,
179 GCancellable *cancellable,
180 YelpDocumentCallback callback,
181 gpointer user_data)
183 YelpInfoDocumentPrivate *priv = GET_PRIV (document);
184 gchar *docuri;
185 GError *error;
186 gboolean handled;
188 if (page_id == NULL)
189 page_id = priv->root_id;
191 handled =
192 YELP_DOCUMENT_CLASS (yelp_info_document_parent_class)->request_page (document,
193 page_id,
194 cancellable,
195 callback,
196 user_data);
197 if (handled) {
198 return TRUE;
201 g_mutex_lock (&priv->mutex);
203 switch (priv->state) {
204 case INFO_STATE_BLANK:
205 priv->state = INFO_STATE_PARSING;
206 priv->process_running = TRUE;
207 g_object_ref (document);
208 priv->thread = g_thread_new ("info-page",
209 (GThreadFunc) info_document_process,
210 document);
211 break;
212 case INFO_STATE_PARSING:
213 break;
214 case INFO_STATE_PARSED:
215 case INFO_STATE_STOP:
216 docuri = yelp_uri_get_document_uri (yelp_document_get_uri (document));
217 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
218 _("The page ‘%s’ was not found in the document ‘%s’."),
219 page_id, docuri);
220 g_free (docuri);
221 yelp_document_signal (document, page_id,
222 YELP_DOCUMENT_SIGNAL_ERROR,
223 error);
224 g_error_free (error);
225 break;
226 default:
227 g_assert_not_reached ();
228 break;
231 g_mutex_unlock (&priv->mutex);
232 return TRUE;
236 /******************************************************************************/
237 /** YelpTransform *************************************************************/
239 static void
240 transform_chunk_ready (YelpTransform *transform,
241 gchar *chunk_id,
242 YelpInfoDocument *info)
244 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
245 gchar *content;
247 g_assert (transform == priv->transform);
249 if (priv->state == INFO_STATE_STOP) {
250 info_document_disconnect (info);
251 return;
254 content = yelp_transform_take_chunk (transform, chunk_id);
255 yelp_document_give_contents (YELP_DOCUMENT (info),
256 chunk_id,
257 content,
258 "application/xhtml+xml");
260 yelp_document_signal (YELP_DOCUMENT (info),
261 chunk_id,
262 YELP_DOCUMENT_SIGNAL_INFO,
263 NULL);
264 yelp_document_signal (YELP_DOCUMENT (info),
265 chunk_id,
266 YELP_DOCUMENT_SIGNAL_CONTENTS,
267 NULL);
270 static void
271 transform_finished (YelpTransform *transform,
272 YelpInfoDocument *info)
274 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
275 gchar *docuri;
276 GError *error;
278 g_assert (transform == priv->transform);
280 if (priv->state == INFO_STATE_STOP) {
281 info_document_disconnect (info);
282 return;
285 info_document_disconnect (info);
286 priv->state = INFO_STATE_PARSED;
288 /* We want to free priv->xmldoc, but we can't free it before transform
289 is finalized. Otherwise, we could crash when YelpTransform frees
290 its libxslt resources.
292 g_object_weak_ref ((GObject *) transform,
293 (GWeakNotify) transform_finalized,
294 info);
296 docuri = yelp_uri_get_document_uri (yelp_document_get_uri ((YelpDocument *) info));
297 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
298 _("The requested page was not found in the document ‘%s’."),
299 docuri);
300 g_free (docuri);
301 yelp_document_error_pending ((YelpDocument *) info, error);
302 g_error_free (error);
305 static void
306 transform_error (YelpTransform *transform,
307 YelpInfoDocument *info)
309 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
310 GError *error;
312 g_assert (transform == priv->transform);
314 if (priv->state == INFO_STATE_STOP) {
315 info_document_disconnect (info);
316 return;
319 error = yelp_transform_get_error (transform);
320 yelp_document_error_pending ((YelpDocument *) info, error);
321 g_error_free (error);
323 info_document_disconnect (info);
326 static void
327 transform_finalized (YelpInfoDocument *info,
328 gpointer transform)
330 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
332 if (priv->xmldoc)
333 xmlFreeDoc (priv->xmldoc);
334 priv->xmldoc = NULL;
339 /******************************************************************************/
340 /** Threaded ******************************************************************/
342 static void
343 info_document_process (YelpInfoDocument *info)
345 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
346 GFile *file = NULL;
347 gchar *filepath = NULL;
348 GError *error;
349 gint params_i = 0;
350 gchar **params = NULL;
352 file = yelp_uri_get_file (yelp_document_get_uri ((YelpDocument *) info));
353 if (file == NULL) {
354 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
355 _("The file does not exist."));
356 yelp_document_error_pending ((YelpDocument *) info, error);
357 g_error_free (error);
358 goto done;
361 filepath = g_file_get_path (file);
362 g_object_unref (file);
363 if (!g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) {
364 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
365 _("The file ‘%s’ does not exist."),
366 filepath);
367 yelp_document_error_pending ((YelpDocument *) info, error);
368 g_error_free (error);
369 goto done;
372 priv->sections = (GtkTreeModel *) yelp_info_parser_parse_file (filepath);
373 gtk_tree_model_foreach (priv->sections,
374 (GtkTreeModelForeachFunc) info_sections_visit,
375 info);
376 priv->xmldoc = yelp_info_parser_parse_tree ((GtkTreeStore *) priv->sections);
378 if (priv->xmldoc == NULL) {
379 error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
380 _("The file ‘%s’ could not be parsed because it is"
381 " not a well-formed info page."),
382 filepath);
383 yelp_document_error_pending ((YelpDocument *) info, error);
384 goto done;
387 g_mutex_lock (&priv->mutex);
388 if (priv->state == INFO_STATE_STOP) {
389 g_mutex_unlock (&priv->mutex);
390 goto done;
393 priv->transform = yelp_transform_new (STYLESHEET);
394 priv->chunk_ready =
395 g_signal_connect (priv->transform, "chunk-ready",
396 (GCallback) transform_chunk_ready,
397 info);
398 priv->finished =
399 g_signal_connect (priv->transform, "finished",
400 (GCallback) transform_finished,
401 info);
402 priv->error =
403 g_signal_connect (priv->transform, "error",
404 (GCallback) transform_error,
405 info);
407 params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, &params_i);
409 priv->transform_running = TRUE;
410 yelp_transform_start (priv->transform,
411 priv->xmldoc,
412 NULL,
413 (const gchar * const *) params);
414 g_strfreev (params);
415 g_mutex_unlock (&priv->mutex);
417 done:
418 g_free (filepath);
419 priv->process_running = FALSE;
420 g_object_unref (info);
423 static gboolean
424 info_sections_visit (GtkTreeModel *model,
425 GtkTreePath *path,
426 GtkTreeIter *iter,
427 YelpInfoDocument *info)
429 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
430 gchar *page_id, *title;
432 gtk_tree_model_get (model, iter,
433 INFO_PARSER_COLUMN_PAGE_NO, &page_id,
434 INFO_PARSER_COLUMN_PAGE_NAME, &title,
435 -1);
436 yelp_document_set_page_id ((YelpDocument *) info, page_id, page_id);
437 yelp_document_set_page_title ((YelpDocument *) info, page_id, title);
439 if (priv->root_id == NULL) {
440 priv->root_id = g_strdup (page_id);
441 yelp_document_set_page_id ((YelpDocument *) info, NULL, page_id);
443 yelp_document_set_root_id ((YelpDocument *) info, page_id, priv->root_id);
445 if (priv->visit_prev_id != NULL) {
446 yelp_document_set_prev_id ((YelpDocument *) info, page_id, priv->visit_prev_id);
447 yelp_document_set_next_id ((YelpDocument *) info, priv->visit_prev_id, page_id);
448 g_free (priv->visit_prev_id);
450 priv->visit_prev_id = page_id;
451 g_free (title);
452 return FALSE;
455 static void
456 info_document_disconnect (YelpInfoDocument *info)
458 YelpInfoDocumentPrivate *priv = GET_PRIV (info);
459 if (priv->chunk_ready) {
460 g_signal_handler_disconnect (priv->transform, priv->chunk_ready);
461 priv->chunk_ready = 0;
463 if (priv->finished) {
464 g_signal_handler_disconnect (priv->transform, priv->finished);
465 priv->finished = 0;
467 if (priv->error) {
468 g_signal_handler_disconnect (priv->transform, priv->error);
469 priv->error = 0;
471 yelp_transform_cancel (priv->transform);
472 g_object_unref (priv->transform);
473 priv->transform = NULL;
474 priv->transform_running = FALSE;