/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * Copyright (C) 2010-2020 Shaun McCance * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, see . * * Author: Shaun McCance */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "yelp-help-list.h" #include "yelp-settings.h" #include "yelp-transform.h" #define STYLESHEET DATADIR"/yelp/xslt/links2html.xsl" typedef struct _HelpListEntry HelpListEntry; static void yelp_help_list_dispose (GObject *object); static void yelp_help_list_finalize (GObject *object); static gboolean help_list_request_page (YelpDocument *document, const gchar *page_id, GCancellable *cancellable, YelpDocumentCallback callback, gpointer user_data, GDestroyNotify notify); static void help_list_think (YelpHelpList *list); static void help_list_handle_page (YelpHelpList *list, const gchar *page_id); static void help_list_process_docbook (YelpHelpList *list, HelpListEntry *entry); static void help_list_process_mallard (YelpHelpList *list, HelpListEntry *entry); static const char*const known_vendor_prefixes[] = { "gnome", "fedora", "mozilla", NULL }; struct _HelpListEntry { gchar *id; gchar *title; gchar *desc; gchar *icon; gchar *filename; YelpUriDocumentType type; }; static void help_list_entry_free (HelpListEntry *entry) { g_free (entry->id); g_free (entry->title); g_free (entry->desc); g_free (entry); } static gint help_list_entry_cmp (HelpListEntry *a, HelpListEntry *b) { gchar *as, *bs; as = a->title ? a->title : strchr (a->id, ':') + 1; bs = b->title ? b->title : strchr (b->id, ':') + 1; return g_utf8_collate (as, bs); } typedef struct _YelpHelpListPrivate YelpHelpListPrivate; struct _YelpHelpListPrivate { GMutex mutex; GThread *thread; gboolean process_running; gboolean process_ran; GHashTable *entries; GList *all_entries; GSList *pending; xmlDocPtr entriesdoc; YelpTransform *transform; xmlXPathCompExprPtr get_docbook_title; xmlXPathCompExprPtr get_mallard_title; xmlXPathCompExprPtr get_mallard_desc; }; G_DEFINE_TYPE_WITH_PRIVATE (YelpHelpList, yelp_help_list, YELP_TYPE_DOCUMENT) static void yelp_help_list_class_init (YelpHelpListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass); object_class->dispose = yelp_help_list_dispose; object_class->finalize = yelp_help_list_finalize; document_class->request_page = help_list_request_page; } static void yelp_help_list_init (YelpHelpList *list) { YelpHelpListPrivate *priv = yelp_help_list_get_instance_private (list); g_mutex_init (&priv->mutex); priv->entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) help_list_entry_free); priv->get_docbook_title = xmlXPathCompile (BAD_CAST "normalize-space(" "( /*/title | /*/db:title" "| /*/articleinfo/title" "| /*/bookinfo/title" "| /*/db:info/db:title" ")[1])"); priv->get_mallard_title = xmlXPathCompile (BAD_CAST "normalize-space((/mal:page/mal:info/mal:title[@type='text'] |" " /mal:page/mal:title)[1])"); priv->get_mallard_desc = xmlXPathCompile (BAD_CAST "normalize-space(/mal:page/mal:info/mal:desc[1])"); yelp_document_set_page_id ((YelpDocument *) list, NULL, "index"); yelp_document_set_page_id ((YelpDocument *) list, "index", "index"); } static void yelp_help_list_dispose (GObject *object) { G_OBJECT_CLASS (yelp_help_list_parent_class)->dispose (object); } static void yelp_help_list_finalize (GObject *object) { YelpHelpListPrivate *priv = yelp_help_list_get_instance_private (YELP_HELP_LIST (object)); g_hash_table_destroy (priv->entries); g_mutex_clear (&priv->mutex); if (priv->entriesdoc) { xmlFreeDoc (priv->entriesdoc); priv->entriesdoc = NULL; } if (priv->get_docbook_title) xmlXPathFreeCompExpr (priv->get_docbook_title); if (priv->get_mallard_title) xmlXPathFreeCompExpr (priv->get_mallard_title); if (priv->get_mallard_desc) xmlXPathFreeCompExpr (priv->get_mallard_desc); g_clear_object (&priv->transform); G_OBJECT_CLASS (yelp_help_list_parent_class)->finalize (object); } YelpDocument * yelp_help_list_new (YelpUri *uri) { return g_object_new (YELP_TYPE_HELP_LIST, NULL); } /******************************************************************************/ static gboolean help_list_request_page (YelpDocument *document, const gchar *page_id, GCancellable *cancellable, YelpDocumentCallback callback, gpointer user_data, GDestroyNotify notify) { gboolean handled; YelpHelpListPrivate *priv = yelp_help_list_get_instance_private (YELP_HELP_LIST (document)); if (page_id == NULL) page_id = "index"; handled = YELP_DOCUMENT_CLASS (yelp_help_list_parent_class)->request_page (document, page_id, cancellable, callback, user_data, notify); if (handled) { return TRUE; } g_mutex_lock (&priv->mutex); if (priv->process_ran) { help_list_handle_page ((YelpHelpList *) document, page_id); return TRUE; } if (!priv->process_running) { priv->process_running = TRUE; g_object_ref (document); priv->thread = g_thread_new ("helplist-page", (GThreadFunc)(GCallback) help_list_think, document); } priv->pending = g_slist_prepend (priv->pending, g_strdup (page_id)); g_mutex_unlock (&priv->mutex); return TRUE; } static void help_list_think (YelpHelpList *list) { const gchar * const *sdatadirs = g_get_system_data_dirs (); const gchar * const *langs = g_get_language_names (); YelpHelpListPrivate *priv = yelp_help_list_get_instance_private (list); /* The strings are still owned by GLib; we just own the array. */ gchar **datadirs; gint datadir_i, lang_i; GList *cur; GtkIconTheme *theme; datadirs = g_new0 (gchar *, g_strv_length ((gchar **) sdatadirs) + 2); datadirs[0] = (gchar *) g_get_user_data_dir (); for (datadir_i = 0; sdatadirs[datadir_i]; datadir_i++) datadirs[datadir_i + 1] = (gchar *) sdatadirs[datadir_i]; for (datadir_i = 0; datadirs[datadir_i]; datadir_i++) { gchar *helpdirname = g_build_filename (datadirs[datadir_i], "gnome", "help", NULL); GFile *helpdir = g_file_new_for_path (helpdirname); GFileEnumerator *children = g_file_enumerate_children (helpdir, G_FILE_ATTRIBUTE_STANDARD_TYPE"," G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL); GFileInfo *child; if (children == NULL) { g_object_unref (helpdir); g_free (helpdirname); continue; } while ((child = g_file_enumerator_next_file (children, NULL, NULL))) { gchar *docid; HelpListEntry *entry = NULL; if (g_file_info_get_file_type (child) != G_FILE_TYPE_DIRECTORY) { g_object_unref (child); continue; } docid = g_strconcat ("ghelp:", g_file_info_get_name (child), NULL); if (g_hash_table_lookup (priv->entries, docid)) { g_free (docid); g_object_unref (child); continue; } for (lang_i = 0; langs[lang_i]; lang_i++) { gchar *filename, *tmp; filename = g_build_filename (helpdirname, g_file_info_get_name (child), langs[lang_i], "index.page", NULL); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { entry = g_new0 (HelpListEntry, 1); entry->id = g_strdup (docid); entry->filename = filename; entry->type = YELP_URI_DOCUMENT_TYPE_MALLARD; break; } g_free (filename); tmp = g_strdup_printf ("%s.xml", g_file_info_get_name (child)); filename = g_build_filename (helpdirname, g_file_info_get_name (child), langs[lang_i], tmp, NULL); g_free (tmp); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { entry = g_new0 (HelpListEntry, 1); entry->id = g_strdup (docid); entry->filename = filename; entry->type = YELP_URI_DOCUMENT_TYPE_DOCBOOK; break; } g_free (filename); } if (entry != NULL) { g_hash_table_insert (priv->entries, docid, entry); priv->all_entries = g_list_prepend (priv->all_entries, entry); } else g_free (docid); g_object_unref (child); } g_object_unref (children); g_object_unref (helpdir); g_free (helpdirname); } for (datadir_i = 0; datadirs[datadir_i]; datadir_i++) { for (lang_i = 0; langs[lang_i]; lang_i++) { gchar *langdirname = g_build_filename (datadirs[datadir_i], "help", langs[lang_i], NULL); GFile *langdir = g_file_new_for_path (langdirname); GFileEnumerator *children = g_file_enumerate_children (langdir, G_FILE_ATTRIBUTE_STANDARD_TYPE"," G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL); GFileInfo *child; if (children == NULL) { g_object_unref (langdir); g_free (langdirname); continue; } while ((child = g_file_enumerator_next_file (children, NULL, NULL))) { gchar *docid, *filename; HelpListEntry *entry = NULL; if (g_file_info_get_file_type (child) != G_FILE_TYPE_DIRECTORY) { g_object_unref (child); continue; } docid = g_strconcat ("help:", g_file_info_get_name (child), NULL); if (g_hash_table_lookup (priv->entries, docid) != NULL) { g_free (docid); continue; } filename = g_build_filename (langdirname, g_file_info_get_name (child), "index.page", NULL); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { entry = g_new0 (HelpListEntry, 1); entry->id = docid; entry->filename = filename; entry->type = YELP_URI_DOCUMENT_TYPE_MALLARD; goto found; } g_free (filename); filename = g_build_filename (langdirname, g_file_info_get_name (child), "index.docbook", NULL); if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { entry = g_new0 (HelpListEntry, 1); entry->id = docid; entry->filename = filename; entry->type = YELP_URI_DOCUMENT_TYPE_DOCBOOK; goto found; } g_free (filename); g_free (docid); found: g_object_unref (child); if (entry != NULL) { g_hash_table_insert (priv->entries, docid, entry); priv->all_entries = g_list_prepend (priv->all_entries, entry); } } g_object_unref (children); } } g_free (datadirs); theme = gtk_icon_theme_get_default (); for (cur = priv->all_entries; cur != NULL; cur = cur->next) { GDesktopAppInfo *app; gchar *tmp; HelpListEntry *entry = (HelpListEntry *) cur->data; const gchar *entryid = strchr (entry->id, ':') + 1; if (entry->type == YELP_URI_DOCUMENT_TYPE_MALLARD) help_list_process_mallard (list, entry); else if (entry->type == YELP_URI_DOCUMENT_TYPE_DOCBOOK) help_list_process_docbook (list, entry); if (g_str_equal (entryid, "gnome-terminal")) { tmp = g_strconcat ("org.gnome.Terminal", ".desktop", NULL); } else { tmp = g_strconcat (entryid, ".desktop", NULL); } app = g_desktop_app_info_new (tmp); g_free (tmp); if (app == NULL) { char **prefix; for (prefix = (char **) known_vendor_prefixes; *prefix; prefix++) { tmp = g_strconcat (*prefix, "-", entryid, ".desktop", NULL); app = g_desktop_app_info_new (tmp); g_free (tmp); if (app) break; } } if (app != NULL) { GIcon *icon = g_app_info_get_icon ((GAppInfo *) app); if (icon != NULL) { GtkIconInfo *info = gtk_icon_theme_lookup_by_gicon (theme, icon, 48, 0); if (info != NULL) { const gchar *iconfile = gtk_icon_info_get_filename (info); if (iconfile) entry->icon = g_filename_to_uri (iconfile, NULL, NULL); g_object_unref (info); } } g_object_unref (app); } } g_mutex_lock (&priv->mutex); priv->process_running = FALSE; priv->process_ran = TRUE; while (priv->pending) { gchar *page_id = (gchar *) priv->pending->data; help_list_handle_page (list, page_id); g_free (page_id); priv->pending = g_slist_delete_link (priv->pending, priv->pending); } g_mutex_unlock (&priv->mutex); g_object_unref (list); } static void transform_chunk_ready (YelpTransform *transform, gchar *chunk_id, YelpHelpList *list) { gchar *content; content = yelp_transform_take_chunk (transform, chunk_id); yelp_document_give_contents (YELP_DOCUMENT (list), chunk_id, content, "application/xhtml+xml"); yelp_document_signal (YELP_DOCUMENT (list), chunk_id, YELP_DOCUMENT_SIGNAL_CONTENTS, NULL); } /* This function expects to be called inside a locked mutex */ static void help_list_handle_page (YelpHelpList *list, const gchar *page_id) { YelpHelpListPrivate *priv = yelp_help_list_get_instance_private (list); xmlNodePtr rootnode; GList *entrycur; gchar **params = NULL; priv->entriesdoc = xmlNewDoc (BAD_CAST "1.0"); rootnode = xmlNewDocNode (priv->entriesdoc, NULL, BAD_CAST "links", NULL); xmlDocSetRootElement (priv->entriesdoc, rootnode); xmlNewTextChild (rootnode, NULL, BAD_CAST "title", BAD_CAST _("All Help Documents")); priv->all_entries = g_list_sort (priv->all_entries, (GCompareFunc) help_list_entry_cmp); for (entrycur = priv->all_entries; entrycur != NULL; entrycur = entrycur->next) { xmlNodePtr linknode, curnode; HelpListEntry *entry = (HelpListEntry *) entrycur->data; linknode = xmlNewChild (rootnode, NULL, BAD_CAST "link", NULL); xmlSetProp (linknode, BAD_CAST "href", BAD_CAST entry->id); xmlNewTextChild (linknode, NULL, BAD_CAST "title", BAD_CAST entry->title); xmlNewTextChild (linknode, NULL, BAD_CAST "desc", BAD_CAST entry->desc); curnode = xmlNewChild (linknode, NULL, BAD_CAST "thumb", NULL); xmlSetProp (curnode, BAD_CAST "src", BAD_CAST entry->icon); } params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, NULL); priv->transform = yelp_transform_new (STYLESHEET); g_signal_connect (priv->transform, "chunk-ready", (GCallback) transform_chunk_ready, list); yelp_transform_start (priv->transform, priv->entriesdoc, NULL, (const gchar * const *) params); g_strfreev (params); } static void help_list_process_docbook (YelpHelpList *list, HelpListEntry *entry) { xmlParserCtxtPtr parserCtxt; xmlDocPtr xmldoc; xmlXPathContextPtr xpath; xmlXPathObjectPtr obj = NULL; YelpHelpListPrivate *priv = yelp_help_list_get_instance_private (list); parserCtxt = xmlNewParserCtxt (); xmldoc = xmlCtxtReadFile (parserCtxt, (const char *) entry->filename, NULL, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ); xmlFreeParserCtxt (parserCtxt); if (xmldoc == NULL) return; xmlXIncludeProcessFlags (xmldoc, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ); xpath = xmlXPathNewContext (xmldoc); xmlXPathRegisterNs (xpath, BAD_CAST "db", BAD_CAST "http://docbook.org/ns/docbook"); obj = xmlXPathCompiledEval (priv->get_docbook_title, xpath); if (obj) { if (obj->stringval) entry->title = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); } if (xmldoc) xmlFreeDoc (xmldoc); if (xpath) xmlXPathFreeContext (xpath); } static void help_list_process_mallard (YelpHelpList *list, HelpListEntry *entry) { xmlParserCtxtPtr parserCtxt; xmlDocPtr xmldoc; xmlXPathContextPtr xpath; xmlXPathObjectPtr obj = NULL; YelpHelpListPrivate *priv = yelp_help_list_get_instance_private (list); parserCtxt = xmlNewParserCtxt (); xmldoc = xmlCtxtReadFile (parserCtxt, (const char *) entry->filename, NULL, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ); xmlFreeParserCtxt (parserCtxt); if (xmldoc == NULL) return; xmlXIncludeProcessFlags (xmldoc, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ); xpath = xmlXPathNewContext (xmldoc); xmlXPathRegisterNs (xpath, BAD_CAST "mal", BAD_CAST "http://projectmallard.org/1.0/"); obj = xmlXPathCompiledEval (priv->get_mallard_title, xpath); if (obj) { if (obj->stringval) entry->title = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); } obj = xmlXPathCompiledEval (priv->get_mallard_desc, xpath); if (obj) { if (obj->stringval) entry->desc = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); } if (xmldoc) xmlFreeDoc (xmldoc); if (xpath) xmlXPathFreeContext (xpath); }