[gnome-software] Add some example docbook markup



commit 360cedd3bf56f5a112eb33915d4175b12786b32c
Author: Richard Hughes <richard hughsie com>
Date:   Sun May 22 19:00:09 2016 +0100

    Add some example docbook markup

 doc/api/gnome-software-docs.xml  |  516 ++++++++++++++++++++++++++++++++++++++
 doc/api/gs-example-details.png   |  Bin 0 -> 53714 bytes
 doc/api/gs-example-installed.png |  Bin 0 -> 106482 bytes
 doc/api/gs-example-search.png    |  Bin 0 -> 24934 bytes
 4 files changed, 516 insertions(+), 0 deletions(-)
---
diff --git a/doc/api/gnome-software-docs.xml b/doc/api/gnome-software-docs.xml
new file mode 100644
index 0000000..1b01c63
--- /dev/null
+++ b/doc/api/gnome-software-docs.xml
@@ -0,0 +1,516 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd";
+[
+  <!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
+]>
+<book id="index">
+  <bookinfo>
+    <title>GNOME Software Reference Manual</title>
+  </bookinfo>
+
+  <reference id="plugins">
+    <title>GNOME Software Plugin Tutorial</title>
+    <partintro>
+      <para>
+        GNOME Software is a software installer designed to be easy to use.
+      </para>
+
+      <section>
+        <title>Introduction</title>
+        <para>
+          At the heart of gnome software the application is just a plugin loader
+          that has some GTK UI that gets created for various result types.
+          The idea is we have lots of small plugins that each do one thing and
+          then pass the result onto the other plugins.
+          These are ordered by dependencies against each other at runtime and
+          each one can do things like editing an existing application or adding a
+          new application to the result set.
+          This is how we can add support for things like firmware updating,
+          Steam, GNOME Shell web-apps and flatpak bundles without making big
+          changes all over the source tree.
+        </para>
+        <para>
+          There are broadly 3 types of plugin methods:
+        </para>
+        <itemizedlist>
+          <listitem>
+            <para><emphasis role="strong">Actions</emphasis>: Do something on a specific GsApp</para>
+          </listitem>
+          <listitem>
+            <para><emphasis role="strong">Refine</emphasis>: Get details about a specific GsApp</para>
+          </listitem>
+          <listitem>
+            <para><emphasis role="strong">Adopt</emphasis>: Can this plugin handle this GsApp</para>
+          </listitem>
+        </itemizedlist>
+        <para>
+          In general, building things out-of-tree isn't something that I think is
+          a very good idea; the API and ABI inside gnome-software is still
+          changing and there's a huge benefit to getting plugins upstream where
+          they can undergo review and be ported as the API adapts.
+          I'm also super keen to provide configurability in GSettings for doing
+          obviously-useful things, the sort of thing Fleet Commander can set for
+          groups of users.
+        </para>
+        <para>
+          However, now we're shipping gnome-software in enterprise-class distros
+          we might want to allow customers to ship their own plugins to make
+          various business-specific changes that don't make sense upstream.
+          This might involve querying a custom LDAP server and changing the
+          suggested apps to reflect what groups the user is in, or might involve
+          showing a whole new class of applications that does not conform to the
+          Linux-specific <emphasis>application is a desktop-file</emphasis> paradigm.
+          This is where a plugin makes sense.
+        </para>
+
+        <para>
+          The plugin only needs to define the vfuncs that are required, and the
+          plugin name is taken automatically from the suffix of the
+          <filename>.so</filename> file.
+        </para>
+        <example>
+          <title>A sample plugin</title>
+          <programlisting>
+/*
+ * Copyright (C) 2016 Richard Hughes
+ */
+
+#include &lt;gnome-software.h&gt;
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+  gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "appstream");
+}
+
+gboolean
+gs_plugin_add_search (GsPlugin *plugin,
+                      gchar **values,
+                      GsAppList *list,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+  guint i;
+  for (i = 0; values[i] != NULL; i++) {
+    if (g_strcmp0 (values[i], "fotoshop") == 0) {
+      g_autoptr(GsApp) app = gs_app_new ("gimp.desktop");
+      gs_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
+      gs_app_list_add (list, app);
+    }
+  }
+  return TRUE;
+}
+          </programlisting>
+        </example>
+
+        <para>
+          We have to define when our plugin is run in reference to other plugins,
+          in this case, making sure we run before <code>appstream</code>.
+          As we're such a simple plugin we're relying on another plugin to run
+          after us to actually make the GsApp <emphasis>complete</emphasis>,
+          i.e. loading icons and setting a localised long description.
+        </para>
+        <para>
+          In this example we want to show GIMP as a result (from any provider,
+          e.g. flatpak or a distro package) when the user searches exactly for
+          <code>fotoshop</code>.
+        </para>
+        <para>
+          We can then build and install the plugin using:
+        </para>
+        <informalexample>
+          <programlisting>
+gcc -shared -o libgs_plugin_example.so gs-plugin-example.c -fPIC \
+ `pkg-config --libs --cflags gnome-software` \
+ -DI_KNOW_THE_GNOME_SOFTWARE_API_IS_SUBJECT_TO_CHANGE &amp;&amp;
+ sudo cp libgs_plugin_example.so `pkg-config gnome-software --variable=plugindir`
+          </programlisting>
+        </informalexample>
+
+        <mediaobject id="gs-example-search">
+          <imageobject>
+            <imagedata format="PNG" fileref="gs-example-search.png" align="center"/>
+          </imageobject>
+        </mediaobject>
+
+      </section>
+
+      <section>
+        <title>Distribution Specific Functionality</title>
+        <para>
+          Some plugins should only run on specific distributions, for instance
+          the <code>ubuntu-reviews</code> plugin should only be used on Ubuntu
+          systems.
+          This can be achieved with a simple runtime check using the helper
+          <code>gs_plugin_check_distro_id()</code> method or the <code>GsOsRelease</code>
+          object where more complicated rules are required.
+        </para>
+        <example>
+          <title>Self disabling on other distributions</title>
+          <programlisting>
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+  if (!gs_plugin_check_distro_id (plugin, "ubuntu")) {
+    gs_plugin_set_enabled (plugin, FALSE);
+    return;
+  }
+  /* allocate private data etc. */
+}
+          </programlisting>
+        </example>
+
+      </section>
+
+      <section>
+        <title>Custom Applications in the Installed List</title>
+        <para>
+          Next is returning custom applications in the installed list.
+          The use case here is a proprietary software distribution method that
+          installs custom files into your home directory, but you can use your
+          imagination for how this could be useful.
+          The example here is all hardcoded, and a true plugin would have to
+          derive the details about the GsApp, for example reading in an XML
+          file or YAML config file somewhere.
+        </para>
+        <example>
+          <title>Example showing a custom installed application</title>
+          <programlisting>
+#include &lt;gnome-software.h&gt;
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+  gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "icons");
+}
+
+gboolean
+gs_plugin_add_installed (GsPlugin *plugin,
+                         GsAppList *list,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+  g_autofree gchar *fn = NULL;
+  g_autoptr(GsApp) app = NULL;
+  g_autoptr(AsIcon) icon = NULL;
+
+  /* check if the app exists */
+  fn = g_build_filename (g_get_home_dir (), "chiron", NULL);
+  if (!g_file_test (fn, G_FILE_TEST_EXISTS))
+    return TRUE;
+
+  /* the trigger exists, so create a fake app */
+  app = gs_app_new ("example:chiron.desktop");
+  gs_app_set_management_plugin (app, "example");
+  gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
+  gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+  gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "Chiron");
+  gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "A teaching application");
+  gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
+        "Chiron is the name of an application.\n\n"
+        "It can be used to demo some of our features");
+
+  /* these are all optional, but make details page looks better */
+  gs_app_set_version (app, "1.2.3");
+  gs_app_set_size_installed (app, 2 * 1024 * 1024);
+  gs_app_set_size_download (app, 3 * 1024 * 1024);
+  gs_app_set_origin_ui (app, "The example plugin");
+  gs_app_add_category (app, "Game");
+  gs_app_add_category (app, "ActionGame");
+  gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
+  gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0+ and LGPL-2.1+");
+
+  /* create a stock icon which will be loaded by the 'icons' plugin */
+  icon = as_icon_new ();
+  as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+  as_icon_set_name (icon, "input-gaming");
+  gs_app_set_icon (app, icon);
+
+  /* return new app */
+  gs_app_list_add (list, app);
+
+  return TRUE;
+}
+          </programlisting>
+        </example>
+        <para>
+          This shows a lot of the plugin architecture in action. Some notable points:
+        </para>
+        <itemizedlist>
+          <listitem>
+            <para>
+              The application ID (<code>example:chiron.desktop</code>) has a
+              prefix of <code>example</code> which means we can co-exist with
+              any package or flatpak version of the Chiron application,
+              not setting the prefix would make the UI confused if more than
+              one <code>chiron.desktop</code> got added.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Setting the management plugin means we can check for this string
+              when working out if we can handle the install or remove action.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Most applications want a kind of <code>AS_APP_KIND_DESKTOP</code>
+              to be visible as an application.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The origin is where the application originated from -- usually
+              this will be something like <emphasis>Fedora Updates</emphasis>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The <code>GS_APP_KUDO_INSTALLS_USER_DOCS</code> means we get the
+              blue "Documentation" award in the details page; there are many
+              kudos to award to deserving apps.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Setting the license means we don't get the non-free warning --
+              removing the 3rd party warning can be done using
+              <code>AS_APP_QUIRK_PROVENANCE</code>
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The <code>icons</code> plugin will take the stock icon and convert
+              it to a pixbuf of the correct size.
+            </para>
+          </listitem>
+        </itemizedlist>
+        <para>
+          To show this fake application just compile and install the plugin,
+          <code>touch ~/chiron</code> and then restart gnome-software.
+          To avoid restarting <filename>gnome-software</filename> each time a
+          proper plugin would create a <code>GFileMonitor</code> object to
+          monitor files.
+        </para>
+
+        <mediaobject id="gs-example-installed">
+          <imageobject>
+            <imagedata format="PNG" fileref="gs-example-installed.png" align="center"/>
+          </imageobject>
+        </mediaobject>
+
+        <para>
+          By filling in the optional details (which can also be filled in using
+          <code>gs_plugin_refine_app()</code> you can also make the details
+          page a much more exciting place.
+          Adding a set of screenshots is left as an exercise to the reader.
+        </para>
+
+        <mediaobject id="gs-example-details">
+          <imageobject>
+            <imagedata format="PNG" fileref="gs-example-details.png" align="center"/>
+          </imageobject>
+        </mediaobject>
+
+      </section>
+
+      <section>
+        <title>Downloading Metadata and Updates</title>
+
+        <para>
+          The plugin loader supports a <code>gs_plugin_refresh()</code> vfunc that
+          is called in various situations.
+          To ensure plugins have the minimum required metadata on disk it is
+          called at startup, but with a cache age of <emphasis>infinite</emphasis>.
+          This basically means the plugin must just ensure that
+          <emphasis role="strong">any</emphasis> data exists no matter what the age.
+        </para>
+        <para>
+          Usually once per hour, we'll call <code>gs_plugin_refresh()</code> but
+          with the correct cache age set (typically a little over 24 hours) which
+          allows the plugin to download new metadata or payload files from remote
+          servers.
+          The <code>gs_utils_get_file_age()</code> utility helper can help you
+          work out the cache age of file, or the plugin can handle it some other
+          way.
+        </para>
+        <para>
+          For the Flatpak plugin we just make sure the AppStream metadata exists
+          at startup, which allows us to show search results in the UI.
+          If the metadata did not exist (e.g. if the user had added a remote
+          using the command-line without gnome-software running) then we would
+          show a loading screen with a progress bar before showing the main UI.
+          On fast connections we should only show that for a couple of seconds,
+          but it's a good idea to try any avoid that if at all possible in the
+          plugin.
+          Once per day the <code>gs_plugin_refresh()</code> method is called again,
+          but this time with <code>GS_PLUGIN_REFRESH_FLAGS_PAYLOAD</code> set.
+          This is where the Flatpak plugin would download any ostree trees (but
+          not doing the deploy step) so that the applications can be updated live
+          in the details panel without having to wait for the download to complete.
+          In a similar way, the fwupd plugin downloads the tiny LVFS metadata with
+          <code>GS_PLUGIN_REFRESH_FLAGS_METADATA</code> and then downloads the
+          large firmware files themselves only when the
+          <code>GS_PLUGIN_REFRESH_FLAGS_PAYLOAD</code> flag is set.
+        </para>
+        <para>
+          If the <code>@app</code> parameter is set for
+          <code>gs_plugin_download_file()</code> then the progress of the download
+          is automatically proxied to the UI elements associated with the
+          application, for instance the install button would show a progress bar
+          in the various different places in the UI.
+          For a refresh there's no relevant GsApp to use, so we'll leave it
+          <code>NULL</code> which means something is happening globally which the
+          UI can handle how it wants, for instance showing a loading page at startup.
+        </para>
+        <para>
+          Note, if the downloading fails it's okay to return <code>FALSE</code>;
+          the plugin loader continues to run all plugins and just logs an error
+          to the console. We'll be calling into <code>gs_plugin_refresh()</code>
+          again in only another hour, so there's no need to bother the user.
+          For actions like <code>gs_plugin_app_install</code> we also do the same
+          thing, but we also save the error on the GsApp itself so that the UI is
+          free to handle that how it wants, for instance showing a GtkDialog
+          window for example.
+        </para>
+        <example>
+          <title>Refresh example</title>
+          <programlisting>
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+                   guint cache_age,
+                   GsPluginRefreshFlags flags,
+                   GCancellable *cancellable,
+                   GError **error)
+{
+  const gchar *metadata_fn = "/var/cache/example/metadata.xml";
+  const gchar *metadata_url = "http://www.example.com/new.xml";;
+
+  /* this is called at startup and once per day */
+  if (flags &amp; GS_PLUGIN_REFRESH_FLAGS_METADATA) {
+    g_autoptr(GFile) file = g_file_new_for_path (metadata_fn);
+
+    /* is the metadata missing or too old */
+    if (gs_utils_get_file_age (file) &gt; cache_age) {
+      if (!gs_plugin_download_file (plugin,
+                                    NULL,
+                                    metadata_url,
+                                    metadata_fn,
+                                    cancellable,
+                                    error)) {
+        /* it's okay to fail here */
+        return FALSE;
+      }
+      g_debug ("successfully downloaded new metadata");
+    }
+  }
+
+  /* this is called when the session is idle */
+  if ((flags &amp; GS_PLUGIN_REFRESH_FLAGS_PAYLOAD) == 0) {
+    // FIXME: download any required updates now
+  }
+  return TRUE;
+}
+          </programlisting>
+        </example>
+      </section>
+
+      <section>
+        <title>Adding Application Information Using Refine</title>
+
+        <para>
+          As previous examples have shown it's very easy to add a new
+          application to the search results, updates list or installed list.
+          Some plugins don't want to add more applications, but want to modify
+          existing applications to add more information depending on what is
+          required by the UI code.
+          The reason we don't just add everything at once is that for
+          search-as-you-type to work effectively we need to return results in
+          less than about 50ms and querying some data can take a long time.
+          For example, it might take a few hundred ms to work out the download
+          size for an application when a plugin has to also look at what
+          dependencies are already installed.
+          We only need this information once the user has clicked the search
+          results and when the user is in the details panel, so we can save a
+          ton of time not working out properties that are not useful.
+        </para>
+        <example>
+          <title>Refine example</title>
+          <programlisting>
+gboolean
+gs_plugin_refine_app (GsPlugin *plugin,
+                      GsApp *app,
+                      GsPluginRefineFlags flags,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+  /* not required */
+  if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) == 0)
+    return TRUE;
+
+  /* already set */
+  if (gs_app_get_license (app) != NULL)
+    return TRUE;
+
+  /* FIXME, not just hardcoded! */
+  if (g_strcmp0 (gs_app_get_id (app, "chiron.desktop") == 0))
+    gs_app_set_license (app, "GPL-2.0 and LGPL-2.0+");
+
+  return TRUE;
+}
+          </programlisting>
+        </example>
+        <para>
+          This is a simple example, but shows what a plugin needs to do.
+          It first checks if the action is required, in this case
+          <code>GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE</code>.
+          This request is more common than you might expect as even the search
+          results shows a non-free label if the license is unspecified or
+          non-free.
+          It then checks if the license is already set, returning with success
+          if so.
+          If not, it checks the application ID and hardcodes a license; in the
+          real world this would be querying a database or parsing an additional
+          config file.
+          As mentioned before, if the license value is freely available without
+          any extra work then it's best just to set this at the same time as
+          when adding the app with <code>gs_app_list_add()</code>.
+          Think of refine as <emphasis>adding things that cost time to
+          calculate only when really required</emphasis>.
+        </para>
+        <para>
+          The UI in gnome-software is quite forgiving for missing data, hiding
+          sections or labels as required.
+          Some things are required however, and forgetting to assign an icon or
+          short description will get the application vetoed so that it's not
+          displayed at all.
+          Helpfully, running <code>gnome-software --verbose</code> on the
+          command line will tell you why an application isn't shown along with
+          any extra data.
+        </para>
+
+      </section>
+
+      <section>
+        <title>Adopting AppStream Applications</title>
+
+        <para>
+          TODO: Example for adopt
+        </para>
+
+      </section>
+
+      <section>
+        <title>Using The Plugin Cache</title>
+
+        <para>
+          TODO: Example for using the cache
+        </para>
+
+      </section>
+
+    </partintro>
+  </reference>
+
+</book>
diff --git a/doc/api/gs-example-details.png b/doc/api/gs-example-details.png
new file mode 100644
index 0000000..88c29e4
Binary files /dev/null and b/doc/api/gs-example-details.png differ
diff --git a/doc/api/gs-example-installed.png b/doc/api/gs-example-installed.png
new file mode 100644
index 0000000..0cd424a
Binary files /dev/null and b/doc/api/gs-example-installed.png differ
diff --git a/doc/api/gs-example-search.png b/doc/api/gs-example-search.png
new file mode 100644
index 0000000..77611bd
Binary files /dev/null and b/doc/api/gs-example-search.png differ


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]