Mercurial > projects > package-update-indicator
changeset 0:6884bb8130ca
Initial revision
author | Guido Berhoerster <guido+pui@berhoerster.name> |
---|---|
date | Sun, 20 May 2018 11:32:57 +0200 |
parents | |
children | 2f04ec9e0506 |
files | Makefile README deps.sed docbook-update-source-data.xsl org.guido-berhoerster.code.package-update-indicator.desktop.in org.guido-berhoerster.code.package-update-indicator.gschema.xml package-update-indicator.1.xml package-update-indicator.c po/LINGUAS po/POTFILES.in pui-application.c pui-application.h pui-backend.c pui-backend.h pui-common.h pui-get-updates.c pui-get-updates.h pui-types.c pui-types.h |
diffstat | 18 files changed, 2276 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,204 @@ +# +# Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +PACKAGE = package-update-indicator +APPLICATION_ID = org.guido-berhoerster.code.package-update-indicator +VERSION = 1 +DISTNAME = $(PACKAGE)-$(VERSION) +AUTHOR = Guido Berhoerster +BUG_ADDRESS = guido+pui@berhoerster.name + +# gcc, clang, icc, Sun/Solaris Studio +CC := $(CC) -std=c99 +COMPILE.c = $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(TARGET_ARCH) -c +# gcc, clang, icc +MAKEDEPEND.c = $(CC) -MM $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) +# Sun/Solaris Studio +#MAKEDEPEND.c = $(CC) -xM1 $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) +# X makedepend +#MAKEDEPEND.c = makedepend -f- -Y -- $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) -- +LINK.c = $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH) +LINK.o = $(CC) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH) +CP := cp +INSTALL := install +INSTALL.exec := $(INSTALL) -D -m 0755 +INSTALL.data := $(INSTALL) -D -m 0644 +INSTALL.link := $(CP) -f -P +PAX := pax +GZIP := gzip +SED := sed +GLIB_COMPILE_SCHEMAS := $(shell pkg-config --variable=glib_compile_schemas gio-2.0) +XSLTPROC := xsltproc +DOCBOOK5_MANPAGES_STYLESHEET = http://docbook.sourceforge.net/release/xsl-ns/current/manpages/docbook.xsl +DOCBOOK5_MANPAGES_FLAGS = --stringparam man.authors.section.enabled 0 \ + --stringparam man.copyright.section.enabled 0 +MSGFMT = msgfmt +MSGMERGE = msgmerge +XGETTEXT = xgettext +XGETTEXT_OPTIONS = --copyright-holder "$(AUTHOR)" \ + --package-name '$(PACKAGE)' \ + --package-version '$(VERSION)' \ + --msgid-bugs-address '$(BUG_ADDRESS)' \ + --default-domain '$(PACKAGE)' \ + --from-code UTF-8 \ + --keyword=_ \ + --keyword=N_ \ + --keyword=C_:1c,2 \ + --keyword=NC_:1c,2 \ + --keyword=g_dngettext:2,3 \ + --add-comments + +define generate-manpage-rule = +%.$1: %.$(addsuffix .xml,$1) docbook-update-source-data.xsl + $$(XSLTPROC) \ + --xinclude \ + --stringparam package $$(PACKAGE) \ + --stringparam version $$(VERSION) \ + docbook-update-source-data.xsl $$< | \ + $$(XSLTPROC) \ + --xinclude \ + --output $$@ \ + $$(DOCBOOK5_MANPAGES_FLAGS) \ + $$(DOCBOOK5_MANPAGES_STYLESHEET) \ + - +endef + +DESTDIR ?= +prefix ?= /usr/local +bindir ?= $(prefix)/bin +datadir ?= $(prefix)/share +mandir ?= $(datadir)/man +localedir ?= $(datadir)/locale +sysconfdir ?= /etc +xdgautostartdir ?= $(sysconfdir)/xdg/autostart + +OS_NAME := $(shell uname -s) +OS_RELEASE := $(shell uname -r) + +OBJS = package-update-indicator.o \ + pui-application.o \ + pui-backend.o \ + pui-get-updates.o \ + pui-types.o + +GSETTINGS_SCHEMAS = $(APPLICATION_ID).gschema.xml + +AUTOSTART_FILES = $(APPLICATION_ID).desktop + +LINGUAS := $(shell sed 's/\#.*//' po/LINGUAS) +MOFILES := $(patsubst %,po/%.mo,$(LINGUAS)) +POTFILES_IN := $(shell sed 's/\#.*//' po/POTFILES.in) +POTFILE = po/$(PACKAGE).pot + +MANPAGES = $(PACKAGE).1 + +.DEFAULT_TARGET = all + +.PHONY: all pot update-po clean clobber dist install + +all: $(PACKAGE) $(AUTOSTART_FILES) $(MOFILES) $(MANPAGES) + +$(PACKAGE): XCPPFLAGS = -DPACKAGE=\"$(PACKAGE)\" \ + -DAPPLICATION_ID=\"$(APPLICATION_ID)\" \ + -DVERSION=\"$(VERSION)\" \ + -DG_LOG_DOMAIN=\"$(PACKAGE)\" \ + -DPACKAGE_LOCALE_DIR="\"$(localedir)\"" \ + -DGETTEXT_PACKAGE=\"$(PACKAGE)\" \ + -DI_KNOW_THE_PACKAGEKIT_GLIB2_API_IS_SUBJECT_TO_CHANGE +$(PACKAGE): XCFLAGS = $(shell pkg-config --cflags gtk+-3.0 \ + appindicator3-0.1 packagekit-glib2) +$(PACKAGE): LDLIBS = $(shell pkg-config --libs gtk+-3.0 \ + appindicator3-0.1 packagekit-glib2) + +ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly OpenBSD),) + $(PACKAGE): XCPPFLAGS += -I/usr/local/include + $(PACKAGE): XLDFLAGS += -L/usr/local/lib +else ifeq ($(OS_NAME),NetBSD) + $(PACKAGE): XCPPFLAGS += -I/usr/pkg/include + $(PACKAGE): XLDFLAGS += -L/usr/pkg/lib +endif +ifeq ($(findstring $(OS_NAME),FreeBSD DragonFly NetBSD OpenBSD),) + $(PACKAGE): XCPPFLAGS += -D_XOPEN_SOURCE=600 +endif + +$(PACKAGE): $(OBJS) + $(LINK.o) $^ $(LDLIBS) -o $@ + +$(POTFILE): po/POTFILES.in $(POTFILES_IN) + $(XGETTEXT) $(XGETTEXT_OPTIONS) --files-from $< --output $@ + +pot: $(POTFILE) + +update-po: $(POTFILE) + for pofile in $(patsubst %.mo,%.po,$(MOFILES)); do \ + $(MSGMERGE) --update --backup off $$pofile $<; \ + done + +%.mo: %.po + $(MSGFMT) --output $@ $< + +%.desktop: %.desktop.in + $(MSGFMT) --desktop --template $< -d po --output $@ + +$(foreach section,1 2 3 4 5 6 7 8 9,$(eval $(call generate-manpage-rule,$(section)))) + +%.o: %.c + $(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d + $(COMPILE.c) -o $@ $< + +install: all + $(INSTALL.exec) $(PACKAGE) "$(DESTDIR)$(bindir)/$(PACKAGE)" + for schema in $(GSETTINGS_SCHEMAS); do \ + $(INSTALL.data) $${schema} \ + $(DESTDIR)$(datadir)/glib-2.0/schemas/$${schema}; \ + done + [ -n "$(GSETTINGS_SCHEMAS)" ] && \ + $(GLIB_COMPILE_SCHEMAS) $(datadir)/glib-2.0/schemas + for desktop_file in $(AUTOSTART_FILES); do \ + $(INSTALL.data) $${desktop_file} \ + $(DESTDIR)$(xdgautostartdir)/$${desktop_file}; \ + done + for lang in $(LINGUAS); do \ + $(INSTALL.data) po/$${lang}.mo \ + $(DESTDIR)$(localedir)/$${lang}/LC_MESSAGES/$(PACKAGE).mo; \ + done + for manpage in $(MANPAGES); do \ + $(INSTALL.data) $${manpage} \ + "$(DESTDIR)$(mandir)/man$${manpage##*.}/$${manpage##*/}"; \ + done + +clean: + rm -f $(PACKAGE) $(OBJS) $(AUTOSTART_FILES) $(POTFILE) $(MOFILES) \ + $(MANPAGES) + +clobber: clean + rm -f $(patsubst %.o,%.d,$(OBJS)) + +dist: clobber + $(PAX) -w -x ustar -s ',.*/\..*,,' -s ',./[^/]*\.tar\.gz,,' \ + -s ',^\.$$,,' -s ',\./,$(DISTNAME)/,' . | \ + $(GZIP) > $(DISTNAME).tar.gz + +-include local.mk + +-include $(patsubst %.o,%.d,$(OBJS))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,107 @@ +package-update-indicator +======================== + +Description +----------- + +The package-update-indicator utility regularly checks for software updates and +notifies the user about available updates using desktop notifications and +either a status notifier icon or a system tray icon. + +Build Instructions +------------------ + +package-update-indicator requires a POSIX:2004 compatible operating system, it +has been tested to work on Linux distributions. The following tools and +shared libraries are required to build package-update-indicator: + +- GNU make 3.81 or later +- pkg-config +- GNU gettext 0.19 or later +- GNU or BSD install +- GLib version 2.48 or later +- GTK+ version 3.18 or later +- libappindicator 12.10.0 or later +- PackageKit-glib2 0.8.17 or later +- the xsltproc tool from libxml2 + +Before building package-update-indicator check the commented macros in the +Makefile for any macros you may need to override depending on the used +toolchain and operating system. + +By default, all files will be installed under the "/usr/local" directory, a +different installation path prefix can be set via the `prefix` macro. In +addition, a second path prefix can be specified via the `DESTDIR` macro which +will be prepended to any path, incuding the `prefix` macro path prefix. In +contrast to `prefix`, the path specified via the `DESTDIR` macro will only be +prepended to paths during installation and not be used for constructing +internal paths. + +The following instructions assume that `make` is GNU make, on some platforms +it may be installed under a different name or a non-default path. In order to +start the build process run `make all`. After a successful build, run `make +install` to install the program, any associated data files and the +documentation. + +Previously built binaries, object files, generated data files and +documentation can be removed by running `make clean`, any additional, +generated files which are not removed by the `clean` target can be removed by +running `make clobber`. + +Contact +------- + +Please send any feedback, translations or bug reports via email to +<guido+pui@berhoerster.name>. + +Bug Reports +----------- + +When sending bug reports, please always mention the exact version of +package-update-indicator with which the issue occurs as well as the version of +the operating system you are using and make sure that you provide sufficient +information to reproduce the issue and include any input, output, any error +messages. + +In case of build issues, please also specify the implementations and versions +of the tools and shared libraries used to build the program, in particular the +compiler. + +In case of crashes, please generate a stack trace with a suitable debugger +such as gdb, lldb, dbx, or debug after a crash has occurred either by +examining the resulting core file or by running the program from the debugger +and attach it to the bug report. In order to generate a meaningful stack +trace the program as well as any dynamically linked libraries need to be built +with debugging information, see the documentation of the used compiler for the +required compiler flags. If any of the dynamically linked shared libraries do +not contain debugging information, please either install debugging information +for these libraries using mechanisms provided by your operating system or +rebuild the libraries accordingly. Please refer to the documentation of the +debugger for detailed instructions on generating backtraces. + +License +------- + +Except otherwise noted, all files are Copyright (C) 2018 Guido Berhoerster and +distributed under the following license terms: + +Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/deps.sed Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,26 @@ +/^[^:]\{1,\}:.*\\$/{ + h + s/\([^:]\{1,\}:\).*/\1/ + x + s/[^:]\{1,\}:// +} +/\\$/,/^$/bgen +/\\$/,/[^\\]$/{ +:gen + s/[[:blank:]]*\\$// + s/^[[:blank:]]*// + G + s/\(.*\)\n\(.*\)/\2 \1/ +} +/^[^:]\{1,\}:[[:blank:]]*$/d +/^[^:]\{1,\}\.o:/{ + s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC][[:blank:]]*/ /g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC]$//g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc[[:blank:]]*/ /g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc$//g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp[[:blank:]]*/ /g + s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp$//g + /^[^:]\{1,\}:[[:blank:]]*$/d + s/^\([^:]\{1,\}\)\.o[[:blank:]]*:[[:blank:]]*\(.*\)/\1.d: $(wildcard \2)\ +&/ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docbook-update-source-data.xsl Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<xsl:stylesheet + version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:db="http://docbook.org/ns/docbook" + xmlns="http://docbook.org/ns/docbook" + exclude-result-prefixes="xsl db"> + + <xsl:param name="package" select="''" /> + <xsl:param name="version" select="''" /> + + <xsl:template match="db:refmeta/db:refmiscinfo[@class = 'source' or + @class = 'version']"/> + + <xsl:template match="db:refmeta"> + <xsl:copy> + <xsl:apply-templates/> + <refmiscinfo class="source"><xsl:value-of select="$package"/></refmiscinfo> + <refmiscinfo class="version"><xsl:value-of select="$version"/></refmiscinfo> + </xsl:copy> + </xsl:template> + + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*|node()"/> + </xsl:copy> + </xsl:template> + +</xsl:stylesheet>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.guido-berhoerster.code.package-update-indicator.desktop.in Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Package Update Indicator +GenericName=Package Update Indicator +Comment=Notify about available software updates +Icon=system-software-update +Exec=package-update-indicator +Terminal=false +Type=Application +Categories=Utility;GTK;TrayIcon; +NotShowIn=GNOME;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.guido-berhoerster.code.package-update-indicator.gschema.xml Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,17 @@ +<schemalist> + <schema id="org.guido-berhoerster.code.package-update-indicator" + path="/org/guido-berhoerster/code/package-update-indicator/" + gettext-domain="package-update-indicator"> + <key name="update-command" type="s"> + <default>""</default> + <summary>Update command</summary> + <description>Command for installing updates.</description> + </key> + <key name="refresh-interval" type="u"> + <default>86400</default> + <summary>Refresh interval</summary> + <description>The interval in seconds for refreshing + metadata.</description> + </key> + </schema> +</schemalist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/package-update-indicator.1.xml Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,130 @@ +<?xml version="1.0"?> +<!-- + +Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--> +<refentry xmlns="http://docbook.org/ns/docbook" xml:lang="en"> + <info> + <author> + <personname> + <firstname>Guido</firstname> + <surname>Berhoerster</surname> + </personname> + <email>guido+pui@berhoerster.name</email> + <personblurb/> + </author> + <date>25 May, 2018</date> + </info> + <refmeta> + <refentrytitle>package-update-indicator</refentrytitle> + <manvolnum>1</manvolnum> + <refmiscinfo class="source"/> + <refmiscinfo class="version"/> + <refmiscinfo class="manual">User Commands</refmiscinfo> + </refmeta> + <refnamediv> + <refname>package-update-indicator</refname> + <refpurpose>notify about available software updates</refpurpose> + </refnamediv> + <refsynopsisdiv> + <cmdsynopsis> + <command>package-update-indicator</command> + <group> + <arg choice="plain"> + <option>-q</option> + </arg> + <arg choice="plain"> + <option>--quit</option> + </arg> + </group> + <group> + <arg choice="plain"> + <option>-V</option> + </arg> + <arg choice="plain"> + <option>--version</option> + </arg> + </group> + </cmdsynopsis> + </refsynopsisdiv> + <refsect1> + <title>Description</title> + <para>The <command>package-update-indicator</command> utility regularly + checks for software updates and notifies the user about available updates + using desktop notifications and either a status notifier icon or a system + tray icon.</para> + </refsect1> + <refsect1> + <title>Options</title> + <para>The following options are supported:</para> + <variablelist> + <varlistentry> + <term> + <option>-q</option> + </term> + <term> + <option>--quit</option> + </term> + <listitem> + <para>Quit the running instance of + <command>package-update-indicator</command>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-V</option> + </term> + <term> + <option>--version</option> + </term> + <listitem> + <para>Print the version number and exit.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + <refsect1> + <title>Exit Status</title> + <para>The following exit values are returned:</para> + <variablelist> + <varlistentry> + <term>0</term> + <listitem> + <para>Command successfully executed.</para> + </listitem> + </varlistentry> + <varlistentry> + <term>1</term> + <listitem> + <para>An unspecified error has occured.</para> + </listitem> + </varlistentry> + <varlistentry> + <term>2</term> + <listitem> + <para>Invalid command line options were specified.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> +</refentry>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/package-update-indicator.c Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <locale.h> +#include <stdlib.h> +#include <unistd.h> + +#include "pui-application.h" + +gboolean restart; + +int +main(int argc, char *argv[]) +{ + int status; + gchar *program; + PuiApplication *application; + + /* try to obtain the name of the executable for safe re-execution */ + if (argv[0] == NULL) { + g_error("unable to locate %s executable", PACKAGE); + } + if (argv[0][0] != '/') { + program = g_find_program_in_path(argv[0]); + if (program == NULL) { + g_error("unable to locate %s executable", PACKAGE); + } + argv[0] = program; + } + + bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); + setlocale(LC_ALL, ""); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); + + g_set_application_name(_("Package Update Indicator")); + + gtk_init(&argc, &argv); + + application = pui_application_new(); + status = g_application_run(G_APPLICATION(application), argc, argv); + g_object_unref(application); + + if (restart) { + /* application restart requested */ + if (execv(argv[0], argv) == -1) { + g_error("exec: %s", g_strerror(errno)); + } + } + + exit(status); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/po/POTFILES.in Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,7 @@ +org.guido-berhoerster.code.package-update-indicator.desktop.in +org.guido-berhoerster.code.package-update-indicator.gschema.xml +package-update-indicator.c +pui-application.c +pui-backend.c +pui-get-updates.c +pui-types.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-application.c Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#define G_SETTINGS_ENABLE_BACKEND +#include <gio/gsettingsbackend.h> +#include <glib/gi18n.h> +#include <libappindicator/app-indicator.h> +#include <stdlib.h> +#include <string.h> + +#include "pui-common.h" +#include "pui-application.h" +#include "pui-types.h" +#include "pui-backend.h" + +#define SETTINGS_ROOT_PATH \ + "/org/guido-berhoerster/code/package-update-indicator/" +#define SETTINGS_ROOT_GROUP "General" + +struct _PuiApplication { + GApplication parent_instance; + GSettings *settings; + GCancellable *cancellable; + PuiBackend *backend; + AppIndicator *indicator; + GtkWidget *about_dialog; + GIcon *icons[PUI_STATE_LAST]; + PuiState state; + gchar *update_command; + gchar *error_message; +}; + +G_DEFINE_TYPE(PuiApplication, pui_application, G_TYPE_APPLICATION) + +enum { + PROP_0, + PROP_UPDATE_COMMAND, + PROP_LAST +}; + +extern gboolean restart; + +static GParamSpec *properties[PROP_LAST] = { NULL }; + +static const gchar *icon_names[PUI_STATE_LAST] = { + [PUI_STATE_INITIAL] = "system-software-update", + [PUI_STATE_UP_TO_DATE] = "system-software-update", + [PUI_STATE_NORMAL_UPDATES_AVAILABLE] = "software-update-available", + [PUI_STATE_IMPORTANT_UPDATES_AVAILABLE] = "software-update-urgent", + [PUI_STATE_ERROR] = "dialog-warning" +}; + +static const GOptionEntry cmd_options[] = { + { "debug", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, + N_("Enable debugging messages"), NULL }, + { "quit", 'q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, + N_("Quit running instance of Package Update Indicator"), NULL }, + { "version", 'V', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, + N_("Print the version number and quit"), NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, NULL, NULL }, + { NULL } +}; + +static void pui_application_show_about_dialog(GSimpleAction *, GVariant *, + gpointer); +static void pui_application_quit(GSimpleAction *, GVariant *, gpointer); +static void pui_application_install_updates(GSimpleAction *, GVariant *, + gpointer); + +static const GActionEntry pui_application_actions[] = { + { "about", pui_application_show_about_dialog }, + { "quit", pui_application_quit }, + { "install-updates", pui_application_install_updates } +}; + +static gboolean +program_exists(const gchar *command_line) +{ + gboolean is_program_in_path; + gchar **argv = NULL; + gchar *program_path; + + if (!g_shell_parse_argv(command_line, NULL, &argv, NULL)) { + return (FALSE); + } + program_path = g_find_program_in_path(argv[0]); + is_program_in_path = (program_path != NULL) ? TRUE : FALSE; + g_free(program_path); + g_strfreev(argv); + + return (is_program_in_path); +} + +static void +pui_application_show_about_dialog(GSimpleAction *simple, GVariant *parameter, + gpointer user_data) +{ + PuiApplication *self = user_data; + const gchar *translators; + + if (self->about_dialog == NULL) { + translators = _("Translators"); + if (strcmp(translators, "Translators") == 0) { + translators = NULL; + } + + self->about_dialog = gtk_about_dialog_new(); + g_object_set(G_OBJECT(self->about_dialog), + "authors", (gchar *[]){ "Guido Berhoerster", NULL }, + "comments", _("Notify about available software updates"), + "copyright", "Copyright \xc2\xa9 2018 Guido Berhoerster", + "license-type", GTK_LICENSE_MIT_X11, + "logo-icon-name", "system-software-update", + "translator-credits", translators, + "version", VERSION, + "website", "https://code.guido-berhoerster.org/projects/" + "package-update-indicator/", + "wrap-license", TRUE, + NULL); + } + + gtk_dialog_run(GTK_DIALOG(self->about_dialog)); + gtk_widget_hide(self->about_dialog); +} + +static void +pui_application_quit(GSimpleAction *simple, GVariant *parameter, + gpointer user_data) +{ + PuiApplication *self = user_data; + + /* quit the GTK main loop if the about dialog is running */ + if (self->about_dialog != NULL) { + gtk_widget_hide(self->about_dialog); + } + + g_application_quit(G_APPLICATION(self)); +} + +static void +pui_application_install_updates(GSimpleAction *simple, GVariant *parameter, + gpointer user_data) +{ + PuiApplication *self = user_data; + GError *error = NULL; + + if (!g_spawn_command_line_async(self->update_command, &error)) { + g_warning("failed to run update command: %s", error->message); + g_error_free(error); + } +} + +static void +update_ui(PuiApplication *self) +{ + GSimpleAction *install_updates_action; + guint important_updates = 0; + guint normal_updates = 0; + gchar *title = NULL; + gchar *body = NULL; + GApplication *application = G_APPLICATION(self); + GNotification *notification = NULL; + + install_updates_action = + G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(self), + "install-updates")); + + if ((self->state == PUI_STATE_NORMAL_UPDATES_AVAILABLE) || + (self->state == PUI_STATE_IMPORTANT_UPDATES_AVAILABLE)) { + g_object_get(self->backend, + "important-updates", &important_updates, + "normal-updates", &normal_updates, NULL); + } + + /* actions */ + switch (self->state) { + case PUI_STATE_INITIAL: /* FALLTHGROUGH */ + case PUI_STATE_UP_TO_DATE: /* FALLTHGROUGH */ + case PUI_STATE_ERROR: + g_simple_action_set_enabled(install_updates_action, FALSE); + break; + case PUI_STATE_NORMAL_UPDATES_AVAILABLE: /* FALLTHROUGH */ + case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE: + g_simple_action_set_enabled(install_updates_action, + program_exists(self->update_command)); + break; + } + + /* title and body for indicator and notification */ + switch (self->state) { + case PUI_STATE_INITIAL: + title = g_strdup(""); + body = g_strdup(""); + break; + case PUI_STATE_UP_TO_DATE: + title = g_strdup(_("Up to Date")); + body = g_strdup(_("The system is up to date.")); + break; + case PUI_STATE_NORMAL_UPDATES_AVAILABLE: + title = g_strdup(g_dngettext(NULL, "Software Update", + "Software Updates", (gulong)normal_updates)); + if (normal_updates == 1) { + body = g_strdup(_("There is a software update " + "avaliable.")); + } else { + body = g_strdup_printf(_("There are %u " + "software updates avaliable."), normal_updates); + } + break; + case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE: + title = g_strdup(g_dngettext(NULL, "Important Software Update", + "Important Software Updates", (gulong)important_updates)); + if ((normal_updates == 0) && (important_updates == 1)) { + body = g_strdup(_("There is an important " + "software update available.")); + } else if ((normal_updates == 0) && (important_updates > 1)) { + body = g_strdup_printf(_("There are %u " + "important software updates available."), + important_updates); + } else if ((normal_updates > 0) && (important_updates == 1)) { + body = g_strdup_printf(_("There are %u " + "software updates available, " + "one of them is important."), + normal_updates + important_updates); + } else { + body = g_strdup_printf(_("There are %u " + "software updates available, " + "%u of them are important."), + normal_updates + important_updates, + important_updates); + } + break; + case PUI_STATE_ERROR: + title = g_strdup(self->error_message); + break; + } + + /* indicator */ + switch (self->state) { + case PUI_STATE_INITIAL: + app_indicator_set_status(self->indicator, + APP_INDICATOR_STATUS_PASSIVE); + break; + case PUI_STATE_UP_TO_DATE: /* FALLTHGROUGH */ + case PUI_STATE_NORMAL_UPDATES_AVAILABLE: /* FALLTHGROUGH */ + case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE: /* FALLTHGROUGH */ + case PUI_STATE_ERROR: + app_indicator_set_status(self->indicator, + APP_INDICATOR_STATUS_ACTIVE); + break; + } + app_indicator_set_icon_full(self->indicator, icon_names[self->state], + title); + + /* notification */ + switch (self->state) { + case PUI_STATE_INITIAL: /* FALLTHGROUGH */ + case PUI_STATE_UP_TO_DATE: /* FALLTHGROUGH */ + case PUI_STATE_ERROR: + /* withdraw exisiting notification */ + g_application_withdraw_notification(application, + "package-updates"); + break; + case PUI_STATE_NORMAL_UPDATES_AVAILABLE: /* FALLTHGROUGH */ + case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE: + /* create notification */ + notification = g_notification_new(title); + g_notification_set_body(notification, body); + g_notification_set_icon(notification, self->icons[self->state]); + g_notification_set_priority(notification, + G_NOTIFICATION_PRIORITY_NORMAL); + if (g_action_get_enabled(G_ACTION(install_updates_action))) { + g_notification_add_button(notification, + _("Install Updates"), + "app.install-updates"); + } + g_application_send_notification(application, "package-updates", + notification); + break; + } + + if (notification != NULL) { + g_object_unref(notification); + } + + g_debug("indicator icon: %s, notification title: \"%s\", " + "notification body: \"%s\"", icon_names[self->state], title, body); + + g_free(body); + g_free(title); +} + +static void +transition_state(PuiApplication *self) +{ + PuiState state = self->state; + guint important_updates; + guint normal_updates; + gchar *old_state; + gchar *new_state; + + switch (self->state) { + case PUI_STATE_INITIAL: /* FALLTHROUGH */ + case PUI_STATE_UP_TO_DATE: /* FALLTHROUGH */ + case PUI_STATE_NORMAL_UPDATES_AVAILABLE: /* FALLTHROUGH */ + case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE: + if (self->error_message != NULL) { + state = PUI_STATE_ERROR; + break; + } + + g_object_get(self->backend, + "important-updates", &important_updates, + "normal-updates", &normal_updates, NULL); + if (important_updates > 0) { + state = PUI_STATE_IMPORTANT_UPDATES_AVAILABLE; + } else if (normal_updates > 0) { + state = PUI_STATE_NORMAL_UPDATES_AVAILABLE; + } else { + state = PUI_STATE_UP_TO_DATE; + } + break; + case PUI_STATE_ERROR: + break; + } + + if (state != self->state) { + old_state = pui_types_enum_to_string(PUI_TYPE_STATE, + self->state); + new_state = pui_types_enum_to_string(PUI_TYPE_STATE, state); + g_debug("state %s -> %s", old_state, new_state); + + self->state = state; + update_ui(self); + + g_free(new_state); + g_free(old_state); + } +} + +static void +on_backend_restart_required(PuiBackend *backend, gpointer user_data) +{ + PuiApplication *self = user_data; + + restart = TRUE; + g_action_group_activate_action(G_ACTION_GROUP(G_APPLICATION(self)), + "quit", NULL); +} + +static void +on_backend_state_changed(PuiBackend *backend, gpointer user_data) +{ + PuiApplication *self = user_data; + + transition_state(self); +} + +static void +on_pui_backend_finished(GObject *source_object, GAsyncResult *result, + gpointer user_data) +{ + PuiApplication *self = user_data; + GError *error = NULL; + + self->backend = pui_backend_new_finish(result, &error); + if (self->backend == NULL) { + g_warning("failed to instantiate backend: %s", error->message); + g_free(self->error_message); + g_error_free(error); + self->error_message = g_strdup(_("Update notifications " + "are not supported.")); + transition_state(self); + return; + } + + g_settings_bind(self->settings, "refresh-interval", self->backend, + "refresh-interval", G_SETTINGS_BIND_GET); + + transition_state(self); + + g_signal_connect(self->backend, "restart-required", + G_CALLBACK(on_backend_restart_required), self); + g_signal_connect(self->backend, "state-changed", + G_CALLBACK(on_backend_state_changed), self); +} + +static void +pui_application_startup(GApplication *application) +{ + PuiApplication *self = PUI_APPLICATION(application); + gsize i; + gchar *settings_filename; + GSettingsBackend *settings_backend; + GtkWidget *menu; + GtkWidget *menu_item; + + G_APPLICATION_CLASS(pui_application_parent_class)->startup(application); + + /* create actions */ + g_action_map_add_action_entries(G_ACTION_MAP(self), + pui_application_actions, G_N_ELEMENTS(pui_application_actions), + self); + + /* load icons */ + for (i = 0; i < G_N_ELEMENTS(self->icons); i++) { + self->icons[i] = g_themed_icon_new(icon_names[i]); + } + + /* create settings */ + settings_filename = g_build_filename(g_get_user_config_dir(), PACKAGE, + PACKAGE ".conf", NULL); + settings_backend = g_keyfile_settings_backend_new(settings_filename, + SETTINGS_ROOT_PATH, SETTINGS_ROOT_GROUP); + self->settings = g_settings_new_with_backend(APPLICATION_ID, + settings_backend); + g_settings_bind(self->settings, "update-command", self, + "update-command", G_SETTINGS_BIND_GET); + + /* start instantiating backend */ + pui_backend_new_async(self->cancellable, on_pui_backend_finished, self); + + /* create indicator */ + self->indicator = app_indicator_new(APPLICATION_ID, + "system-software-update", + APP_INDICATOR_CATEGORY_APPLICATION_STATUS); + + /* build menu */ + menu = gtk_menu_new(); + gtk_widget_insert_action_group(GTK_WIDGET(menu), "app", + G_ACTION_GROUP(self)); + + menu_item = gtk_menu_item_new_with_label(_("Install " + "Updates\342\200\246")); + gtk_actionable_set_action_name(GTK_ACTIONABLE(menu_item), + "app.install-updates"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + menu_item = gtk_menu_item_new_with_label(_("About\342\200\246")); + gtk_actionable_set_action_name(GTK_ACTIONABLE(menu_item), + "app.about"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + gtk_widget_show_all(menu); + app_indicator_set_menu(self->indicator, GTK_MENU(menu)); + + update_ui(self); + + /* keep GApplication running */ + g_application_hold(application); + + g_object_unref(settings_backend); + g_free(settings_filename); +} + +static void +pui_application_shutdown(GApplication *application) +{ + GApplicationClass *application_class = + G_APPLICATION_CLASS(pui_application_parent_class); + + application_class->shutdown(application); +} + +static gint +pui_application_handle_local_options(GApplication *application, + GVariantDict *options) +{ + gchar *messages_debug; + gchar **args = NULL; + GError *error = NULL; + + /* filename arguments are not allowed */ + if (g_variant_dict_lookup(options, G_OPTION_REMAINING, "^a&ay", + &args)) { + g_printerr("invalid argument: \"%s\"\n", args[0]); + g_free(args); + return (1); + } + + if (g_variant_dict_contains(options, "version")) { + g_print("%s %s\n", PACKAGE, VERSION); + + /* quit */ + return (0); + } + + if (g_variant_dict_contains(options, "debug")) { + /* enable debug logging */ + messages_debug = g_strjoin(":", G_LOG_DOMAIN, + g_getenv("G_MESSAGES_DEBUG"), NULL); + g_setenv("G_MESSAGES_DEBUG", messages_debug, TRUE); + g_free(messages_debug); + } + + /* + * register with the session bus so that it is possible to discern + * between remote and primary instance and that remote actions can be + * invoked, this causes the startup signal to be emitted which, in case + * of the primary instance, starts to instantiate the + * backend with the given values + */ + if (!g_application_register(application, NULL, &error)) { + g_critical("g_application_register: %s", error->message); + g_error_free(error); + return (1); + } + + if (g_variant_dict_contains(options, "quit")) { + /* only valid if a remote instance is running */ + if (!g_application_get_is_remote(application)) { + g_printerr("%s is not running\n", g_get_prgname()); + return (1); + } + + /* signal remote instance to quit */ + g_action_group_activate_action(G_ACTION_GROUP(application), + "quit", NULL); + + /* quit local instance */ + return (0); + } + + /* proceed with default command line processing */ + return (-1); +} + +static void +pui_application_activate(GApplication *application) { + GApplicationClass *application_class = + G_APPLICATION_CLASS(pui_application_parent_class); + + /* do nothing, implementation required by GApplication */ + + application_class->activate(application); +} + +static void +pui_application_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + PuiApplication *self = PUI_APPLICATION(object); + + switch (property_id) { + case PROP_UPDATE_COMMAND: + g_free(self->update_command); + self->update_command = g_value_dup_string(value); + g_debug("property \"update-command\" set to \"%s\"", + self->update_command); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +pui_application_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) +{ + PuiApplication *self = PUI_APPLICATION(object); + + switch (property_id) { + case PROP_UPDATE_COMMAND: + g_value_set_string(value, self->update_command); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +pui_application_dispose(GObject *object) +{ + PuiApplication *self = PUI_APPLICATION(object); + gsize i; + + if (self->settings != NULL) { + g_signal_handlers_disconnect_by_data(self->settings, self); + g_clear_object(&self->settings); + } + + if (self->cancellable != NULL) { + g_cancellable_cancel(self->cancellable); + g_clear_object(&self->cancellable); + } + + if (self->backend != NULL) { + g_clear_object(&self->backend); + } + + if (self->indicator != NULL) { + g_clear_object(&self->indicator); + } + + if (self->about_dialog != NULL) { + g_clear_pointer(&self->about_dialog, + (GDestroyNotify)(gtk_widget_destroy)); + } + + for (i = 0; i < G_N_ELEMENTS(self->icons); i++) { + if (self->icons[i] != NULL) { + g_clear_object(&self->icons[i]); + } + } + + G_OBJECT_CLASS(pui_application_parent_class)->dispose(object); +} + +static void +pui_application_finalize(GObject *object) +{ + PuiApplication *self = PUI_APPLICATION(object); + + g_free(self->update_command); + g_free(self->error_message); + + G_OBJECT_CLASS(pui_application_parent_class)->finalize(object); +} + +static void +pui_application_class_init(PuiApplicationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GApplicationClass *application_class = G_APPLICATION_CLASS(klass); + + object_class->set_property = pui_application_set_property; + object_class->get_property = pui_application_get_property; + object_class->dispose = pui_application_dispose; + object_class->finalize = pui_application_finalize; + + properties[PROP_UPDATE_COMMAND] = g_param_spec_string("update-command", + "Update command", "Command for installing updates", NULL, + G_PARAM_READWRITE); + + g_object_class_install_properties(object_class, PROP_LAST, properties); + + application_class->startup = pui_application_startup; + application_class->shutdown = pui_application_shutdown; + application_class->handle_local_options = + pui_application_handle_local_options; + application_class->activate = pui_application_activate; +} + +static void +pui_application_init(PuiApplication *self) +{ + g_application_add_main_option_entries(G_APPLICATION(self), + cmd_options); + + self->cancellable = g_cancellable_new(); +} + +PuiApplication * +pui_application_new(void) +{ + return (g_object_new(PUI_TYPE_APPLICATION, "application-id", + APPLICATION_ID, NULL)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-application.h Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PUI_APPLICATION_H +#define PUI_APPLICATION_H + +#include <gio/gio.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define PUI_TYPE_APPLICATION (pui_application_get_type()) + +G_DECLARE_FINAL_TYPE(PuiApplication, pui_application, PUI, APPLICATION, + GApplication) + +PuiApplication * pui_application_new(void); + +G_END_DECLS + +#endif /* !PUI_APPLICATION_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-backend.c Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <fcntl.h> +#include <packagekit-glib2/packagekit.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <utime.h> + +#include "pui-common.h" +#include "pui-backend.h" +#include "pui-get-updates.h" + +struct _PuiBackend { + GObject parent_instance; + PkControl *pk_control; + GCancellable *cancellable; + gint64 last_check; + PkNetworkEnum network_state; + guint periodic_check_id; + guint refresh_interval; + guint important_updates; + guint normal_updates; +}; + +static void pui_backend_async_initable_iface_init(gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE(PuiBackend, pui_backend, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE, + pui_backend_async_initable_iface_init)) + +enum { + STATE_CHANGED, + RESTART_REQUIRED, + SIGNAL_LAST +}; + +enum { + PROP_0, + PROP_IMPORTANT_UPDATES, + PROP_NORMAL_UPDATES, + PROP_REFRESH_INTERVAL, + PROP_LAST +}; + +static guint signals[SIGNAL_LAST] = { 0 }; +static GParamSpec *properties[PROP_LAST] = { NULL }; + +static gboolean periodic_check(gpointer); + +GQuark +pui_backend_error_quark(void) +{ + return (g_quark_from_static_string("pui-backend-error-quark")); +} + +static void +process_pk_package(gpointer data, gpointer user_data) +{ + PkPackage *package = data; + PuiBackend *self = user_data; + PkInfoEnum type_info = pk_package_get_info(package); + + switch (type_info) { + case PK_INFO_ENUM_LOW: /* FALLTHROUGH */ + case PK_INFO_ENUM_ENHANCEMENT: /* FALLTHROUGH */ + case PK_INFO_ENUM_NORMAL: + self->normal_updates++; + break; + case PK_INFO_ENUM_BUGFIX: /* FALLTHROUGH */ + case PK_INFO_ENUM_IMPORTANT: /* FALLTHROUGH */ + case PK_INFO_ENUM_SECURITY: + self->important_updates++; + break; + default: + break; + } +} + +static void +on_get_updates_finished(GObject *source_object, GAsyncResult *async_result, + gpointer user_data) +{ + PuiBackend *self = user_data; + GPtrArray *package_list = NULL; + GError *error = NULL; + guint prev_normal_updates = self->normal_updates; + guint prev_important_updates = self->important_updates; + + package_list = pui_get_updates_finish(async_result, &error); + if (package_list == NULL) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || + g_error_matches(error, PUI_GET_UPDATES_ERROR, + PUI_GET_UPDATES_ERROR_CANCELLED)) { + /* cancelled */ + g_debug("cancelled checking for updates"); + } else { + g_warning("failed to check for updates: %s", + error->message); + } + g_error_free(error); + goto out; + } + + self->normal_updates = 0; + self->important_updates = 0; + g_ptr_array_foreach(package_list, process_pk_package, self); + g_debug("normal updates: %u, important updates: %u", + self->normal_updates, self->important_updates); + if (self->normal_updates != prev_normal_updates) { + g_object_notify_by_pspec(G_OBJECT(self), + properties[PROP_NORMAL_UPDATES]); + } + if (self->important_updates != prev_important_updates) { + g_object_notify_by_pspec(G_OBJECT(self), + properties[PROP_IMPORTANT_UPDATES]); + } + if ((self->normal_updates != prev_normal_updates) || + (self->important_updates != prev_important_updates)) { + g_debug("emitting signal state-changed"); + g_signal_emit(self, signals[STATE_CHANGED], 0); + } + + /* last successful check */ + self->last_check = g_get_monotonic_time(); + +out: + /* reschedule periodic check */ + if (self->network_state != PK_NETWORK_ENUM_OFFLINE) { + self->periodic_check_id = + g_timeout_add_seconds(PUI_CHECK_UPDATES_INTERVAL, + periodic_check, self); + } + + if (package_list != NULL) { + g_ptr_array_unref(package_list); + } +} + +static gboolean +periodic_check(gpointer user_data) +{ + PuiBackend *self = user_data; + + g_debug("running periodic check"); + + pui_get_updates_async(self->pk_control, self->refresh_interval, + self->cancellable, on_get_updates_finished, self); + + /* next periodic check will be scheduled after completion */ + self->periodic_check_id = 0; + + return (G_SOURCE_REMOVE); +} + +static void +pui_backend_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + PuiBackend *self = PUI_BACKEND(object); + + switch (property_id) { + case PROP_REFRESH_INTERVAL: + self->refresh_interval = g_value_get_uint(value); + g_debug("property \"refresh-interval\" set to %u", + self->refresh_interval); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +pui_backend_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) +{ + PuiBackend *self = PUI_BACKEND(object); + + switch (property_id) { + case PROP_IMPORTANT_UPDATES: + g_value_set_uint(value, self->important_updates); + break; + case PROP_NORMAL_UPDATES: + g_value_set_uint(value, self->normal_updates); + break; + case PROP_REFRESH_INTERVAL: + g_value_set_uint(value, self->refresh_interval); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +pui_backend_dispose(GObject *object) +{ + PuiBackend *self = PUI_BACKEND(object); + + if (self->periodic_check_id != 0) { + g_source_remove(self->periodic_check_id); + self->periodic_check_id = 0; + } + + if (self->cancellable != NULL) { + g_cancellable_cancel(self->cancellable); + g_clear_object(&self->cancellable); + } + + if (self->pk_control != NULL) { + g_clear_object(&self->pk_control); + } + + G_OBJECT_CLASS(pui_backend_parent_class)->dispose(object); +} + +static void +pui_backend_class_init(PuiBackendClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->set_property = pui_backend_set_property; + object_class->get_property = pui_backend_get_property; + object_class->dispose = pui_backend_dispose; + + properties[PROP_IMPORTANT_UPDATES] = + g_param_spec_uint("important-updates", "Important updates", + "Number of available important updates", 0, G_MAXUINT, 0, + G_PARAM_READABLE); + + properties[PROP_NORMAL_UPDATES] = + g_param_spec_uint("normal-updates", "Normal updates", + "Number of available normal updates", 0, G_MAXUINT, 0, + G_PARAM_READABLE); + + properties[PROP_REFRESH_INTERVAL] = + g_param_spec_uint("refresh-interval", "Refresh interval", + "Interval in seconds for refreshing the package cache", 0, + G_MAXUINT, PUI_DEFAULT_REFRESH_INTERVAL, G_PARAM_READWRITE); + + g_object_class_install_properties(object_class, PROP_LAST, properties); + + signals[STATE_CHANGED] = g_signal_new("state-changed", + G_TYPE_FROM_CLASS(object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); + + signals[RESTART_REQUIRED] = g_signal_new("restart-required", + G_TYPE_FROM_CLASS(object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +static void +pui_backend_init(PuiBackend *self) +{ + self->cancellable = g_cancellable_new(); + self->pk_control = pk_control_new(); +} + +static void +on_get_properties_finished(GObject *object, GAsyncResult *result, + gpointer user_data) +{ + PkControl *control = PK_CONTROL(object); + PuiBackend *self; + GTask *task = user_data; + GError *error = NULL; + gchar *backend_name = NULL; + PkBitfield roles = 0; + gchar *roles_str = NULL; + + self = g_task_get_task_data(task); + + if (!pk_control_get_properties_finish(control, result, &error)) { + g_task_return_error(task, error); + goto out; + } + + /* check whether the backend supports GetUpdates */ + g_object_get(control, "backend-name", &backend_name, "roles", &roles, + "network-state", &self->network_state, NULL); + g_debug("backend: %s", backend_name); + roles_str = pk_role_bitfield_to_string(roles); + g_debug("roles: %s", roles_str); + g_debug("network-state: %s", + pk_network_enum_to_string(self->network_state)); + if (!pk_bitfield_contain(roles, PK_ROLE_ENUM_GET_UPDATES)) { + error = g_error_new(PUI_BACKEND_ERROR, + PUI_BACKEND_ERROR_GET_UPDATES_NOT_IMPLEMENTED, + "Getting updates is not implemented in the %s PackageKit " + "backend", backend_name); + g_task_return_error(task, error); + goto out; + } + + g_task_return_boolean(task, TRUE); +out: + g_free(roles_str); + g_free(backend_name); + g_object_unref(task); +} + +static void +on_notify_network_state(PkControl *pk_control, GParamSpec *pspec, + gpointer user_data) +{ + PuiBackend *self = user_data; + PkNetworkEnum network_state; + guint elapsed_time; + guint remaining_time; + + g_object_get(pk_control, "network-state", &network_state, NULL); + g_debug("network state changed: %s", + pk_network_enum_to_string(network_state)); + if ((self->network_state == PK_NETWORK_ENUM_OFFLINE) && + (network_state != PK_NETWORK_ENUM_OFFLINE)) { + /* schedule periodic checks when coming back online */ + elapsed_time = (g_get_monotonic_time() - self->last_check) / + G_USEC_PER_SEC; + /* + * if more time that the check interval has passed since the + * last check, schedule a check after a short delay, otherwise + * wait until the interval has passed + */ + remaining_time = (elapsed_time < PUI_CHECK_UPDATES_INTERVAL) ? + PUI_CHECK_UPDATES_INTERVAL - elapsed_time : + PUI_STARTUP_DELAY; + self->periodic_check_id = g_timeout_add_seconds(remaining_time, + periodic_check, self); + } else if ((self->network_state != PK_NETWORK_ENUM_OFFLINE) && + (network_state == PK_NETWORK_ENUM_OFFLINE)) { + /* cancel periodic checks while offline */ + if (self->periodic_check_id != 0) { + g_source_remove(self->periodic_check_id); + } + } + self->network_state = network_state; +} + +static void +on_updates_changed(PkControl *control, gpointer user_data) +{ + PuiBackend *self = user_data; + + /* + * schedule a check after a short delay so that a rapid succession of + * signals is coalesced + */ + if (self->network_state != PK_NETWORK_ENUM_OFFLINE) { + if (self->periodic_check_id != 0) { + g_source_remove(self->periodic_check_id); + } + self->periodic_check_id = + g_timeout_add_seconds(PUI_STARTUP_DELAY, periodic_check, + self); + } +} + +static void +on_restart_schedule(PkControl *control, gpointer user_data) +{ + PuiBackend *self = user_data; + + g_debug("emitting signal restart-required"); + g_signal_emit(self, signals[RESTART_REQUIRED], 0); +} + +static void +pui_backend_init_async(GAsyncInitable *initable, int io_priority, + GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + PuiBackend *self = PUI_BACKEND(initable); + GTask *task; + + task = g_task_new(G_OBJECT(initable), cancellable, callback, user_data); + g_task_set_priority(task, io_priority); + g_task_set_task_data(task, g_object_ref(self), + (GDestroyNotify)g_object_unref); + + pk_control_get_properties_async(self->pk_control, cancellable, + on_get_properties_finished, task); +} + +static gboolean +pui_backend_init_finish(GAsyncInitable *initable, GAsyncResult *result, + GError **errorp) +{ + PuiBackend *self = PUI_BACKEND(initable); + GTask *task = G_TASK(result); + + if (!g_task_propagate_boolean(task, errorp)) { + return (FALSE); + } + + /* get notification when the network state changes */ + g_signal_connect(self->pk_control, "notify::network-state", + G_CALLBACK(on_notify_network_state), self); + /* get notifications when the available package updates change */ + g_signal_connect(self->pk_control, "updates-changed", + G_CALLBACK(on_updates_changed), self); + /* get notifications when an application restart is required */ + g_signal_connect(self->pk_control, "restart-schedule", + G_CALLBACK(on_restart_schedule), self); + /* schedule first check after a small delay */ + self->periodic_check_id = g_timeout_add_seconds(PUI_STARTUP_DELAY, + periodic_check, self); + + return (TRUE); +} + +static void +pui_backend_async_initable_iface_init(gpointer g_iface, gpointer iface_data) +{ + GAsyncInitableIface *iface = g_iface; + + iface->init_async = pui_backend_init_async; + iface->init_finish = pui_backend_init_finish; +} + +void +pui_backend_new_async(GCancellable *cancellable, GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async(PUI_TYPE_BACKEND, G_PRIORITY_DEFAULT, + cancellable, callback, user_data, NULL); +} + +PuiBackend * +pui_backend_new_finish(GAsyncResult *result, GError **errorp) +{ + GObject *object; + GObject *source_object; + + source_object = g_async_result_get_source_object(result); + object = g_async_initable_new_finish(G_ASYNC_INITABLE(source_object), + result, errorp); + g_object_unref(source_object); + + return ((object != NULL) ? PUI_BACKEND(object) : NULL); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-backend.h Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PUI_BACKEND_H +#define PUI_BACKEND_H + +#include <gio/gio.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define PUI_TYPE_BACKEND (pui_backend_get_type()) + +#define PUI_BACKEND_ERROR (pui_backend_error_quark()) + +G_DECLARE_FINAL_TYPE(PuiBackend, pui_backend, PUI, BACKEND, GObject) + +enum { + PUI_BACKEND_ERROR_GET_UPDATES_NOT_IMPLEMENTED +}; + +GQuark pui_backend_error_quark(void); +void pui_backend_new_async(GCancellable *, GAsyncReadyCallback, + gpointer); +PuiBackend * pui_backend_new_finish(GAsyncResult *, GError **); + +G_END_DECLS + +#endif /* !PUI_BACKEND_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-common.h Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PUI_COMMON_H +#define PUI_COMMON_H + +G_BEGIN_DECLS + +#ifndef PUI_STARTUP_DELAY +#define PUI_STARTUP_DELAY (3 * 60) +#endif /* !PUI_STARTUP_DELAY */ + +#ifndef PUI_CHECK_UPDATES_INTERVAL +#define PUI_CHECK_UPDATES_INTERVAL (60 * 60) +#endif /* !PUI_CHECK_UPDATES_INTERVAL */ + +#ifndef PUI_DEFAULT_REFRESH_INTERVAL +#define PUI_DEFAULT_REFRESH_INTERVAL (24 * 60 * 60) +#endif /* !PUI_DEFAULT_REFRESH_INTERVAL */ + +G_END_DECLS + +#endif /* !PUI_COMMON_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-get-updates.c Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "pui-get-updates.h" + +typedef struct { + PkControl *pk_control; + PkTask *pk_task; + guint refresh_interval; +} PuiGetUpdates; + +GQuark +pui_get_updates_error_quark(void) +{ + return (g_quark_from_static_string("pui-get-updates-error-quark")); +} + +static void +on_get_updates_finished(GObject *source_object, GAsyncResult *async_result, + gpointer user_data) +{ + GTask *task = user_data; + PuiGetUpdates *get_updates; + PkResults *results = NULL; + PkError *pk_error = NULL; + GError *error = NULL; + gint error_code; + GPtrArray *package_list; + + get_updates = g_task_get_task_data(task); + + g_debug("get updates transaction finished"); + results = pk_client_generic_finish(PK_CLIENT(get_updates->pk_task), + async_result, &error); + if (results == NULL) { + /* pass the error on */ + g_task_return_error(task, error); + goto out; + } + + pk_error = pk_results_get_error_code(results); + if (pk_error != NULL) { + /* transaction failed, return error */ + g_debug("failed to refresh the cache: %s", + pk_error_get_details(pk_error)); + if (pk_error_get_code(pk_error) == + PK_ERROR_ENUM_TRANSACTION_CANCELLED) { + error_code = PUI_GET_UPDATES_ERROR_CANCELLED; + } else { + error_code = + PUI_GET_UPDATES_ERROR_GET_UPDATES_FAILED; + } + error = g_error_new(PUI_GET_UPDATES_ERROR, error_code, + "Failed to get package updates: %s", + pk_error_get_details(pk_error)); + g_task_return_error(task, error); + g_object_unref(pk_error); + goto out; + } + + /* return results */ + package_list = pk_results_get_package_array(results); + g_assert(package_list != NULL); + g_task_return_pointer(task, package_list, + (GDestroyNotify)g_ptr_array_unref); + +out: + if (results != NULL) { + g_object_unref(results); + } + g_object_unref(task); +} + +static void +on_refresh_cache_finished(GObject *source_object, GAsyncResult *async_result, + gpointer user_data) +{ + GTask *task = user_data; + PuiGetUpdates *get_updates; + PkResults *results = NULL; + PkClient *pk_client; + GError *error = NULL; + PkError *pk_error = NULL; + gint error_code; + + get_updates = g_task_get_task_data(task); + pk_client = PK_CLIENT(get_updates->pk_task); + + g_debug("refresh cache transaction finished"); + results = pk_client_generic_finish(pk_client, async_result, &error); + if (results == NULL) { + g_task_return_error(task, error); + goto out; + } + + pk_error = pk_results_get_error_code(results); + if (pk_error != NULL) { + /* transaction failed, return error */ + g_debug("failed to refresh the cache: %s", + pk_error_get_details(pk_error)); + if (pk_error_get_code(pk_error) == + PK_ERROR_ENUM_TRANSACTION_CANCELLED) { + error_code = PUI_GET_UPDATES_ERROR_CANCELLED; + } else { + error_code = PUI_GET_UPDATES_ERROR_REFRESH_FAILED; + } + error = g_error_new(PUI_GET_UPDATES_ERROR, error_code, + "Failed to refresh the cache: %s", + pk_error_get_details(pk_error)); + g_task_return_error(task, error); + g_object_unref(pk_error); + goto out; + } + + /* cache is up to date, get updates */ + pk_client_get_updates_async(pk_client, + pk_bitfield_value(PK_FILTER_ENUM_NONE), + g_task_get_cancellable(task), NULL, NULL, on_get_updates_finished, + task); + +out: + if (results != NULL) { + g_object_unref(results); + } +} + +static void +on_get_time_since_refresh_finished(GObject *source_object, + GAsyncResult *async_result, gpointer user_data) +{ + GTask *task = user_data; + PuiGetUpdates *get_updates; + guint last_refresh; + GError *error = NULL; + PkClient *pk_client; + + get_updates = g_task_get_task_data(task); + pk_client = PK_CLIENT(get_updates->pk_task); + + last_refresh = + pk_control_get_time_since_action_finish(get_updates->pk_control, + async_result, &error); + if (last_refresh == 0) { + g_task_return_error(task, error); + g_object_unref(task); + return; + } + g_debug("time since last cache refresh: %us", last_refresh); + + if (last_refresh > get_updates->refresh_interval) { + /* cache is out of date, refresh first */ + g_debug("refreshing the cache"); + pk_client_refresh_cache_async(pk_client, FALSE, + g_task_get_cancellable(task), NULL, NULL, + on_refresh_cache_finished, task); + } else { + /* cache is up to date, get updates */ + g_debug("getting updates"); + pk_client_get_updates_async(pk_client, + pk_bitfield_value(PK_FILTER_ENUM_NONE), + g_task_get_cancellable(task), NULL, NULL, + on_get_updates_finished, task); + } +} + +static void +pui_get_updates_free(gpointer data) +{ + PuiGetUpdates *get_updates = data; + + g_object_unref(get_updates->pk_control); + g_object_unref(get_updates->pk_task); + g_slice_free(PuiGetUpdates, data); +} + +void +pui_get_updates_async(PkControl *pk_control, guint refresh_interval, + GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + PuiGetUpdates *get_updates; + GTask *task; + PkClient *pk_client; + + get_updates = g_slice_new0(PuiGetUpdates); + get_updates->pk_control = g_object_ref(pk_control); + get_updates->pk_task = pk_task_new(); + get_updates->refresh_interval = refresh_interval; + + pk_client = PK_CLIENT(get_updates->pk_task); + pk_client_set_cache_age(pk_client, refresh_interval); + pk_client_set_background(pk_client, TRUE); + + task = g_task_new(NULL, cancellable, callback, user_data); + g_task_set_task_data(task, get_updates, pui_get_updates_free); + + /* check whether to refresh the cache before checking for updates */ + g_debug("getting the time since the cache was last refreshed"); + pk_control_get_time_since_action_async(pk_control, + PK_ROLE_ENUM_REFRESH_CACHE, cancellable, + on_get_time_since_refresh_finished, task); +} + +GPtrArray * +pui_get_updates_finish(GAsyncResult *result, GError **errorp) +{ + return (g_task_propagate_pointer(G_TASK(result), errorp)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-get-updates.h Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PUI_GET_UPDATES_H +#define PUI_GET_UPDATES_H + +#include <gio/gio.h> +#include <glib-object.h> +#include <packagekit-glib2/packagekit.h> + +G_BEGIN_DECLS + +#define PUI_GET_UPDATES_ERROR (pui_get_updates_error_quark()) + +enum { + PUI_GET_UPDATES_ERROR_REFRESH_FAILED, + PUI_GET_UPDATES_ERROR_GET_UPDATES_FAILED, + PUI_GET_UPDATES_ERROR_CANCELLED +}; + +GQuark pui_get_updates_error_quark(void); +void pui_get_updates_async(PkControl *, guint, GCancellable *, + GAsyncReadyCallback, gpointer); +GPtrArray * pui_get_updates_finish(GAsyncResult *, GError **); + +G_END_DECLS + +#endif /* !PUI_GET_UPDATES_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-types.c Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "pui-types.h" + +#define PUI_DEFINE_ENUM_VALUE(valuename, valuenick) \ + { valuename, #valuename, #valuenick }, +#define PUI_DEFINE_ENUM_TYPE(EnumName, enum_name, values) \ + GType \ + enum_name##_get_type(void) \ + { \ + static volatile gsize g_define_type_id__volatile = 0; \ + static const GEnumValue v[] = { \ + values \ + { 0 } \ + }; \ + GType g_define_type_id; \ + if (g_once_init_enter(&g_define_type_id__volatile)) { \ + g_define_type_id = \ + g_enum_register_static(g_intern_static_string(#EnumName), \ + v); \ + g_once_init_leave(&g_define_type_id__volatile, \ + g_define_type_id); \ + } \ + return (g_define_type_id__volatile); \ + } + +PUI_DEFINE_ENUM_TYPE(PuiState, pui_state, + PUI_DEFINE_ENUM_VALUE(PUI_STATE_UP_TO_DATE, "up-to-date") + PUI_DEFINE_ENUM_VALUE(PUI_STATE_NORMAL_UPDATES_AVAILABLE, + "normal-updates-available") + PUI_DEFINE_ENUM_VALUE(PUI_STATE_IMPORTANT_UPDATES_AVAILABLE, + "important-updates-available") + PUI_DEFINE_ENUM_VALUE(PUI_STATE_ERROR, "error")) + +gchar * +pui_types_enum_to_string(GType type, gint value) +{ + GTypeClass *type_class; + GEnumValue *enum_value; + + type_class = g_type_class_ref(type); + + g_return_val_if_fail(G_IS_ENUM_CLASS(type_class), NULL); + + enum_value = g_enum_get_value(G_ENUM_CLASS(type_class), value); + if (enum_value == NULL) { + return (g_strdup_printf("%d", value)); + } + + return (g_strdup(enum_value->value_nick)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pui-types.h Sun May 20 11:32:57 2018 +0200 @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PUI_TYPES_H +#define PUI_TYPES_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define PUI_TYPE_STATE (pui_state_get_type()) + +typedef enum { + PUI_STATE_INITIAL, + PUI_STATE_UP_TO_DATE, + PUI_STATE_NORMAL_UPDATES_AVAILABLE, + PUI_STATE_IMPORTANT_UPDATES_AVAILABLE, + PUI_STATE_ERROR, + PUI_STATE_LAST +} PuiState; + +GType pui_state_get_type(void); +gchar * pui_types_enum_to_string(GType, gint); + +G_END_DECLS + +#endif /* !PUI_TYPES_H */