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, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
21 * Author: Don Scorgie <dscorgie@svn.gnome.org>
29 #include <glib/gi18n.h>
31 #include <libxml/tree.h>
33 #include "yelp-error.h"
34 #include "yelp-info-document.h"
35 #include "yelp-info-parser.h"
36 #include "yelp-transform.h"
37 #include "yelp-debug.h"
38 #include "yelp-settings.h"
40 #define STYLESHEET DATADIR"/yelp/xslt/info2html.xsl"
43 INFO_STATE_BLANK
, /* Brand new, run transform as needed */
44 INFO_STATE_PARSING
, /* Parsing/transforming document, please wait */
45 INFO_STATE_PARSED
, /* All done, if we ain't got it, it ain't here */
46 INFO_STATE_STOP
/* Stop everything now, object to be disposed */
49 typedef struct _YelpInfoDocumentPrivate YelpInfoDocumentPrivate
;
50 struct _YelpInfoDocumentPrivate
{
58 GtkTreeModel
*sections
;
60 gboolean process_running
;
61 gboolean transform_running
;
63 YelpTransform
*transform
;
73 static void yelp_info_document_class_init (YelpInfoDocumentClass
*klass
);
74 static void yelp_info_document_init (YelpInfoDocument
*info
);
75 static void yelp_info_document_dispose (GObject
*object
);
76 static void yelp_info_document_finalize (GObject
*object
);
79 static gboolean
info_request_page (YelpDocument
*document
,
81 GCancellable
*cancellable
,
82 YelpDocumentCallback callback
,
86 static void transform_chunk_ready (YelpTransform
*transform
,
88 YelpInfoDocument
*info
);
89 static void transform_finished (YelpTransform
*transform
,
90 YelpInfoDocument
*info
);
91 static void transform_error (YelpTransform
*transform
,
92 YelpInfoDocument
*info
);
93 static void transform_finalized (YelpInfoDocument
*info
,
96 static void info_document_process (YelpInfoDocument
*info
);
97 static gboolean
info_sections_visit (GtkTreeModel
*model
,
100 YelpInfoDocument
*info
);
101 static void info_document_disconnect (YelpInfoDocument
*info
);
104 G_DEFINE_TYPE (YelpInfoDocument
, yelp_info_document
, YELP_TYPE_DOCUMENT
);
105 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_INFO_DOCUMENT, YelpInfoDocumentPrivate))
108 yelp_info_document_class_init (YelpInfoDocumentClass
*klass
)
110 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
111 YelpDocumentClass
*document_class
= YELP_DOCUMENT_CLASS (klass
);
113 object_class
->dispose
= yelp_info_document_dispose
;
114 object_class
->finalize
= yelp_info_document_finalize
;
116 document_class
->request_page
= info_request_page
;
118 g_type_class_add_private (klass
, sizeof (YelpInfoDocumentPrivate
));
122 yelp_info_document_init (YelpInfoDocument
*info
)
124 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
126 priv
->state
= INFO_STATE_BLANK
;
128 priv
->mutex
= g_mutex_new ();
132 yelp_info_document_dispose (GObject
*object
)
134 YelpInfoDocumentPrivate
*priv
= GET_PRIV (object
);
137 g_object_unref (priv
->uri
);
141 if (priv
->sections
) {
142 g_object_unref (priv
->sections
);
143 priv
->sections
= NULL
;
146 if (priv
->transform
) {
147 g_object_unref (priv
->transform
);
148 priv
->transform
= NULL
;
151 G_OBJECT_CLASS (yelp_info_document_parent_class
)->dispose (object
);
155 yelp_info_document_finalize (GObject
*object
)
157 YelpInfoDocumentPrivate
*priv
= GET_PRIV (object
);
160 xmlFreeDoc (priv
->xmldoc
);
162 g_free (priv
->root_id
);
163 g_free (priv
->visit_prev_id
);
165 g_mutex_free (priv
->mutex
);
167 G_OBJECT_CLASS (yelp_info_document_parent_class
)->finalize (object
);
170 /******************************************************************************/
173 yelp_info_document_new (YelpUri
*uri
)
175 YelpInfoDocument
*info
;
176 YelpInfoDocumentPrivate
*priv
;
179 g_return_val_if_fail (uri
!= NULL
, NULL
);
181 doc_uri
= yelp_uri_get_document_uri (uri
);
182 info
= (YelpInfoDocument
*) g_object_new (YELP_TYPE_INFO_DOCUMENT
,
183 "document-uri", doc_uri
,
186 priv
= GET_PRIV (info
);
188 priv
->uri
= g_object_ref (uri
);
190 return (YelpDocument
*) info
;
194 /******************************************************************************/
195 /** YelpDocument **************************************************************/
198 info_request_page (YelpDocument
*document
,
199 const gchar
*page_id
,
200 GCancellable
*cancellable
,
201 YelpDocumentCallback callback
,
204 YelpInfoDocumentPrivate
*priv
= GET_PRIV (document
);
210 page_id
= priv
->root_id
;
213 YELP_DOCUMENT_CLASS (yelp_info_document_parent_class
)->request_page (document
,
222 g_mutex_lock (priv
->mutex
);
224 switch (priv
->state
) {
225 case INFO_STATE_BLANK
:
226 priv
->state
= INFO_STATE_PARSING
;
227 priv
->process_running
= TRUE
;
228 g_object_ref (document
);
229 priv
->thread
= g_thread_create ((GThreadFunc
) info_document_process
,
230 document
, FALSE
, NULL
);
232 case INFO_STATE_PARSING
:
234 case INFO_STATE_PARSED
:
235 case INFO_STATE_STOP
:
236 docuri
= yelp_uri_get_document_uri (priv
->uri
);
237 error
= g_error_new (YELP_ERROR
, YELP_ERROR_NOT_FOUND
,
238 _("The page ‘%s’ was not found in the document ‘%s’."),
241 yelp_document_signal (document
, page_id
,
242 YELP_DOCUMENT_SIGNAL_ERROR
,
244 g_error_free (error
);
248 g_mutex_unlock (priv
->mutex
);
253 /******************************************************************************/
254 /** YelpTransform *************************************************************/
257 transform_chunk_ready (YelpTransform
*transform
,
259 YelpInfoDocument
*info
)
261 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
264 g_assert (transform
== priv
->transform
);
266 if (priv
->state
== INFO_STATE_STOP
) {
267 info_document_disconnect (info
);
271 content
= yelp_transform_take_chunk (transform
, chunk_id
);
272 yelp_document_give_contents (YELP_DOCUMENT (info
),
275 "application/xhtml+xml");
277 yelp_document_signal (YELP_DOCUMENT (info
),
279 YELP_DOCUMENT_SIGNAL_INFO
,
281 yelp_document_signal (YELP_DOCUMENT (info
),
283 YELP_DOCUMENT_SIGNAL_CONTENTS
,
288 transform_finished (YelpTransform
*transform
,
289 YelpInfoDocument
*info
)
291 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
295 g_assert (transform
== priv
->transform
);
297 if (priv
->state
== INFO_STATE_STOP
) {
298 info_document_disconnect (info
);
302 info_document_disconnect (info
);
303 priv
->state
= INFO_STATE_PARSED
;
305 /* We want to free priv->xmldoc, but we can't free it before transform
306 is finalized. Otherwise, we could crash when YelpTransform frees
307 its libxslt resources.
309 g_object_weak_ref ((GObject
*) transform
,
310 (GWeakNotify
) transform_finalized
,
313 docuri
= yelp_uri_get_document_uri (priv
->uri
);
314 error
= g_error_new (YELP_ERROR
, YELP_ERROR_NOT_FOUND
,
315 _("The requested page was not found in the document ‘%s’."),
318 yelp_document_error_pending ((YelpDocument
*) info
, error
);
319 g_error_free (error
);
323 transform_error (YelpTransform
*transform
,
324 YelpInfoDocument
*info
)
326 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
329 g_assert (transform
== priv
->transform
);
331 if (priv
->state
== INFO_STATE_STOP
) {
332 info_document_disconnect (info
);
336 error
= yelp_transform_get_error (transform
);
337 yelp_document_error_pending ((YelpDocument
*) info
, error
);
338 g_error_free (error
);
340 info_document_disconnect (info
);
344 transform_finalized (YelpInfoDocument
*info
,
347 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
350 xmlFreeDoc (priv
->xmldoc
);
356 /******************************************************************************/
357 /** Threaded ******************************************************************/
360 info_document_process (YelpInfoDocument
*info
)
362 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
364 gchar
*filepath
= NULL
;
367 gchar
**params
= NULL
;
369 file
= yelp_uri_get_file (priv
->uri
);
371 error
= g_error_new (YELP_ERROR
, YELP_ERROR_NOT_FOUND
,
372 _("The file does not exist."));
373 yelp_document_error_pending ((YelpDocument
*) info
, error
);
374 g_error_free (error
);
378 filepath
= g_file_get_path (file
);
379 g_object_unref (file
);
380 if (!g_file_test (filepath
, G_FILE_TEST_IS_REGULAR
)) {
381 error
= g_error_new (YELP_ERROR
, YELP_ERROR_NOT_FOUND
,
382 _("The file ‘%s’ does not exist."),
384 yelp_document_error_pending ((YelpDocument
*) info
, error
);
385 g_error_free (error
);
389 priv
->sections
= (GtkTreeModel
*) yelp_info_parser_parse_file (filepath
);
390 gtk_tree_model_foreach (priv
->sections
,
391 (GtkTreeModelForeachFunc
) info_sections_visit
,
393 priv
->xmldoc
= yelp_info_parser_parse_tree ((GtkTreeStore
*) priv
->sections
);
395 if (priv
->xmldoc
== NULL
) {
396 error
= g_error_new (YELP_ERROR
, YELP_ERROR_PROCESSING
,
397 _("The file ‘%s’ could not be parsed because it is"
398 " not a well-formed info page."),
400 yelp_document_error_pending ((YelpDocument
*) info
, error
);
404 g_mutex_lock (priv
->mutex
);
405 if (priv
->state
== INFO_STATE_STOP
) {
406 g_mutex_unlock (priv
->mutex
);
410 priv
->transform
= yelp_transform_new (STYLESHEET
);
412 g_signal_connect (priv
->transform
, "chunk-ready",
413 (GCallback
) transform_chunk_ready
,
416 g_signal_connect (priv
->transform
, "finished",
417 (GCallback
) transform_finished
,
420 g_signal_connect (priv
->transform
, "error",
421 (GCallback
) transform_error
,
424 params
= yelp_settings_get_all_params (yelp_settings_get_default (), 0, ¶ms_i
);
426 priv
->transform_running
= TRUE
;
427 yelp_transform_start (priv
->transform
,
430 (const gchar
* const *) params
);
432 g_mutex_unlock (priv
->mutex
);
436 priv
->process_running
= FALSE
;
437 g_object_unref (info
);
441 info_sections_visit (GtkTreeModel
*model
,
444 YelpInfoDocument
*info
)
446 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
447 gchar
*page_id
, *title
;
449 gtk_tree_model_get (model
, iter
,
450 INFO_PARSER_COLUMN_PAGE_NO
, &page_id
,
451 INFO_PARSER_COLUMN_PAGE_NAME
, &title
,
453 yelp_document_set_page_id ((YelpDocument
*) info
, page_id
, page_id
);
454 yelp_document_set_page_title ((YelpDocument
*) info
, page_id
, title
);
456 if (priv
->root_id
== NULL
) {
457 priv
->root_id
= g_strdup (page_id
);
458 yelp_document_set_page_id ((YelpDocument
*) info
, NULL
, page_id
);
460 yelp_document_set_root_id ((YelpDocument
*) info
, page_id
, priv
->root_id
);
462 if (priv
->visit_prev_id
!= NULL
) {
463 yelp_document_set_prev_id ((YelpDocument
*) info
, page_id
, priv
->visit_prev_id
);
464 yelp_document_set_next_id ((YelpDocument
*) info
, priv
->visit_prev_id
, page_id
);
465 g_free (priv
->visit_prev_id
);
467 priv
->visit_prev_id
= page_id
;
473 info_document_disconnect (YelpInfoDocument
*info
)
475 YelpInfoDocumentPrivate
*priv
= GET_PRIV (info
);
476 if (priv
->chunk_ready
) {
477 g_signal_handler_disconnect (priv
->transform
, priv
->chunk_ready
);
478 priv
->chunk_ready
= 0;
480 if (priv
->finished
) {
481 g_signal_handler_disconnect (priv
->transform
, priv
->finished
);
485 g_signal_handler_disconnect (priv
->transform
, priv
->error
);
488 yelp_transform_cancel (priv
->transform
);
489 g_object_unref (priv
->transform
);
490 priv
->transform
= NULL
;
491 priv
->transform_running
= FALSE
;