1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
27 #include <glib/gi18n.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"
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 */
47 typedef struct _YelpInfoDocumentPrivate YelpInfoDocumentPrivate
;
48 struct _YelpInfoDocumentPrivate
{
55 GtkTreeModel
*sections
;
57 gboolean process_running
;
58 gboolean transform_running
;
60 YelpTransform
*transform
;
70 static void yelp_info_document_dispose (GObject
*object
);
71 static void yelp_info_document_finalize (GObject
*object
);
74 static gboolean
info_request_page (YelpDocument
*document
,
76 GCancellable
*cancellable
,
77 YelpDocumentCallback callback
,
81 static void transform_chunk_ready (YelpTransform
*transform
,
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
,
91 static void info_document_process (YelpInfoDocument
*info
);
92 static gboolean
info_sections_visit (GtkTreeModel
*model
,
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))
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
));
117 yelp_info_document_init (YelpInfoDocument
*info
)
119 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
121 priv
->state
= INFO_STATE_BLANK
;
123 g_mutex_init (&priv
->mutex
);
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
);
145 yelp_info_document_finalize (GObject
*object
)
147 YelpInfoDocumentPrivate
*priv
= GET_PRIV (object
);
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 /******************************************************************************/
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
,
173 /******************************************************************************/
174 /** YelpDocument **************************************************************/
177 info_request_page (YelpDocument
*document
,
178 const gchar
*page_id
,
179 GCancellable
*cancellable
,
180 YelpDocumentCallback callback
,
183 YelpInfoDocumentPrivate
*priv
= GET_PRIV (document
);
189 page_id
= priv
->root_id
;
192 YELP_DOCUMENT_CLASS (yelp_info_document_parent_class
)->request_page (document
,
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
,
212 case INFO_STATE_PARSING
:
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’."),
221 yelp_document_signal (document
, page_id
,
222 YELP_DOCUMENT_SIGNAL_ERROR
,
224 g_error_free (error
);
227 g_assert_not_reached ();
231 g_mutex_unlock (&priv
->mutex
);
236 /******************************************************************************/
237 /** YelpTransform *************************************************************/
240 transform_chunk_ready (YelpTransform
*transform
,
242 YelpInfoDocument
*info
)
244 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
247 g_assert (transform
== priv
->transform
);
249 if (priv
->state
== INFO_STATE_STOP
) {
250 info_document_disconnect (info
);
254 content
= yelp_transform_take_chunk (transform
, chunk_id
);
255 yelp_document_give_contents (YELP_DOCUMENT (info
),
258 "application/xhtml+xml");
260 yelp_document_signal (YELP_DOCUMENT (info
),
262 YELP_DOCUMENT_SIGNAL_INFO
,
264 yelp_document_signal (YELP_DOCUMENT (info
),
266 YELP_DOCUMENT_SIGNAL_CONTENTS
,
271 transform_finished (YelpTransform
*transform
,
272 YelpInfoDocument
*info
)
274 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
278 g_assert (transform
== priv
->transform
);
280 if (priv
->state
== INFO_STATE_STOP
) {
281 info_document_disconnect (info
);
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
,
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’."),
301 yelp_document_error_pending ((YelpDocument
*) info
, error
);
302 g_error_free (error
);
306 transform_error (YelpTransform
*transform
,
307 YelpInfoDocument
*info
)
309 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
312 g_assert (transform
== priv
->transform
);
314 if (priv
->state
== INFO_STATE_STOP
) {
315 info_document_disconnect (info
);
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
);
327 transform_finalized (YelpInfoDocument
*info
,
330 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
333 xmlFreeDoc (priv
->xmldoc
);
339 /******************************************************************************/
340 /** Threaded ******************************************************************/
343 info_document_process (YelpInfoDocument
*info
)
345 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
347 gchar
*filepath
= NULL
;
350 gchar
**params
= NULL
;
352 file
= yelp_uri_get_file (yelp_document_get_uri ((YelpDocument
*) info
));
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
);
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."),
367 yelp_document_error_pending ((YelpDocument
*) info
, error
);
368 g_error_free (error
);
372 priv
->sections
= (GtkTreeModel
*) yelp_info_parser_parse_file (filepath
);
373 gtk_tree_model_foreach (priv
->sections
,
374 (GtkTreeModelForeachFunc
) info_sections_visit
,
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."),
383 yelp_document_error_pending ((YelpDocument
*) info
, error
);
387 g_mutex_lock (&priv
->mutex
);
388 if (priv
->state
== INFO_STATE_STOP
) {
389 g_mutex_unlock (&priv
->mutex
);
393 priv
->transform
= yelp_transform_new (STYLESHEET
);
395 g_signal_connect (priv
->transform
, "chunk-ready",
396 (GCallback
) transform_chunk_ready
,
399 g_signal_connect (priv
->transform
, "finished",
400 (GCallback
) transform_finished
,
403 g_signal_connect (priv
->transform
, "error",
404 (GCallback
) transform_error
,
407 params
= yelp_settings_get_all_params (yelp_settings_get_default (), 0, ¶ms_i
);
409 priv
->transform_running
= TRUE
;
410 yelp_transform_start (priv
->transform
,
413 (const gchar
* const *) params
);
415 g_mutex_unlock (&priv
->mutex
);
419 priv
->process_running
= FALSE
;
420 g_object_unref (info
);
424 info_sections_visit (GtkTreeModel
*model
,
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
,
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
;
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
);
468 g_signal_handler_disconnect (priv
->transform
, priv
->error
);
471 yelp_transform_cancel (priv
->transform
);
472 g_object_unref (priv
->transform
);
473 priv
->transform
= NULL
;
474 priv
->transform_running
= FALSE
;