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-2020 Shaun McCance <shaunm@gnome.org>
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Don Scorgie <dscorgie@svn.gnome.org>
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29 #include <libxml/tree.h>
30
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"
37
38 #define STYLESHEET DATADIR"/yelp/xslt/info2html.xsl"
39
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;
46
47 typedef struct _YelpInfoDocumentPrivate YelpInfoDocumentPrivate;
48 struct _YelpInfoDocumentPrivate {
49 InfoState state;
50
51 GMutex mutex;
52 GThread *thread;
53
54 xmlDocPtr xmldoc;
55 GtkTreeModel *sections;
56
57 gboolean process_running;
58 gboolean transform_running;
59
60 YelpTransform *transform;
61 guint chunk_ready;
62 guint finished;
63 guint error;
64
65 gchar *root_id;
66 gchar *visit_prev_id;
67 };
68
69 G_DEFINE_TYPE_WITH_PRIVATE (YelpInfoDocument, yelp_info_document, YELP_TYPE_DOCUMENT)
70
71 static void yelp_info_document_dispose (GObject *object);
72 static void yelp_info_document_finalize (GObject *object);
73
74 /* YelpDocument */
75 static gboolean info_request_page (YelpDocument *document,
76 const gchar *page_id,
77 GCancellable *cancellable,
78 YelpDocumentCallback callback,
79 gpointer user_data,
80 GDestroyNotify notify);
81
82 /* YelpTransform */
83 static void transform_chunk_ready (YelpTransform *transform,
84 gchar *chunk_id,
85 YelpInfoDocument *info);
86 static void transform_finished (YelpTransform *transform,
87 YelpInfoDocument *info);
88 static void transform_error (YelpTransform *transform,
89 YelpInfoDocument *info);
90 static void transform_finalized (YelpInfoDocument *info,
91 gpointer transform);
92
93 static void info_document_process (YelpInfoDocument *info);
94 static gboolean info_sections_visit (GtkTreeModel *model,
95 GtkTreePath *path,
96 GtkTreeIter *iter,
97 YelpInfoDocument *info);
98 static void info_document_disconnect (YelpInfoDocument *info);
99
100
101 static void
yelp_info_document_class_init(YelpInfoDocumentClass * klass)102 yelp_info_document_class_init (YelpInfoDocumentClass *klass)
103 {
104 GObjectClass *object_class = G_OBJECT_CLASS (klass);
105 YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass);
106
107 object_class->dispose = yelp_info_document_dispose;
108 object_class->finalize = yelp_info_document_finalize;
109
110 document_class->request_page = info_request_page;
111 }
112
113 static void
yelp_info_document_init(YelpInfoDocument * info)114 yelp_info_document_init (YelpInfoDocument *info)
115 {
116 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
117
118 priv->state = INFO_STATE_BLANK;
119 priv->xmldoc = NULL;
120 g_mutex_init (&priv->mutex);
121 }
122
123 static void
yelp_info_document_dispose(GObject * object)124 yelp_info_document_dispose (GObject *object)
125 {
126 YelpInfoDocumentPrivate *priv =
127 yelp_info_document_get_instance_private (YELP_INFO_DOCUMENT (object));
128
129 if (priv->sections) {
130 g_object_unref (priv->sections);
131 priv->sections = NULL;
132 }
133
134 if (priv->transform) {
135 g_object_unref (priv->transform);
136 priv->transform = NULL;
137 }
138
139 G_OBJECT_CLASS (yelp_info_document_parent_class)->dispose (object);
140 }
141
142 static void
yelp_info_document_finalize(GObject * object)143 yelp_info_document_finalize (GObject *object)
144 {
145 YelpInfoDocumentPrivate *priv =
146 yelp_info_document_get_instance_private (YELP_INFO_DOCUMENT (object));
147
148 if (priv->xmldoc)
149 xmlFreeDoc (priv->xmldoc);
150
151 g_free (priv->root_id);
152 g_free (priv->visit_prev_id);
153
154 g_mutex_clear (&priv->mutex);
155
156 G_OBJECT_CLASS (yelp_info_document_parent_class)->finalize (object);
157 }
158
159 /******************************************************************************/
160
161 YelpDocument *
yelp_info_document_new(YelpUri * uri)162 yelp_info_document_new (YelpUri *uri)
163 {
164 g_return_val_if_fail (uri != NULL, NULL);
165
166 return (YelpDocument *) g_object_new (YELP_TYPE_INFO_DOCUMENT,
167 "document-uri", uri,
168 NULL);
169 }
170
171
172 /******************************************************************************/
173 /** YelpDocument **************************************************************/
174
175 static gboolean
info_request_page(YelpDocument * document,const gchar * page_id,GCancellable * cancellable,YelpDocumentCallback callback,gpointer user_data,GDestroyNotify notify)176 info_request_page (YelpDocument *document,
177 const gchar *page_id,
178 GCancellable *cancellable,
179 YelpDocumentCallback callback,
180 gpointer user_data,
181 GDestroyNotify notify)
182 {
183 YelpInfoDocumentPrivate *priv =
184 yelp_info_document_get_instance_private (YELP_INFO_DOCUMENT (document));
185 gchar *docuri;
186 GError *error;
187 gboolean handled;
188
189 if (page_id == NULL)
190 page_id = priv->root_id;
191
192 handled =
193 YELP_DOCUMENT_CLASS (yelp_info_document_parent_class)->request_page (document,
194 page_id,
195 cancellable,
196 callback,
197 user_data,
198 notify);
199 if (handled) {
200 return TRUE;
201 }
202
203 g_mutex_lock (&priv->mutex);
204
205 switch (priv->state) {
206 case INFO_STATE_BLANK:
207 priv->state = INFO_STATE_PARSING;
208 priv->process_running = TRUE;
209 g_object_ref (document);
210 priv->thread = g_thread_new ("info-page",
211 (GThreadFunc)(GCallback) info_document_process,
212 document);
213 break;
214 case INFO_STATE_PARSING:
215 break;
216 case INFO_STATE_PARSED:
217 case INFO_STATE_STOP:
218 docuri = yelp_uri_get_document_uri (yelp_document_get_uri (document));
219 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
220 _("The page ‘%s’ was not found in the document ‘%s’."),
221 page_id, docuri);
222 g_free (docuri);
223 yelp_document_signal (document, page_id,
224 YELP_DOCUMENT_SIGNAL_ERROR,
225 error);
226 g_error_free (error);
227 break;
228 default:
229 g_assert_not_reached ();
230 break;
231 }
232
233 g_mutex_unlock (&priv->mutex);
234 return TRUE;
235 }
236
237
238 /******************************************************************************/
239 /** YelpTransform *************************************************************/
240
241 static void
transform_chunk_ready(YelpTransform * transform,gchar * chunk_id,YelpInfoDocument * info)242 transform_chunk_ready (YelpTransform *transform,
243 gchar *chunk_id,
244 YelpInfoDocument *info)
245 {
246 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
247 gchar *content;
248
249 g_assert (transform == priv->transform);
250
251 if (priv->state == INFO_STATE_STOP) {
252 info_document_disconnect (info);
253 return;
254 }
255
256 content = yelp_transform_take_chunk (transform, chunk_id);
257 yelp_document_give_contents (YELP_DOCUMENT (info),
258 chunk_id,
259 content,
260 "application/xhtml+xml");
261
262 yelp_document_signal (YELP_DOCUMENT (info),
263 chunk_id,
264 YELP_DOCUMENT_SIGNAL_INFO,
265 NULL);
266 yelp_document_signal (YELP_DOCUMENT (info),
267 chunk_id,
268 YELP_DOCUMENT_SIGNAL_CONTENTS,
269 NULL);
270 }
271
272 static void
transform_finished(YelpTransform * transform,YelpInfoDocument * info)273 transform_finished (YelpTransform *transform,
274 YelpInfoDocument *info)
275 {
276 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
277 gchar *docuri;
278 GError *error;
279
280 g_assert (transform == priv->transform);
281
282 if (priv->state == INFO_STATE_STOP) {
283 info_document_disconnect (info);
284 return;
285 }
286
287 info_document_disconnect (info);
288 priv->state = INFO_STATE_PARSED;
289
290 /* We want to free priv->xmldoc, but we can't free it before transform
291 is finalized. Otherwise, we could crash when YelpTransform frees
292 its libxslt resources.
293 */
294 g_object_weak_ref ((GObject *) transform,
295 (GWeakNotify) transform_finalized,
296 info);
297
298 docuri = yelp_uri_get_document_uri (yelp_document_get_uri ((YelpDocument *) info));
299 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
300 _("The requested page was not found in the document ‘%s’."),
301 docuri);
302 g_free (docuri);
303 yelp_document_error_pending ((YelpDocument *) info, error);
304 g_error_free (error);
305 }
306
307 static void
transform_error(YelpTransform * transform,YelpInfoDocument * info)308 transform_error (YelpTransform *transform,
309 YelpInfoDocument *info)
310 {
311 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
312 GError *error;
313
314 g_assert (transform == priv->transform);
315
316 if (priv->state == INFO_STATE_STOP) {
317 info_document_disconnect (info);
318 return;
319 }
320
321 error = yelp_transform_get_error (transform);
322 yelp_document_error_pending ((YelpDocument *) info, error);
323 g_error_free (error);
324
325 info_document_disconnect (info);
326 }
327
328 static void
transform_finalized(YelpInfoDocument * info,gpointer transform)329 transform_finalized (YelpInfoDocument *info,
330 gpointer transform)
331 {
332 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
333
334 if (priv->xmldoc)
335 xmlFreeDoc (priv->xmldoc);
336 priv->xmldoc = NULL;
337 }
338
339
340
341 /******************************************************************************/
342 /** Threaded ******************************************************************/
343
344 static void
info_document_process(YelpInfoDocument * info)345 info_document_process (YelpInfoDocument *info)
346 {
347 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
348 GFile *file = NULL;
349 gchar *filepath = NULL;
350 GError *error;
351 gint params_i = 0;
352 gchar **params = NULL;
353
354 file = yelp_uri_get_file (yelp_document_get_uri ((YelpDocument *) info));
355 if (file == NULL) {
356 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
357 _("The file does not exist."));
358 yelp_document_error_pending ((YelpDocument *) info, error);
359 g_error_free (error);
360 goto done;
361 }
362
363 filepath = g_file_get_path (file);
364 g_object_unref (file);
365 if (!g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) {
366 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
367 _("The file ‘%s’ does not exist."),
368 filepath);
369 yelp_document_error_pending ((YelpDocument *) info, error);
370 g_error_free (error);
371 goto done;
372 }
373
374 priv->sections = (GtkTreeModel *) yelp_info_parser_parse_file (filepath);
375 gtk_tree_model_foreach (priv->sections,
376 (GtkTreeModelForeachFunc) info_sections_visit,
377 info);
378 priv->xmldoc = yelp_info_parser_parse_tree ((GtkTreeStore *) priv->sections);
379
380 if (priv->xmldoc == NULL) {
381 error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
382 _("The file ‘%s’ could not be parsed because it is"
383 " not a well-formed info page."),
384 filepath);
385 yelp_document_error_pending ((YelpDocument *) info, error);
386 goto done;
387 }
388
389 g_mutex_lock (&priv->mutex);
390 if (priv->state == INFO_STATE_STOP) {
391 g_mutex_unlock (&priv->mutex);
392 goto done;
393 }
394
395 priv->transform = yelp_transform_new (STYLESHEET);
396 priv->chunk_ready =
397 g_signal_connect (priv->transform, "chunk-ready",
398 (GCallback) transform_chunk_ready,
399 info);
400 priv->finished =
401 g_signal_connect (priv->transform, "finished",
402 (GCallback) transform_finished,
403 info);
404 priv->error =
405 g_signal_connect (priv->transform, "error",
406 (GCallback) transform_error,
407 info);
408
409 params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, ¶ms_i);
410
411 priv->transform_running = TRUE;
412 yelp_transform_start (priv->transform,
413 priv->xmldoc,
414 NULL,
415 (const gchar * const *) params);
416 g_strfreev (params);
417 g_mutex_unlock (&priv->mutex);
418
419 done:
420 g_free (filepath);
421 priv->process_running = FALSE;
422 g_object_unref (info);
423 }
424
425 static gboolean
info_sections_visit(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,YelpInfoDocument * info)426 info_sections_visit (GtkTreeModel *model,
427 GtkTreePath *path,
428 GtkTreeIter *iter,
429 YelpInfoDocument *info)
430 {
431 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
432 gchar *page_id, *title;
433
434 gtk_tree_model_get (model, iter,
435 INFO_PARSER_COLUMN_PAGE_NO, &page_id,
436 INFO_PARSER_COLUMN_PAGE_NAME, &title,
437 -1);
438 yelp_document_set_page_id ((YelpDocument *) info, page_id, page_id);
439 yelp_document_set_page_title ((YelpDocument *) info, page_id, title);
440
441 if (priv->root_id == NULL) {
442 priv->root_id = g_strdup (page_id);
443 yelp_document_set_page_id ((YelpDocument *) info, NULL, page_id);
444 }
445 yelp_document_set_root_id ((YelpDocument *) info, page_id, priv->root_id);
446
447 if (priv->visit_prev_id != NULL) {
448 yelp_document_set_prev_id ((YelpDocument *) info, page_id, priv->visit_prev_id);
449 yelp_document_set_next_id ((YelpDocument *) info, priv->visit_prev_id, page_id);
450 g_free (priv->visit_prev_id);
451 }
452 priv->visit_prev_id = page_id;
453 g_free (title);
454 return FALSE;
455 }
456
457 static void
info_document_disconnect(YelpInfoDocument * info)458 info_document_disconnect (YelpInfoDocument *info)
459 {
460 YelpInfoDocumentPrivate *priv = yelp_info_document_get_instance_private (info);
461 if (priv->chunk_ready) {
462 g_signal_handler_disconnect (priv->transform, priv->chunk_ready);
463 priv->chunk_ready = 0;
464 }
465 if (priv->finished) {
466 g_signal_handler_disconnect (priv->transform, priv->finished);
467 priv->finished = 0;
468 }
469 if (priv->error) {
470 g_signal_handler_disconnect (priv->transform, priv->error);
471 priv->error = 0;
472 }
473 yelp_transform_cancel (priv->transform);
474 g_object_unref (priv->transform);
475 priv->transform = NULL;
476 priv->transform_running = FALSE;
477 }
478