projects/package-update-indicator

changeset 0:6884bb8130ca

Initial revision
author Guido Berhoerster <guido+pui@berhoerster.name>
date Sun May 20 11:32:57 2018 +0200 (2018-05-20)
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
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/Makefile	Sun May 20 11:32:57 2018 +0200
     1.3 @@ -0,0 +1,204 @@
     1.4 +#
     1.5 +# Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
     1.6 +#
     1.7 +# Permission is hereby granted, free of charge, to any person obtaining
     1.8 +# a copy of this software and associated documentation files (the
     1.9 +# "Software"), to deal in the Software without restriction, including
    1.10 +# without limitation the rights to use, copy, modify, merge, publish,
    1.11 +# distribute, sublicense, and/or sell copies of the Software, and to
    1.12 +# permit persons to whom the Software is furnished to do so, subject to
    1.13 +# the following conditions:
    1.14 +#
    1.15 +# The above copyright notice and this permission notice shall be included
    1.16 +# in all copies or substantial portions of the Software.
    1.17 +#
    1.18 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    1.19 +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    1.20 +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    1.21 +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    1.22 +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    1.23 +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    1.24 +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    1.25 +#
    1.26 +
    1.27 +PACKAGE =	package-update-indicator
    1.28 +APPLICATION_ID = org.guido-berhoerster.code.package-update-indicator
    1.29 +VERSION =	1
    1.30 +DISTNAME =	$(PACKAGE)-$(VERSION)
    1.31 +AUTHOR =	Guido Berhoerster
    1.32 +BUG_ADDRESS =	guido+pui@berhoerster.name
    1.33 +
    1.34 +# gcc, clang, icc, Sun/Solaris Studio
    1.35 +CC :=		$(CC) -std=c99
    1.36 +COMPILE.c =	$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(TARGET_ARCH) -c
    1.37 +# gcc, clang, icc
    1.38 +MAKEDEPEND.c =	$(CC) -MM $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS)
    1.39 +# Sun/Solaris Studio
    1.40 +#MAKEDEPEND.c =	$(CC) -xM1 $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS)
    1.41 +# X makedepend
    1.42 +#MAKEDEPEND.c =	makedepend -f- -Y -- $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) --
    1.43 +LINK.c =	$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH)
    1.44 +LINK.o =	$(CC) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH)
    1.45 +CP :=		cp
    1.46 +INSTALL :=	install
    1.47 +INSTALL.exec :=	$(INSTALL) -D -m 0755
    1.48 +INSTALL.data :=	$(INSTALL) -D -m 0644
    1.49 +INSTALL.link :=	$(CP) -f -P
    1.50 +PAX :=		pax
    1.51 +GZIP :=		gzip
    1.52 +SED :=		sed
    1.53 +GLIB_COMPILE_SCHEMAS := $(shell pkg-config --variable=glib_compile_schemas gio-2.0)
    1.54 +XSLTPROC :=	xsltproc
    1.55 +DOCBOOK5_MANPAGES_STYLESHEET = http://docbook.sourceforge.net/release/xsl-ns/current/manpages/docbook.xsl
    1.56 +DOCBOOK5_MANPAGES_FLAGS = --stringparam man.authors.section.enabled 0 \
    1.57 +		--stringparam man.copyright.section.enabled 0
    1.58 +MSGFMT =	msgfmt
    1.59 +MSGMERGE =	msgmerge
    1.60 +XGETTEXT =	xgettext
    1.61 +XGETTEXT_OPTIONS = --copyright-holder "$(AUTHOR)" \
    1.62 +		--package-name '$(PACKAGE)' \
    1.63 +		--package-version '$(VERSION)' \
    1.64 +		--msgid-bugs-address '$(BUG_ADDRESS)' \
    1.65 +		--default-domain '$(PACKAGE)' \
    1.66 +		--from-code UTF-8 \
    1.67 +		--keyword=_ \
    1.68 +		--keyword=N_ \
    1.69 +		--keyword=C_:1c,2 \
    1.70 +		--keyword=NC_:1c,2 \
    1.71 +		--keyword=g_dngettext:2,3 \
    1.72 +		--add-comments
    1.73 +
    1.74 +define generate-manpage-rule =
    1.75 +%.$1: %.$(addsuffix .xml,$1) docbook-update-source-data.xsl
    1.76 +	$$(XSLTPROC) \
    1.77 +	    --xinclude \
    1.78 +	    --stringparam package $$(PACKAGE) \
    1.79 +	    --stringparam version $$(VERSION) \
    1.80 +	    docbook-update-source-data.xsl $$< | \
    1.81 +	    $$(XSLTPROC) \
    1.82 +	    --xinclude \
    1.83 +	    --output $$@ \
    1.84 +	    $$(DOCBOOK5_MANPAGES_FLAGS) \
    1.85 +	    $$(DOCBOOK5_MANPAGES_STYLESHEET) \
    1.86 +	    -
    1.87 +endef
    1.88 +
    1.89 +DESTDIR ?=
    1.90 +prefix ?=	/usr/local
    1.91 +bindir ?=	$(prefix)/bin
    1.92 +datadir ?=	$(prefix)/share
    1.93 +mandir ?=	$(datadir)/man
    1.94 +localedir ?=	$(datadir)/locale
    1.95 +sysconfdir ?=	/etc
    1.96 +xdgautostartdir ?= $(sysconfdir)/xdg/autostart
    1.97 +
    1.98 +OS_NAME :=	$(shell uname -s)
    1.99 +OS_RELEASE :=	$(shell uname -r)
   1.100 +
   1.101 +OBJS =		package-update-indicator.o \
   1.102 +		pui-application.o \
   1.103 +		pui-backend.o \
   1.104 +		pui-get-updates.o \
   1.105 +		pui-types.o
   1.106 +
   1.107 +GSETTINGS_SCHEMAS = $(APPLICATION_ID).gschema.xml
   1.108 +
   1.109 +AUTOSTART_FILES = $(APPLICATION_ID).desktop
   1.110 +
   1.111 +LINGUAS :=	$(shell sed 's/\#.*//' po/LINGUAS)
   1.112 +MOFILES :=	$(patsubst %,po/%.mo,$(LINGUAS))
   1.113 +POTFILES_IN :=	$(shell sed 's/\#.*//' po/POTFILES.in)
   1.114 +POTFILE =	po/$(PACKAGE).pot
   1.115 +
   1.116 +MANPAGES =	$(PACKAGE).1
   1.117 +
   1.118 +.DEFAULT_TARGET = all
   1.119 +
   1.120 +.PHONY: all pot update-po clean clobber dist install
   1.121 +
   1.122 +all: $(PACKAGE) $(AUTOSTART_FILES) $(MOFILES) $(MANPAGES)
   1.123 +
   1.124 +$(PACKAGE): XCPPFLAGS =	-DPACKAGE=\"$(PACKAGE)\" \
   1.125 +			-DAPPLICATION_ID=\"$(APPLICATION_ID)\" \
   1.126 +			-DVERSION=\"$(VERSION)\" \
   1.127 +			-DG_LOG_DOMAIN=\"$(PACKAGE)\" \
   1.128 +			-DPACKAGE_LOCALE_DIR="\"$(localedir)\"" \
   1.129 +			-DGETTEXT_PACKAGE=\"$(PACKAGE)\" \
   1.130 +			-DI_KNOW_THE_PACKAGEKIT_GLIB2_API_IS_SUBJECT_TO_CHANGE
   1.131 +$(PACKAGE): XCFLAGS =	$(shell pkg-config --cflags gtk+-3.0 \
   1.132 +			    appindicator3-0.1 packagekit-glib2)
   1.133 +$(PACKAGE): LDLIBS =	$(shell pkg-config --libs gtk+-3.0 \
   1.134 +			    appindicator3-0.1 packagekit-glib2)
   1.135 +
   1.136 +ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly OpenBSD),)
   1.137 +  $(PACKAGE): XCPPFLAGS +=	-I/usr/local/include
   1.138 +  $(PACKAGE): XLDFLAGS +=	-L/usr/local/lib
   1.139 +else ifeq ($(OS_NAME),NetBSD)
   1.140 +  $(PACKAGE): XCPPFLAGS +=	-I/usr/pkg/include
   1.141 +  $(PACKAGE): XLDFLAGS +=	-L/usr/pkg/lib
   1.142 +endif
   1.143 +ifeq ($(findstring $(OS_NAME),FreeBSD DragonFly NetBSD OpenBSD),)
   1.144 +  $(PACKAGE): XCPPFLAGS +=	-D_XOPEN_SOURCE=600
   1.145 +endif
   1.146 +
   1.147 +$(PACKAGE): $(OBJS)
   1.148 +	$(LINK.o) $^ $(LDLIBS) -o $@
   1.149 +
   1.150 +$(POTFILE): po/POTFILES.in $(POTFILES_IN)
   1.151 +	$(XGETTEXT) $(XGETTEXT_OPTIONS) --files-from $< --output $@
   1.152 +
   1.153 +pot: $(POTFILE)
   1.154 +
   1.155 +update-po: $(POTFILE)
   1.156 +	for pofile in $(patsubst %.mo,%.po,$(MOFILES)); do \
   1.157 +	    $(MSGMERGE) --update --backup off $$pofile $<; \
   1.158 +	done
   1.159 +
   1.160 +%.mo: %.po
   1.161 +	$(MSGFMT) --output $@ $<
   1.162 +
   1.163 +%.desktop: %.desktop.in
   1.164 +	$(MSGFMT) --desktop --template $< -d po --output $@
   1.165 +
   1.166 +$(foreach section,1 2 3 4 5 6 7 8 9,$(eval $(call generate-manpage-rule,$(section))))
   1.167 +
   1.168 +%.o: %.c
   1.169 +	$(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d
   1.170 +	$(COMPILE.c) -o $@ $<
   1.171 +
   1.172 +install: all
   1.173 +	$(INSTALL.exec) $(PACKAGE) "$(DESTDIR)$(bindir)/$(PACKAGE)"
   1.174 +	for schema in $(GSETTINGS_SCHEMAS); do \
   1.175 +	    $(INSTALL.data) $${schema} \
   1.176 +	        $(DESTDIR)$(datadir)/glib-2.0/schemas/$${schema}; \
   1.177 +	done
   1.178 +	[ -n "$(GSETTINGS_SCHEMAS)" ] && \
   1.179 +	    $(GLIB_COMPILE_SCHEMAS) $(datadir)/glib-2.0/schemas
   1.180 +	for desktop_file in $(AUTOSTART_FILES); do \
   1.181 +	    $(INSTALL.data) $${desktop_file} \
   1.182 +	        $(DESTDIR)$(xdgautostartdir)/$${desktop_file}; \
   1.183 +	done
   1.184 +	for lang in $(LINGUAS); do \
   1.185 +	   $(INSTALL.data) po/$${lang}.mo \
   1.186 +	        $(DESTDIR)$(localedir)/$${lang}/LC_MESSAGES/$(PACKAGE).mo; \
   1.187 +	done
   1.188 +	for manpage in $(MANPAGES); do \
   1.189 +	    $(INSTALL.data) $${manpage} \
   1.190 +	        "$(DESTDIR)$(mandir)/man$${manpage##*.}/$${manpage##*/}"; \
   1.191 +	done
   1.192 +
   1.193 +clean:
   1.194 +	rm -f $(PACKAGE) $(OBJS) $(AUTOSTART_FILES) $(POTFILE) $(MOFILES) \
   1.195 +	    $(MANPAGES)
   1.196 +
   1.197 +clobber: clean
   1.198 +	rm -f $(patsubst %.o,%.d,$(OBJS))
   1.199 +
   1.200 +dist: clobber
   1.201 +	$(PAX) -w -x ustar -s ',.*/\..*,,' -s ',./[^/]*\.tar\.gz,,' \
   1.202 +	    -s ',^\.$$,,' -s ',\./,$(DISTNAME)/,' . | \
   1.203 +	    $(GZIP) > $(DISTNAME).tar.gz
   1.204 +
   1.205 +-include local.mk
   1.206 +
   1.207 +-include $(patsubst %.o,%.d,$(OBJS))
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/README	Sun May 20 11:32:57 2018 +0200
     2.3 @@ -0,0 +1,107 @@
     2.4 +package-update-indicator
     2.5 +========================
     2.6 +
     2.7 +Description
     2.8 +-----------
     2.9 +
    2.10 +The package-update-indicator utility regularly checks for software updates and
    2.11 +notifies the user about available updates using desktop notifications and
    2.12 +either a status notifier icon or a system tray icon.
    2.13 +
    2.14 +Build Instructions
    2.15 +------------------
    2.16 +
    2.17 +package-update-indicator requires a POSIX:2004 compatible operating system, it
    2.18 +has been tested to work on Linux distributions.  The following tools and
    2.19 +shared libraries are required to build package-update-indicator:
    2.20 +
    2.21 +- GNU make 3.81 or later
    2.22 +- pkg-config
    2.23 +- GNU gettext 0.19 or later
    2.24 +- GNU or BSD install
    2.25 +- GLib version 2.48 or later
    2.26 +- GTK+ version 3.18 or later
    2.27 +- libappindicator 12.10.0 or later
    2.28 +- PackageKit-glib2 0.8.17 or later
    2.29 +- the xsltproc tool from libxml2
    2.30 +
    2.31 +Before building package-update-indicator check the commented macros in the
    2.32 +Makefile for any macros you may need to override depending on the used
    2.33 +toolchain and operating system.
    2.34 +
    2.35 +By default, all files will be installed under the "/usr/local" directory, a
    2.36 +different installation path prefix can be set via the `prefix` macro.  In
    2.37 +addition, a second path prefix can be specified via the `DESTDIR` macro which
    2.38 +will be prepended to any path, incuding the `prefix` macro path prefix.  In
    2.39 +contrast to `prefix`, the path specified via the `DESTDIR` macro will only be
    2.40 +prepended to paths during installation and not be used for constructing
    2.41 +internal paths.
    2.42 +
    2.43 +The following instructions assume that `make` is GNU make, on some platforms
    2.44 +it may be installed under a different name or a non-default path.  In order to
    2.45 +start the build process run `make all`.  After a successful build, run `make
    2.46 +install` to install the program, any associated data files and the
    2.47 +documentation.
    2.48 +
    2.49 +Previously built binaries, object files, generated data files and
    2.50 +documentation can be removed by running `make clean`, any additional,
    2.51 +generated files which are not removed by the `clean` target can be removed by
    2.52 +running `make clobber`.
    2.53 +
    2.54 +Contact
    2.55 +-------
    2.56 +
    2.57 +Please send any feedback, translations or bug reports via email to
    2.58 +<guido+pui@berhoerster.name>.
    2.59 +
    2.60 +Bug Reports
    2.61 +-----------
    2.62 +
    2.63 +When sending bug reports, please always mention the exact version of
    2.64 +package-update-indicator with which the issue occurs as well as the version of
    2.65 +the operating system you are using and make sure that you provide sufficient
    2.66 +information to reproduce the issue and include any input, output, any error
    2.67 +messages.
    2.68 +
    2.69 +In case of build issues, please also specify the implementations and versions
    2.70 +of the tools and shared libraries used to build the program, in particular the
    2.71 +compiler.
    2.72 +
    2.73 +In case of crashes, please generate a stack trace with a suitable debugger
    2.74 +such as gdb, lldb, dbx, or debug after a crash has occurred either by
    2.75 +examining the resulting core file or by running the program from the debugger
    2.76 +and attach it to the bug report.  In order to generate a meaningful stack
    2.77 +trace the program as well as any dynamically linked libraries need to be built
    2.78 +with debugging information, see the documentation of the used compiler for the
    2.79 +required compiler flags.  If any of the dynamically linked shared libraries do
    2.80 +not contain debugging information, please either install debugging information
    2.81 +for these libraries using mechanisms provided by your operating system or
    2.82 +rebuild the libraries accordingly.  Please refer to the documentation of the
    2.83 +debugger for detailed instructions on generating backtraces.
    2.84 +
    2.85 +License
    2.86 +-------
    2.87 +
    2.88 +Except otherwise noted, all files are Copyright (C) 2018 Guido Berhoerster and
    2.89 +distributed under the following license terms:
    2.90 +
    2.91 +Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    2.92 +
    2.93 +Permission is hereby granted, free of charge, to any person obtaining
    2.94 +a copy of this software and associated documentation files (the
    2.95 +"Software"), to deal in the Software without restriction, including
    2.96 +without limitation the rights to use, copy, modify, merge, publish,
    2.97 +distribute, sublicense, and/or sell copies of the Software, and to
    2.98 +permit persons to whom the Software is furnished to do so, subject to
    2.99 +the following conditions:
   2.100 +
   2.101 +The above copyright notice and this permission notice shall be included
   2.102 +in all copies or substantial portions of the Software.
   2.103 +
   2.104 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   2.105 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   2.106 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   2.107 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   2.108 +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   2.109 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   2.110 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/deps.sed	Sun May 20 11:32:57 2018 +0200
     3.3 @@ -0,0 +1,26 @@
     3.4 +/^[^:]\{1,\}:.*\\$/{
     3.5 +    h
     3.6 +    s/\([^:]\{1,\}:\).*/\1/
     3.7 +    x
     3.8 +    s/[^:]\{1,\}://
     3.9 +}
    3.10 +/\\$/,/^$/bgen
    3.11 +/\\$/,/[^\\]$/{
    3.12 +:gen
    3.13 +    s/[[:blank:]]*\\$//
    3.14 +    s/^[[:blank:]]*//
    3.15 +    G
    3.16 +    s/\(.*\)\n\(.*\)/\2 \1/
    3.17 +}
    3.18 +/^[^:]\{1,\}:[[:blank:]]*$/d
    3.19 +/^[^:]\{1,\}\.o:/{
    3.20 +    s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC][[:blank:]]*/ /g
    3.21 +    s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC]$//g
    3.22 +    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc[[:blank:]]*/ /g
    3.23 +    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc$//g
    3.24 +    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp[[:blank:]]*/ /g
    3.25 +    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp$//g
    3.26 +    /^[^:]\{1,\}:[[:blank:]]*$/d
    3.27 +    s/^\([^:]\{1,\}\)\.o[[:blank:]]*:[[:blank:]]*\(.*\)/\1.d: $(wildcard \2)\
    3.28 +&/
    3.29 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/docbook-update-source-data.xsl	Sun May 20 11:32:57 2018 +0200
     4.3 @@ -0,0 +1,29 @@
     4.4 +<?xml version="1.0"?>
     4.5 +<xsl:stylesheet
     4.6 +  version="1.0"
     4.7 +  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     4.8 +  xmlns:db="http://docbook.org/ns/docbook"
     4.9 +  xmlns="http://docbook.org/ns/docbook"
    4.10 +  exclude-result-prefixes="xsl db">
    4.11 +
    4.12 +  <xsl:param name="package" select="''" />
    4.13 +  <xsl:param name="version" select="''" />
    4.14 +
    4.15 +  <xsl:template match="db:refmeta/db:refmiscinfo[@class = 'source' or
    4.16 +    @class = 'version']"/>
    4.17 +
    4.18 +  <xsl:template match="db:refmeta">
    4.19 +    <xsl:copy>
    4.20 +      <xsl:apply-templates/>
    4.21 +      <refmiscinfo class="source"><xsl:value-of select="$package"/></refmiscinfo>
    4.22 +      <refmiscinfo class="version"><xsl:value-of select="$version"/></refmiscinfo>
    4.23 +    </xsl:copy>
    4.24 +  </xsl:template>
    4.25 +
    4.26 +  <xsl:template match="@*|node()">
    4.27 +    <xsl:copy>
    4.28 +      <xsl:apply-templates select="@*|node()"/>
    4.29 +    </xsl:copy>
    4.30 +  </xsl:template>
    4.31 +
    4.32 +</xsl:stylesheet>
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/org.guido-berhoerster.code.package-update-indicator.desktop.in	Sun May 20 11:32:57 2018 +0200
     5.3 @@ -0,0 +1,10 @@
     5.4 +[Desktop Entry]
     5.5 +Name=Package Update Indicator
     5.6 +GenericName=Package Update Indicator
     5.7 +Comment=Notify about available software updates
     5.8 +Icon=system-software-update
     5.9 +Exec=package-update-indicator
    5.10 +Terminal=false
    5.11 +Type=Application
    5.12 +Categories=Utility;GTK;TrayIcon;
    5.13 +NotShowIn=GNOME;
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/org.guido-berhoerster.code.package-update-indicator.gschema.xml	Sun May 20 11:32:57 2018 +0200
     6.3 @@ -0,0 +1,17 @@
     6.4 +<schemalist>
     6.5 +  <schema id="org.guido-berhoerster.code.package-update-indicator"
     6.6 +  path="/org/guido-berhoerster/code/package-update-indicator/"
     6.7 +  gettext-domain="package-update-indicator">
     6.8 +    <key name="update-command" type="s">
     6.9 +      <default>""</default>
    6.10 +      <summary>Update command</summary>
    6.11 +      <description>Command for installing updates.</description>
    6.12 +    </key>
    6.13 +    <key name="refresh-interval" type="u">
    6.14 +      <default>86400</default>
    6.15 +      <summary>Refresh interval</summary>
    6.16 +      <description>The interval in seconds for refreshing
    6.17 +      metadata.</description>
    6.18 +    </key>
    6.19 +  </schema>
    6.20 +</schemalist>
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/package-update-indicator.1.xml	Sun May 20 11:32:57 2018 +0200
     7.3 @@ -0,0 +1,130 @@
     7.4 +<?xml version="1.0"?>
     7.5 +<!--
     7.6 +
     7.7 +Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
     7.8 +
     7.9 +Permission is hereby granted, free of charge, to any person obtaining
    7.10 +a copy of this software and associated documentation files (the
    7.11 +"Software"), to deal in the Software without restriction, including
    7.12 +without limitation the rights to use, copy, modify, merge, publish,
    7.13 +distribute, sublicense, and/or sell copies of the Software, and to
    7.14 +permit persons to whom the Software is furnished to do so, subject to
    7.15 +the following conditions:
    7.16 +
    7.17 +The above copyright notice and this permission notice shall be included
    7.18 +in all copies or substantial portions of the Software.
    7.19 +
    7.20 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    7.21 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    7.22 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    7.23 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    7.24 +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    7.25 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    7.26 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    7.27 +
    7.28 +-->
    7.29 +<refentry xmlns="http://docbook.org/ns/docbook" xml:lang="en">
    7.30 +  <info>
    7.31 +    <author>
    7.32 +      <personname>
    7.33 +        <firstname>Guido</firstname>
    7.34 +        <surname>Berhoerster</surname>
    7.35 +      </personname>
    7.36 +      <email>guido+pui@berhoerster.name</email>
    7.37 +      <personblurb/>
    7.38 +    </author>
    7.39 +    <date>25 May, 2018</date>
    7.40 +  </info>
    7.41 +  <refmeta>
    7.42 +    <refentrytitle>package-update-indicator</refentrytitle>
    7.43 +    <manvolnum>1</manvolnum>
    7.44 +    <refmiscinfo class="source"/>
    7.45 +    <refmiscinfo class="version"/>
    7.46 +    <refmiscinfo class="manual">User Commands</refmiscinfo>
    7.47 +  </refmeta>
    7.48 +  <refnamediv>
    7.49 +    <refname>package-update-indicator</refname>
    7.50 +    <refpurpose>notify about available software updates</refpurpose>
    7.51 +  </refnamediv>
    7.52 +  <refsynopsisdiv>
    7.53 +    <cmdsynopsis>
    7.54 +      <command>package-update-indicator</command>
    7.55 +      <group>
    7.56 +        <arg choice="plain">
    7.57 +          <option>-q</option>
    7.58 +        </arg>
    7.59 +        <arg choice="plain">
    7.60 +          <option>--quit</option>
    7.61 +        </arg>
    7.62 +      </group>
    7.63 +      <group>
    7.64 +        <arg choice="plain">
    7.65 +          <option>-V</option>
    7.66 +        </arg>
    7.67 +        <arg choice="plain">
    7.68 +          <option>--version</option>
    7.69 +        </arg>
    7.70 +      </group>
    7.71 +    </cmdsynopsis>
    7.72 +  </refsynopsisdiv>
    7.73 +  <refsect1>
    7.74 +    <title>Description</title>
    7.75 +    <para>The <command>package-update-indicator</command> utility regularly
    7.76 +    checks for software updates and notifies the user about available updates
    7.77 +    using desktop notifications and either a status notifier icon or a system
    7.78 +    tray icon.</para>
    7.79 +  </refsect1>
    7.80 +  <refsect1>
    7.81 +    <title>Options</title>
    7.82 +    <para>The following options are supported:</para>
    7.83 +    <variablelist>
    7.84 +      <varlistentry>
    7.85 +        <term>
    7.86 +          <option>-q</option>
    7.87 +        </term>
    7.88 +        <term>
    7.89 +          <option>--quit</option>
    7.90 +        </term>
    7.91 +        <listitem>
    7.92 +          <para>Quit the running instance of
    7.93 +          <command>package-update-indicator</command>.</para>
    7.94 +        </listitem>
    7.95 +      </varlistentry>
    7.96 +      <varlistentry>
    7.97 +        <term>
    7.98 +          <option>-V</option>
    7.99 +        </term>
   7.100 +        <term>
   7.101 +          <option>--version</option>
   7.102 +        </term>
   7.103 +        <listitem>
   7.104 +          <para>Print the version number and exit.</para>
   7.105 +        </listitem>
   7.106 +      </varlistentry>
   7.107 +    </variablelist>
   7.108 +  </refsect1>
   7.109 +  <refsect1>
   7.110 +    <title>Exit Status</title>
   7.111 +    <para>The following exit values are returned:</para>
   7.112 +    <variablelist>
   7.113 +      <varlistentry>
   7.114 +        <term>0</term>
   7.115 +        <listitem>
   7.116 +          <para>Command successfully executed.</para>
   7.117 +        </listitem>
   7.118 +      </varlistentry>
   7.119 +      <varlistentry>
   7.120 +        <term>1</term>
   7.121 +        <listitem>
   7.122 +          <para>An unspecified error has occured.</para>
   7.123 +        </listitem>
   7.124 +      </varlistentry>
   7.125 +      <varlistentry>
   7.126 +        <term>2</term>
   7.127 +        <listitem>
   7.128 +          <para>Invalid command line options were specified.</para>
   7.129 +        </listitem>
   7.130 +      </varlistentry>
   7.131 +    </variablelist>
   7.132 +  </refsect1>
   7.133 +</refentry>
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/package-update-indicator.c	Sun May 20 11:32:57 2018 +0200
     8.3 @@ -0,0 +1,75 @@
     8.4 +/*
     8.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
     8.6 + *
     8.7 + * Permission is hereby granted, free of charge, to any person obtaining
     8.8 + * a copy of this software and associated documentation files (the
     8.9 + * "Software"), to deal in the Software without restriction, including
    8.10 + * without limitation the rights to use, copy, modify, merge, publish,
    8.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    8.12 + * permit persons to whom the Software is furnished to do so, subject to
    8.13 + * the following conditions:
    8.14 + *
    8.15 + * The above copyright notice and this permission notice shall be included
    8.16 + * in all copies or substantial portions of the Software.
    8.17 + *
    8.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    8.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    8.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    8.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    8.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    8.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    8.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    8.25 + */
    8.26 +
    8.27 +#include <errno.h>
    8.28 +#include <gtk/gtk.h>
    8.29 +#include <glib/gi18n.h>
    8.30 +#include <locale.h>
    8.31 +#include <stdlib.h>
    8.32 +#include <unistd.h>
    8.33 +
    8.34 +#include "pui-application.h"
    8.35 +
    8.36 +gboolean	restart;
    8.37 +
    8.38 +int
    8.39 +main(int argc, char *argv[])
    8.40 +{
    8.41 +	int		status;
    8.42 +	gchar		*program;
    8.43 +	PuiApplication	*application;
    8.44 +
    8.45 +	/* try to obtain the name of the executable for safe re-execution */
    8.46 +	if (argv[0] == NULL) {
    8.47 +		g_error("unable to locate %s executable", PACKAGE);
    8.48 +	}
    8.49 +	if (argv[0][0] != '/') {
    8.50 +		program = g_find_program_in_path(argv[0]);
    8.51 +		if (program == NULL) {
    8.52 +			g_error("unable to locate %s executable", PACKAGE);
    8.53 +		}
    8.54 +		argv[0] = program;
    8.55 +	}
    8.56 +
    8.57 +	bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
    8.58 +	setlocale(LC_ALL, "");
    8.59 +	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    8.60 +	textdomain(GETTEXT_PACKAGE);
    8.61 +
    8.62 +	g_set_application_name(_("Package Update Indicator"));
    8.63 +
    8.64 +	gtk_init(&argc, &argv);
    8.65 +
    8.66 +	application = pui_application_new();
    8.67 +	status = g_application_run(G_APPLICATION(application), argc, argv);
    8.68 +	g_object_unref(application);
    8.69 +
    8.70 +	if (restart) {
    8.71 +		/* application restart requested */
    8.72 +		if (execv(argv[0], argv) == -1) {
    8.73 +			g_error("exec: %s", g_strerror(errno));
    8.74 +		}
    8.75 +	}
    8.76 +
    8.77 +	exit(status);
    8.78 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/po/POTFILES.in	Sun May 20 11:32:57 2018 +0200
     9.3 @@ -0,0 +1,7 @@
     9.4 +org.guido-berhoerster.code.package-update-indicator.desktop.in
     9.5 +org.guido-berhoerster.code.package-update-indicator.gschema.xml
     9.6 +package-update-indicator.c
     9.7 +pui-application.c
     9.8 +pui-backend.c
     9.9 +pui-get-updates.c
    9.10 +pui-types.c
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/pui-application.c	Sun May 20 11:32:57 2018 +0200
    10.3 @@ -0,0 +1,680 @@
    10.4 +/*
    10.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    10.6 + *
    10.7 + * Permission is hereby granted, free of charge, to any person obtaining
    10.8 + * a copy of this software and associated documentation files (the
    10.9 + * "Software"), to deal in the Software without restriction, including
   10.10 + * without limitation the rights to use, copy, modify, merge, publish,
   10.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   10.12 + * permit persons to whom the Software is furnished to do so, subject to
   10.13 + * the following conditions:
   10.14 + *
   10.15 + * The above copyright notice and this permission notice shall be included
   10.16 + * in all copies or substantial portions of the Software.
   10.17 + *
   10.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   10.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   10.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   10.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   10.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   10.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   10.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   10.25 + */
   10.26 +
   10.27 +#define	G_SETTINGS_ENABLE_BACKEND
   10.28 +#include <gio/gsettingsbackend.h>
   10.29 +#include <glib/gi18n.h>
   10.30 +#include <libappindicator/app-indicator.h>
   10.31 +#include <stdlib.h>
   10.32 +#include <string.h>
   10.33 +
   10.34 +#include "pui-common.h"
   10.35 +#include "pui-application.h"
   10.36 +#include "pui-types.h"
   10.37 +#include "pui-backend.h"
   10.38 +
   10.39 +#define	SETTINGS_ROOT_PATH \
   10.40 +    "/org/guido-berhoerster/code/package-update-indicator/"
   10.41 +#define	SETTINGS_ROOT_GROUP	"General"
   10.42 +
   10.43 +struct _PuiApplication {
   10.44 +	GApplication	parent_instance;
   10.45 +	GSettings	*settings;
   10.46 +	GCancellable	*cancellable;
   10.47 +	PuiBackend	*backend;
   10.48 +	AppIndicator	*indicator;
   10.49 +	GtkWidget	*about_dialog;
   10.50 +	GIcon		*icons[PUI_STATE_LAST];
   10.51 +	PuiState	state;
   10.52 +	gchar		*update_command;
   10.53 +	gchar		*error_message;
   10.54 +};
   10.55 +
   10.56 +G_DEFINE_TYPE(PuiApplication, pui_application, G_TYPE_APPLICATION)
   10.57 +
   10.58 +enum {
   10.59 +    PROP_0,
   10.60 +    PROP_UPDATE_COMMAND,
   10.61 +    PROP_LAST
   10.62 +};
   10.63 +
   10.64 +extern gboolean	restart;
   10.65 +
   10.66 +static GParamSpec *properties[PROP_LAST] = { NULL };
   10.67 +
   10.68 +static const gchar *icon_names[PUI_STATE_LAST] = {
   10.69 +    [PUI_STATE_INITIAL] = "system-software-update",
   10.70 +    [PUI_STATE_UP_TO_DATE] = "system-software-update",
   10.71 +    [PUI_STATE_NORMAL_UPDATES_AVAILABLE] = "software-update-available",
   10.72 +    [PUI_STATE_IMPORTANT_UPDATES_AVAILABLE] = "software-update-urgent",
   10.73 +    [PUI_STATE_ERROR] = "dialog-warning"
   10.74 +};
   10.75 +
   10.76 +static const GOptionEntry cmd_options[] = {
   10.77 +    { "debug", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL,
   10.78 +    N_("Enable debugging messages"), NULL },
   10.79 +    { "quit", 'q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL,
   10.80 +    N_("Quit running instance of Package Update Indicator"), NULL },
   10.81 +    { "version", 'V', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL,
   10.82 +    N_("Print the version number and quit"), NULL },
   10.83 +    { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, NULL, NULL },
   10.84 +    { NULL }
   10.85 +};
   10.86 +
   10.87 +static void	pui_application_show_about_dialog(GSimpleAction *, GVariant *,
   10.88 +    gpointer);
   10.89 +static void	pui_application_quit(GSimpleAction *, GVariant *, gpointer);
   10.90 +static void	pui_application_install_updates(GSimpleAction *, GVariant *,
   10.91 +    gpointer);
   10.92 +
   10.93 +static const GActionEntry pui_application_actions[] = {
   10.94 +    { "about", pui_application_show_about_dialog },
   10.95 +    { "quit", pui_application_quit },
   10.96 +    { "install-updates", pui_application_install_updates }
   10.97 +};
   10.98 +
   10.99 +static gboolean
  10.100 +program_exists(const gchar *command_line)
  10.101 +{
  10.102 +	gboolean	is_program_in_path;
  10.103 +	gchar		**argv = NULL;
  10.104 +	gchar		*program_path;
  10.105 +
  10.106 +	if (!g_shell_parse_argv(command_line, NULL, &argv, NULL)) {
  10.107 +		return (FALSE);
  10.108 +	}
  10.109 +	program_path = g_find_program_in_path(argv[0]);
  10.110 +	is_program_in_path = (program_path != NULL) ? TRUE : FALSE;
  10.111 +	g_free(program_path);
  10.112 +	g_strfreev(argv);
  10.113 +
  10.114 +	return (is_program_in_path);
  10.115 +}
  10.116 +
  10.117 +static void
  10.118 +pui_application_show_about_dialog(GSimpleAction *simple, GVariant *parameter,
  10.119 +    gpointer user_data)
  10.120 +{
  10.121 +	PuiApplication	*self = user_data;
  10.122 +	const gchar	*translators;
  10.123 +
  10.124 +	if (self->about_dialog == NULL) {
  10.125 +		translators = _("Translators");
  10.126 +		if (strcmp(translators, "Translators") == 0) {
  10.127 +			translators = NULL;
  10.128 +		}
  10.129 +
  10.130 +		self->about_dialog = gtk_about_dialog_new();
  10.131 +		g_object_set(G_OBJECT(self->about_dialog),
  10.132 +		    "authors", (gchar *[]){ "Guido Berhoerster", NULL },
  10.133 +		    "comments", _("Notify about available software updates"),
  10.134 +		    "copyright", "Copyright \xc2\xa9 2018 Guido Berhoerster",
  10.135 +		    "license-type", GTK_LICENSE_MIT_X11,
  10.136 +		    "logo-icon-name", "system-software-update",
  10.137 +		    "translator-credits", translators,
  10.138 +		    "version", VERSION,
  10.139 +		    "website", "https://code.guido-berhoerster.org/projects/"
  10.140 +		    "package-update-indicator/",
  10.141 +		    "wrap-license", TRUE,
  10.142 +		    NULL);
  10.143 +	}
  10.144 +
  10.145 +	gtk_dialog_run(GTK_DIALOG(self->about_dialog));
  10.146 +	gtk_widget_hide(self->about_dialog);
  10.147 +}
  10.148 +
  10.149 +static void
  10.150 +pui_application_quit(GSimpleAction *simple, GVariant *parameter,
  10.151 +    gpointer user_data)
  10.152 +{
  10.153 +	PuiApplication	*self = user_data;
  10.154 +
  10.155 +	/* quit the GTK main loop if the about dialog is running */
  10.156 +	if (self->about_dialog != NULL) {
  10.157 +		gtk_widget_hide(self->about_dialog);
  10.158 +	}
  10.159 +
  10.160 +	g_application_quit(G_APPLICATION(self));
  10.161 +}
  10.162 +
  10.163 +static void
  10.164 +pui_application_install_updates(GSimpleAction *simple, GVariant *parameter,
  10.165 +    gpointer user_data)
  10.166 +{
  10.167 +	PuiApplication	*self = user_data;
  10.168 +	GError		*error = NULL;
  10.169 +
  10.170 +	if (!g_spawn_command_line_async(self->update_command, &error)) {
  10.171 +		g_warning("failed to run update command: %s", error->message);
  10.172 +		g_error_free(error);
  10.173 +	}
  10.174 +}
  10.175 +
  10.176 +static void
  10.177 +update_ui(PuiApplication *self)
  10.178 +{
  10.179 +	GSimpleAction	*install_updates_action;
  10.180 +	guint		important_updates = 0;
  10.181 +	guint		normal_updates = 0;
  10.182 +	gchar		*title = NULL;
  10.183 +	gchar		*body = NULL;
  10.184 +	GApplication	*application = G_APPLICATION(self);
  10.185 +	GNotification	*notification = NULL;
  10.186 +
  10.187 +	install_updates_action =
  10.188 +	    G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(self),
  10.189 +	    "install-updates"));
  10.190 +
  10.191 +	if ((self->state == PUI_STATE_NORMAL_UPDATES_AVAILABLE) ||
  10.192 +	    (self->state == PUI_STATE_IMPORTANT_UPDATES_AVAILABLE)) {
  10.193 +		g_object_get(self->backend,
  10.194 +		    "important-updates", &important_updates,
  10.195 +		    "normal-updates", &normal_updates, NULL);
  10.196 +	}
  10.197 +
  10.198 +	/* actions */
  10.199 +	switch (self->state) {
  10.200 +	case PUI_STATE_INITIAL:				/* FALLTHGROUGH */
  10.201 +	case PUI_STATE_UP_TO_DATE:			/* FALLTHGROUGH */
  10.202 +	case PUI_STATE_ERROR:
  10.203 +		g_simple_action_set_enabled(install_updates_action, FALSE);
  10.204 +		break;
  10.205 +	case PUI_STATE_NORMAL_UPDATES_AVAILABLE:	/* FALLTHROUGH */
  10.206 +	case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE:
  10.207 +		g_simple_action_set_enabled(install_updates_action,
  10.208 +		    program_exists(self->update_command));
  10.209 +		break;
  10.210 +	}
  10.211 +
  10.212 +	/* title and body for indicator and notification */
  10.213 +	switch (self->state) {
  10.214 +	case PUI_STATE_INITIAL:
  10.215 +		title = g_strdup("");
  10.216 +		body = g_strdup("");
  10.217 +		break;
  10.218 +	case PUI_STATE_UP_TO_DATE:
  10.219 +		title = g_strdup(_("Up to Date"));
  10.220 +		body = g_strdup(_("The system is up to date."));
  10.221 +		break;
  10.222 +	case PUI_STATE_NORMAL_UPDATES_AVAILABLE:
  10.223 +		title = g_strdup(g_dngettext(NULL, "Software Update",
  10.224 +		    "Software Updates", (gulong)normal_updates));
  10.225 +		if (normal_updates == 1) {
  10.226 +			body = g_strdup(_("There is a software update "
  10.227 +			    "avaliable."));
  10.228 +		} else {
  10.229 +			body = g_strdup_printf(_("There are %u "
  10.230 +			    "software updates avaliable."), normal_updates);
  10.231 +		}
  10.232 +		break;
  10.233 +	case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE:
  10.234 +		title = g_strdup(g_dngettext(NULL, "Important Software Update",
  10.235 +		    "Important Software Updates", (gulong)important_updates));
  10.236 +		if ((normal_updates == 0) && (important_updates == 1)) {
  10.237 +			body = g_strdup(_("There is an important "
  10.238 +			    "software update available."));
  10.239 +		} else if ((normal_updates == 0) && (important_updates > 1)) {
  10.240 +			body = g_strdup_printf(_("There are %u "
  10.241 +			    "important software updates available."),
  10.242 +			    important_updates);
  10.243 +		} else if ((normal_updates > 0) && (important_updates == 1)) {
  10.244 +			body = g_strdup_printf(_("There are %u "
  10.245 +			    "software updates available, "
  10.246 +			    "one of them is important."),
  10.247 +			    normal_updates + important_updates);
  10.248 +		} else {
  10.249 +			body = g_strdup_printf(_("There are %u "
  10.250 +			    "software updates available, "
  10.251 +			    "%u of them are important."),
  10.252 +			    normal_updates + important_updates,
  10.253 +			    important_updates);
  10.254 +		}
  10.255 +		break;
  10.256 +	case PUI_STATE_ERROR:
  10.257 +		title = g_strdup(self->error_message);
  10.258 +		break;
  10.259 +	}
  10.260 +
  10.261 +	/* indicator */
  10.262 +	switch (self->state) {
  10.263 +	case PUI_STATE_INITIAL:
  10.264 +		app_indicator_set_status(self->indicator,
  10.265 +		    APP_INDICATOR_STATUS_PASSIVE);
  10.266 +		break;
  10.267 +	case PUI_STATE_UP_TO_DATE:			/* FALLTHGROUGH */
  10.268 +	case PUI_STATE_NORMAL_UPDATES_AVAILABLE:	/* FALLTHGROUGH */
  10.269 +	case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE:	/* FALLTHGROUGH */
  10.270 +	case PUI_STATE_ERROR:
  10.271 +		app_indicator_set_status(self->indicator,
  10.272 +		    APP_INDICATOR_STATUS_ACTIVE);
  10.273 +		break;
  10.274 +	}
  10.275 +	app_indicator_set_icon_full(self->indicator, icon_names[self->state],
  10.276 +	    title);
  10.277 +
  10.278 +	/* notification */
  10.279 +	switch (self->state) {
  10.280 +	case PUI_STATE_INITIAL:				/* FALLTHGROUGH */
  10.281 +	case PUI_STATE_UP_TO_DATE:			/* FALLTHGROUGH */
  10.282 +	case PUI_STATE_ERROR:
  10.283 +		/* withdraw exisiting notification */
  10.284 +		g_application_withdraw_notification(application,
  10.285 +		    "package-updates");
  10.286 +		break;
  10.287 +	case PUI_STATE_NORMAL_UPDATES_AVAILABLE:	/* FALLTHGROUGH */
  10.288 +	case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE:
  10.289 +		/* create notification */
  10.290 +		notification = g_notification_new(title);
  10.291 +		g_notification_set_body(notification, body);
  10.292 +		g_notification_set_icon(notification, self->icons[self->state]);
  10.293 +		g_notification_set_priority(notification,
  10.294 +		    G_NOTIFICATION_PRIORITY_NORMAL);
  10.295 +		if (g_action_get_enabled(G_ACTION(install_updates_action))) {
  10.296 +			g_notification_add_button(notification,
  10.297 +			    _("Install Updates"),
  10.298 +			    "app.install-updates");
  10.299 +		}
  10.300 +		g_application_send_notification(application, "package-updates",
  10.301 +		    notification);
  10.302 +		break;
  10.303 +	}
  10.304 +
  10.305 +	if (notification != NULL) {
  10.306 +		g_object_unref(notification);
  10.307 +	}
  10.308 +
  10.309 +	g_debug("indicator icon: %s, notification title: \"%s\", "
  10.310 +	    "notification body: \"%s\"", icon_names[self->state], title, body);
  10.311 +
  10.312 +	g_free(body);
  10.313 +	g_free(title);
  10.314 +}
  10.315 +
  10.316 +static void
  10.317 +transition_state(PuiApplication *self)
  10.318 +{
  10.319 +	PuiState	state = self->state;
  10.320 +	guint		important_updates;
  10.321 +	guint		normal_updates;
  10.322 +	gchar		*old_state;
  10.323 +	gchar		*new_state;
  10.324 +
  10.325 +	switch (self->state) {
  10.326 +	case PUI_STATE_INITIAL:				/* FALLTHROUGH */
  10.327 +	case PUI_STATE_UP_TO_DATE:			/* FALLTHROUGH */
  10.328 +	case PUI_STATE_NORMAL_UPDATES_AVAILABLE:	/* FALLTHROUGH */
  10.329 +	case PUI_STATE_IMPORTANT_UPDATES_AVAILABLE:
  10.330 +		if (self->error_message != NULL) {
  10.331 +			state = PUI_STATE_ERROR;
  10.332 +			break;
  10.333 +		}
  10.334 +
  10.335 +		g_object_get(self->backend,
  10.336 +		    "important-updates", &important_updates,
  10.337 +		    "normal-updates", &normal_updates, NULL);
  10.338 +		if (important_updates > 0) {
  10.339 +			state = PUI_STATE_IMPORTANT_UPDATES_AVAILABLE;
  10.340 +		} else if (normal_updates > 0) {
  10.341 +			state = PUI_STATE_NORMAL_UPDATES_AVAILABLE;
  10.342 +		} else {
  10.343 +			state = PUI_STATE_UP_TO_DATE;
  10.344 +		}
  10.345 +		break;
  10.346 +	case PUI_STATE_ERROR:
  10.347 +		break;
  10.348 +	}
  10.349 +
  10.350 +	if (state != self->state) {
  10.351 +		old_state = pui_types_enum_to_string(PUI_TYPE_STATE,
  10.352 +		    self->state);
  10.353 +		new_state = pui_types_enum_to_string(PUI_TYPE_STATE, state);
  10.354 +		g_debug("state %s -> %s", old_state, new_state);
  10.355 +
  10.356 +		self->state = state;
  10.357 +		update_ui(self);
  10.358 +
  10.359 +		g_free(new_state);
  10.360 +		g_free(old_state);
  10.361 +	}
  10.362 +}
  10.363 +
  10.364 +static void
  10.365 +on_backend_restart_required(PuiBackend *backend, gpointer user_data)
  10.366 +{
  10.367 +	PuiApplication	*self = user_data;
  10.368 +
  10.369 +	restart = TRUE;
  10.370 +	g_action_group_activate_action(G_ACTION_GROUP(G_APPLICATION(self)),
  10.371 +	    "quit", NULL);
  10.372 +}
  10.373 +
  10.374 +static void
  10.375 +on_backend_state_changed(PuiBackend *backend, gpointer user_data)
  10.376 +{
  10.377 +	PuiApplication	*self = user_data;
  10.378 +
  10.379 +	transition_state(self);
  10.380 +}
  10.381 +
  10.382 +static void
  10.383 +on_pui_backend_finished(GObject *source_object, GAsyncResult *result,
  10.384 +    gpointer user_data)
  10.385 +{
  10.386 +	PuiApplication	*self = user_data;
  10.387 +	GError		*error = NULL;
  10.388 +
  10.389 +	self->backend = pui_backend_new_finish(result, &error);
  10.390 +	if (self->backend == NULL) {
  10.391 +		g_warning("failed to instantiate backend: %s", error->message);
  10.392 +		g_free(self->error_message);
  10.393 +		g_error_free(error);
  10.394 +		self->error_message = g_strdup(_("Update notifications "
  10.395 +		    "are not supported."));
  10.396 +		transition_state(self);
  10.397 +		return;
  10.398 +	}
  10.399 +
  10.400 +	g_settings_bind(self->settings, "refresh-interval", self->backend,
  10.401 +	    "refresh-interval", G_SETTINGS_BIND_GET);
  10.402 +
  10.403 +	transition_state(self);
  10.404 +
  10.405 +	g_signal_connect(self->backend, "restart-required",
  10.406 +	    G_CALLBACK(on_backend_restart_required), self);
  10.407 +	g_signal_connect(self->backend, "state-changed",
  10.408 +	    G_CALLBACK(on_backend_state_changed), self);
  10.409 +}
  10.410 +
  10.411 +static void
  10.412 +pui_application_startup(GApplication *application)
  10.413 +{
  10.414 +	PuiApplication	*self = PUI_APPLICATION(application);
  10.415 +	gsize		i;
  10.416 +	gchar		*settings_filename;
  10.417 +	GSettingsBackend *settings_backend;
  10.418 +	GtkWidget	*menu;
  10.419 +	GtkWidget	*menu_item;
  10.420 +
  10.421 +	G_APPLICATION_CLASS(pui_application_parent_class)->startup(application);
  10.422 +
  10.423 +	/* create actions */
  10.424 +	g_action_map_add_action_entries(G_ACTION_MAP(self),
  10.425 +	    pui_application_actions, G_N_ELEMENTS(pui_application_actions),
  10.426 +	    self);
  10.427 +
  10.428 +	/* load icons */
  10.429 +	for (i = 0; i < G_N_ELEMENTS(self->icons); i++) {
  10.430 +		self->icons[i] = g_themed_icon_new(icon_names[i]);
  10.431 +	}
  10.432 +
  10.433 +	/* create settings */
  10.434 +	settings_filename = g_build_filename(g_get_user_config_dir(), PACKAGE,
  10.435 +	    PACKAGE ".conf", NULL);
  10.436 +	settings_backend = g_keyfile_settings_backend_new(settings_filename,
  10.437 +	    SETTINGS_ROOT_PATH, SETTINGS_ROOT_GROUP);
  10.438 +	self->settings = g_settings_new_with_backend(APPLICATION_ID,
  10.439 +	    settings_backend);
  10.440 +	g_settings_bind(self->settings, "update-command", self,
  10.441 +	    "update-command", G_SETTINGS_BIND_GET);
  10.442 +
  10.443 +	/* start instantiating backend */
  10.444 +	pui_backend_new_async(self->cancellable, on_pui_backend_finished, self);
  10.445 +
  10.446 +	/* create indicator */
  10.447 +	self->indicator = app_indicator_new(APPLICATION_ID,
  10.448 +	    "system-software-update",
  10.449 +	    APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
  10.450 +
  10.451 +	/* build menu */
  10.452 +	menu = gtk_menu_new();
  10.453 +	gtk_widget_insert_action_group(GTK_WIDGET(menu), "app",
  10.454 +	    G_ACTION_GROUP(self));
  10.455 +
  10.456 +	menu_item = gtk_menu_item_new_with_label(_("Install "
  10.457 +	    "Updates\342\200\246"));
  10.458 +	gtk_actionable_set_action_name(GTK_ACTIONABLE(menu_item),
  10.459 +	    "app.install-updates");
  10.460 +	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
  10.461 +
  10.462 +	menu_item = gtk_menu_item_new_with_label(_("About\342\200\246"));
  10.463 +	gtk_actionable_set_action_name(GTK_ACTIONABLE(menu_item),
  10.464 +	    "app.about");
  10.465 +	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
  10.466 +
  10.467 +	gtk_widget_show_all(menu);
  10.468 +	app_indicator_set_menu(self->indicator, GTK_MENU(menu));
  10.469 +
  10.470 +	update_ui(self);
  10.471 +
  10.472 +	/* keep GApplication running */
  10.473 +	g_application_hold(application);
  10.474 +
  10.475 +	g_object_unref(settings_backend);
  10.476 +	g_free(settings_filename);
  10.477 +}
  10.478 +
  10.479 +static void
  10.480 +pui_application_shutdown(GApplication *application)
  10.481 +{
  10.482 +	GApplicationClass *application_class =
  10.483 +	    G_APPLICATION_CLASS(pui_application_parent_class);
  10.484 +
  10.485 +	application_class->shutdown(application);
  10.486 +}
  10.487 +
  10.488 +static gint
  10.489 +pui_application_handle_local_options(GApplication *application,
  10.490 +    GVariantDict *options)
  10.491 +{
  10.492 +	gchar		*messages_debug;
  10.493 +	gchar		**args = NULL;
  10.494 +	GError		*error = NULL;
  10.495 +
  10.496 +	/* filename arguments are not allowed */
  10.497 +	if (g_variant_dict_lookup(options, G_OPTION_REMAINING, "^a&ay",
  10.498 +	    &args)) {
  10.499 +		g_printerr("invalid argument: \"%s\"\n", args[0]);
  10.500 +		g_free(args);
  10.501 +		return (1);
  10.502 +	}
  10.503 +
  10.504 +	if (g_variant_dict_contains(options, "version")) {
  10.505 +		g_print("%s %s\n", PACKAGE, VERSION);
  10.506 +
  10.507 +		/* quit */
  10.508 +		return (0);
  10.509 +	}
  10.510 +
  10.511 +	if (g_variant_dict_contains(options, "debug")) {
  10.512 +		/* enable debug logging */
  10.513 +		messages_debug = g_strjoin(":", G_LOG_DOMAIN,
  10.514 +		    g_getenv("G_MESSAGES_DEBUG"), NULL);
  10.515 +		g_setenv("G_MESSAGES_DEBUG", messages_debug, TRUE);
  10.516 +		g_free(messages_debug);
  10.517 +	}
  10.518 +
  10.519 +	/*
  10.520 +	 * register with the session bus so that it is possible to discern
  10.521 +	 * between remote and primary instance and that remote actions can be
  10.522 +	 * invoked, this causes the startup signal to be emitted which, in case
  10.523 +	 * of the primary instance, starts to instantiate the
  10.524 +	 * backend with the given values
  10.525 +	 */
  10.526 +	if (!g_application_register(application, NULL, &error)) {
  10.527 +		g_critical("g_application_register: %s", error->message);
  10.528 +		g_error_free(error);
  10.529 +		return (1);
  10.530 +	}
  10.531 +
  10.532 +	if (g_variant_dict_contains(options, "quit")) {
  10.533 +		/* only valid if a remote instance is running */
  10.534 +		if (!g_application_get_is_remote(application)) {
  10.535 +			g_printerr("%s is not running\n", g_get_prgname());
  10.536 +			return (1);
  10.537 +		}
  10.538 +
  10.539 +		/* signal remote instance to quit */
  10.540 +		g_action_group_activate_action(G_ACTION_GROUP(application),
  10.541 +		    "quit", NULL);
  10.542 +
  10.543 +		/* quit local instance */
  10.544 +		return (0);
  10.545 +	}
  10.546 +
  10.547 +	/* proceed with default command line processing */
  10.548 +	return (-1);
  10.549 +}
  10.550 +
  10.551 +static void
  10.552 +pui_application_activate(GApplication *application) {
  10.553 +	GApplicationClass *application_class =
  10.554 +	    G_APPLICATION_CLASS(pui_application_parent_class);
  10.555 +
  10.556 +	/* do nothing, implementation required by GApplication */
  10.557 +
  10.558 +	application_class->activate(application);
  10.559 +}
  10.560 +
  10.561 +static void
  10.562 +pui_application_set_property(GObject *object, guint property_id,
  10.563 +    const GValue *value, GParamSpec *pspec)
  10.564 +{
  10.565 +	PuiApplication	*self = PUI_APPLICATION(object);
  10.566 +
  10.567 +	switch (property_id) {
  10.568 +	case PROP_UPDATE_COMMAND:
  10.569 +		g_free(self->update_command);
  10.570 +		self->update_command = g_value_dup_string(value);
  10.571 +		g_debug("property \"update-command\" set to \"%s\"",
  10.572 +		    self->update_command);
  10.573 +		break;
  10.574 +	default:
  10.575 +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
  10.576 +		break;
  10.577 +	}
  10.578 +}
  10.579 +
  10.580 +static void
  10.581 +pui_application_get_property(GObject *object, guint property_id, GValue *value,
  10.582 +    GParamSpec *pspec)
  10.583 +{
  10.584 +	PuiApplication	*self = PUI_APPLICATION(object);
  10.585 +
  10.586 +	switch (property_id) {
  10.587 +	case PROP_UPDATE_COMMAND:
  10.588 +		g_value_set_string(value, self->update_command);
  10.589 +		break;
  10.590 +	default:
  10.591 +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
  10.592 +		break;
  10.593 +	}
  10.594 +}
  10.595 +
  10.596 +static void
  10.597 +pui_application_dispose(GObject *object)
  10.598 +{
  10.599 +	PuiApplication	*self = PUI_APPLICATION(object);
  10.600 +	gsize		i;
  10.601 +
  10.602 +	if (self->settings != NULL) {
  10.603 +		g_signal_handlers_disconnect_by_data(self->settings, self);
  10.604 +		g_clear_object(&self->settings);
  10.605 +	}
  10.606 +
  10.607 +	if (self->cancellable != NULL) {
  10.608 +		g_cancellable_cancel(self->cancellable);
  10.609 +		g_clear_object(&self->cancellable);
  10.610 +	}
  10.611 +
  10.612 +	if (self->backend != NULL) {
  10.613 +		g_clear_object(&self->backend);
  10.614 +	}
  10.615 +
  10.616 +	if (self->indicator != NULL) {
  10.617 +		g_clear_object(&self->indicator);
  10.618 +	}
  10.619 +
  10.620 +	if (self->about_dialog != NULL) {
  10.621 +		g_clear_pointer(&self->about_dialog,
  10.622 +		    (GDestroyNotify)(gtk_widget_destroy));
  10.623 +	}
  10.624 +
  10.625 +	for (i = 0; i < G_N_ELEMENTS(self->icons); i++) {
  10.626 +		if (self->icons[i] != NULL) {
  10.627 +			g_clear_object(&self->icons[i]);
  10.628 +		}
  10.629 +	}
  10.630 +
  10.631 +	G_OBJECT_CLASS(pui_application_parent_class)->dispose(object);
  10.632 +}
  10.633 +
  10.634 +static void
  10.635 +pui_application_finalize(GObject *object)
  10.636 +{
  10.637 +	PuiApplication	*self = PUI_APPLICATION(object);
  10.638 +
  10.639 +	g_free(self->update_command);
  10.640 +	g_free(self->error_message);
  10.641 +
  10.642 +	G_OBJECT_CLASS(pui_application_parent_class)->finalize(object);
  10.643 +}
  10.644 +
  10.645 +static void
  10.646 +pui_application_class_init(PuiApplicationClass *klass)
  10.647 +{
  10.648 +	GObjectClass	*object_class = G_OBJECT_CLASS(klass);
  10.649 +	GApplicationClass *application_class = G_APPLICATION_CLASS(klass);
  10.650 +
  10.651 +	object_class->set_property = pui_application_set_property;
  10.652 +	object_class->get_property = pui_application_get_property;
  10.653 +	object_class->dispose = pui_application_dispose;
  10.654 +	object_class->finalize = pui_application_finalize;
  10.655 +
  10.656 +	properties[PROP_UPDATE_COMMAND] = g_param_spec_string("update-command",
  10.657 +	    "Update command", "Command for installing updates", NULL,
  10.658 +	    G_PARAM_READWRITE);
  10.659 +
  10.660 +	g_object_class_install_properties(object_class, PROP_LAST, properties);
  10.661 +
  10.662 +	application_class->startup = pui_application_startup;
  10.663 +	application_class->shutdown = pui_application_shutdown;
  10.664 +	application_class->handle_local_options =
  10.665 +	    pui_application_handle_local_options;
  10.666 +	application_class->activate = pui_application_activate;
  10.667 +}
  10.668 +
  10.669 +static void
  10.670 +pui_application_init(PuiApplication *self)
  10.671 +{
  10.672 +	g_application_add_main_option_entries(G_APPLICATION(self),
  10.673 +	    cmd_options);
  10.674 +
  10.675 +	self->cancellable = g_cancellable_new();
  10.676 +}
  10.677 +
  10.678 +PuiApplication *
  10.679 +pui_application_new(void)
  10.680 +{
  10.681 +	return (g_object_new(PUI_TYPE_APPLICATION, "application-id",
  10.682 +	    APPLICATION_ID, NULL));
  10.683 +}
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/pui-application.h	Sun May 20 11:32:57 2018 +0200
    11.3 @@ -0,0 +1,41 @@
    11.4 +/*
    11.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    11.6 + *
    11.7 + * Permission is hereby granted, free of charge, to any person obtaining
    11.8 + * a copy of this software and associated documentation files (the
    11.9 + * "Software"), to deal in the Software without restriction, including
   11.10 + * without limitation the rights to use, copy, modify, merge, publish,
   11.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   11.12 + * permit persons to whom the Software is furnished to do so, subject to
   11.13 + * the following conditions:
   11.14 + *
   11.15 + * The above copyright notice and this permission notice shall be included
   11.16 + * in all copies or substantial portions of the Software.
   11.17 + *
   11.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   11.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   11.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   11.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   11.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   11.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   11.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   11.25 + */
   11.26 +
   11.27 +#ifndef	PUI_APPLICATION_H
   11.28 +#define	PUI_APPLICATION_H
   11.29 +
   11.30 +#include <gio/gio.h>
   11.31 +#include <glib-object.h>
   11.32 +
   11.33 +G_BEGIN_DECLS
   11.34 +
   11.35 +#define	PUI_TYPE_APPLICATION	(pui_application_get_type())
   11.36 +
   11.37 +G_DECLARE_FINAL_TYPE(PuiApplication, pui_application, PUI, APPLICATION,
   11.38 +    GApplication)
   11.39 +
   11.40 +PuiApplication *	pui_application_new(void);
   11.41 +
   11.42 +G_END_DECLS
   11.43 +
   11.44 +#endif /* !PUI_APPLICATION_H */
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/pui-backend.c	Sun May 20 11:32:57 2018 +0200
    12.3 @@ -0,0 +1,464 @@
    12.4 +/*
    12.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    12.6 + *
    12.7 + * Permission is hereby granted, free of charge, to any person obtaining
    12.8 + * a copy of this software and associated documentation files (the
    12.9 + * "Software"), to deal in the Software without restriction, including
   12.10 + * without limitation the rights to use, copy, modify, merge, publish,
   12.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   12.12 + * permit persons to whom the Software is furnished to do so, subject to
   12.13 + * the following conditions:
   12.14 + *
   12.15 + * The above copyright notice and this permission notice shall be included
   12.16 + * in all copies or substantial portions of the Software.
   12.17 + *
   12.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   12.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   12.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   12.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   12.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   12.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   12.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   12.25 + */
   12.26 +
   12.27 +#include <errno.h>
   12.28 +#include <fcntl.h>
   12.29 +#include <packagekit-glib2/packagekit.h>
   12.30 +#include <string.h>
   12.31 +#include <sys/stat.h>
   12.32 +#include <sys/types.h>
   12.33 +#include <utime.h>
   12.34 +
   12.35 +#include "pui-common.h"
   12.36 +#include "pui-backend.h"
   12.37 +#include "pui-get-updates.h"
   12.38 +
   12.39 +struct _PuiBackend {
   12.40 +	GObject		parent_instance;
   12.41 +	PkControl	*pk_control;
   12.42 +	GCancellable	*cancellable;
   12.43 +	gint64		last_check;
   12.44 +	PkNetworkEnum	network_state;
   12.45 +	guint		periodic_check_id;
   12.46 +	guint		refresh_interval;
   12.47 +	guint		important_updates;
   12.48 +	guint		normal_updates;
   12.49 +};
   12.50 +
   12.51 +static void	pui_backend_async_initable_iface_init(gpointer, gpointer);
   12.52 +
   12.53 +G_DEFINE_TYPE_WITH_CODE(PuiBackend, pui_backend, G_TYPE_OBJECT,
   12.54 +    G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE,
   12.55 +    pui_backend_async_initable_iface_init))
   12.56 +
   12.57 +enum {
   12.58 +    STATE_CHANGED,
   12.59 +    RESTART_REQUIRED,
   12.60 +    SIGNAL_LAST
   12.61 +};
   12.62 +
   12.63 +enum {
   12.64 +    PROP_0,
   12.65 +    PROP_IMPORTANT_UPDATES,
   12.66 +    PROP_NORMAL_UPDATES,
   12.67 +    PROP_REFRESH_INTERVAL,
   12.68 +    PROP_LAST
   12.69 +};
   12.70 +
   12.71 +static guint	signals[SIGNAL_LAST] = { 0 };
   12.72 +static GParamSpec *properties[PROP_LAST] = { NULL };
   12.73 +
   12.74 +static gboolean	periodic_check(gpointer);
   12.75 +
   12.76 +GQuark
   12.77 +pui_backend_error_quark(void)
   12.78 +{
   12.79 +	return (g_quark_from_static_string("pui-backend-error-quark"));
   12.80 +}
   12.81 +
   12.82 +static void
   12.83 +process_pk_package(gpointer data, gpointer user_data)
   12.84 +{
   12.85 +	PkPackage	*package = data;
   12.86 +	PuiBackend	*self = user_data;
   12.87 +	PkInfoEnum	type_info = pk_package_get_info(package);
   12.88 +
   12.89 +	switch (type_info) {
   12.90 +	case PK_INFO_ENUM_LOW:		/* FALLTHROUGH */
   12.91 +	case PK_INFO_ENUM_ENHANCEMENT:	/* FALLTHROUGH */
   12.92 +	case PK_INFO_ENUM_NORMAL:
   12.93 +		self->normal_updates++;
   12.94 +		break;
   12.95 +	case PK_INFO_ENUM_BUGFIX:	/* FALLTHROUGH */
   12.96 +	case PK_INFO_ENUM_IMPORTANT:	/* FALLTHROUGH */
   12.97 +	case PK_INFO_ENUM_SECURITY:
   12.98 +		self->important_updates++;
   12.99 +		break;
  12.100 +	default:
  12.101 +		break;
  12.102 +	}
  12.103 +}
  12.104 +
  12.105 +static void
  12.106 +on_get_updates_finished(GObject *source_object, GAsyncResult *async_result,
  12.107 +    gpointer user_data)
  12.108 +{
  12.109 +	PuiBackend	*self = user_data;
  12.110 +	GPtrArray	*package_list = NULL;
  12.111 +	GError		*error = NULL;
  12.112 +	guint		prev_normal_updates = self->normal_updates;
  12.113 +	guint		prev_important_updates = self->important_updates;
  12.114 +
  12.115 +	package_list = pui_get_updates_finish(async_result, &error);
  12.116 +	if (package_list == NULL) {
  12.117 +		if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
  12.118 +		    g_error_matches(error, PUI_GET_UPDATES_ERROR,
  12.119 +		    PUI_GET_UPDATES_ERROR_CANCELLED)) {
  12.120 +			/* cancelled */
  12.121 +			g_debug("cancelled checking for updates");
  12.122 +		} else {
  12.123 +			g_warning("failed to check for updates: %s",
  12.124 +			    error->message);
  12.125 +		}
  12.126 +		g_error_free(error);
  12.127 +		goto out;
  12.128 +	}
  12.129 +
  12.130 +	self->normal_updates = 0;
  12.131 +	self->important_updates = 0;
  12.132 +	g_ptr_array_foreach(package_list, process_pk_package, self);
  12.133 +	g_debug("normal updates: %u, important updates: %u",
  12.134 +	    self->normal_updates, self->important_updates);
  12.135 +	if (self->normal_updates != prev_normal_updates) {
  12.136 +		g_object_notify_by_pspec(G_OBJECT(self),
  12.137 +		    properties[PROP_NORMAL_UPDATES]);
  12.138 +	}
  12.139 +	if (self->important_updates != prev_important_updates) {
  12.140 +		g_object_notify_by_pspec(G_OBJECT(self),
  12.141 +		    properties[PROP_IMPORTANT_UPDATES]);
  12.142 +	}
  12.143 +	if ((self->normal_updates != prev_normal_updates) ||
  12.144 +	    (self->important_updates != prev_important_updates)) {
  12.145 +		g_debug("emitting signal state-changed");
  12.146 +		g_signal_emit(self, signals[STATE_CHANGED], 0);
  12.147 +	}
  12.148 +
  12.149 +	/* last successful check */
  12.150 +	self->last_check = g_get_monotonic_time();
  12.151 +
  12.152 +out:
  12.153 +	/* reschedule periodic check */
  12.154 +	if (self->network_state != PK_NETWORK_ENUM_OFFLINE) {
  12.155 +		self->periodic_check_id =
  12.156 +		    g_timeout_add_seconds(PUI_CHECK_UPDATES_INTERVAL,
  12.157 +		    periodic_check, self);
  12.158 +	}
  12.159 +
  12.160 +	if (package_list != NULL) {
  12.161 +		g_ptr_array_unref(package_list);
  12.162 +	}
  12.163 +}
  12.164 +
  12.165 +static gboolean
  12.166 +periodic_check(gpointer user_data)
  12.167 +{
  12.168 +	PuiBackend	*self = user_data;
  12.169 +
  12.170 +	g_debug("running periodic check");
  12.171 +
  12.172 +	pui_get_updates_async(self->pk_control, self->refresh_interval,
  12.173 +	    self->cancellable, on_get_updates_finished, self);
  12.174 +
  12.175 +	/* next periodic check will be scheduled after completion */
  12.176 +	self->periodic_check_id = 0;
  12.177 +
  12.178 +	return (G_SOURCE_REMOVE);
  12.179 +}
  12.180 +
  12.181 +static void
  12.182 +pui_backend_set_property(GObject *object, guint property_id,
  12.183 +    const GValue *value, GParamSpec *pspec)
  12.184 +{
  12.185 +	PuiBackend	*self = PUI_BACKEND(object);
  12.186 +
  12.187 +	switch (property_id) {
  12.188 +	case PROP_REFRESH_INTERVAL:
  12.189 +		self->refresh_interval = g_value_get_uint(value);
  12.190 +		g_debug("property \"refresh-interval\" set to %u",
  12.191 +		    self->refresh_interval);
  12.192 +		break;
  12.193 +	default:
  12.194 +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
  12.195 +		break;
  12.196 +	}
  12.197 +}
  12.198 +
  12.199 +static void
  12.200 +pui_backend_get_property(GObject *object, guint property_id, GValue *value,
  12.201 +    GParamSpec *pspec)
  12.202 +{
  12.203 +	PuiBackend	*self = PUI_BACKEND(object);
  12.204 +
  12.205 +	switch (property_id) {
  12.206 +	case PROP_IMPORTANT_UPDATES:
  12.207 +		g_value_set_uint(value, self->important_updates);
  12.208 +		break;
  12.209 +	case PROP_NORMAL_UPDATES:
  12.210 +		g_value_set_uint(value, self->normal_updates);
  12.211 +		break;
  12.212 +	case PROP_REFRESH_INTERVAL:
  12.213 +		g_value_set_uint(value, self->refresh_interval);
  12.214 +		break;
  12.215 +	default:
  12.216 +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
  12.217 +		break;
  12.218 +	}
  12.219 +}
  12.220 +
  12.221 +static void
  12.222 +pui_backend_dispose(GObject *object)
  12.223 +{
  12.224 +	PuiBackend	*self = PUI_BACKEND(object);
  12.225 +
  12.226 +	if (self->periodic_check_id != 0) {
  12.227 +		g_source_remove(self->periodic_check_id);
  12.228 +		self->periodic_check_id = 0;
  12.229 +	}
  12.230 +
  12.231 +	if (self->cancellable != NULL) {
  12.232 +		g_cancellable_cancel(self->cancellable);
  12.233 +		g_clear_object(&self->cancellable);
  12.234 +	}
  12.235 +
  12.236 +	if (self->pk_control != NULL) {
  12.237 +		g_clear_object(&self->pk_control);
  12.238 +	}
  12.239 +
  12.240 +	G_OBJECT_CLASS(pui_backend_parent_class)->dispose(object);
  12.241 +}
  12.242 +
  12.243 +static void
  12.244 +pui_backend_class_init(PuiBackendClass *klass)
  12.245 +{
  12.246 +	GObjectClass	*object_class = G_OBJECT_CLASS(klass);
  12.247 +
  12.248 +	object_class->set_property = pui_backend_set_property;
  12.249 +	object_class->get_property = pui_backend_get_property;
  12.250 +	object_class->dispose = pui_backend_dispose;
  12.251 +
  12.252 +	properties[PROP_IMPORTANT_UPDATES] =
  12.253 +	    g_param_spec_uint("important-updates", "Important updates",
  12.254 +	    "Number of available important updates", 0, G_MAXUINT, 0,
  12.255 +	    G_PARAM_READABLE);
  12.256 +
  12.257 +	properties[PROP_NORMAL_UPDATES] =
  12.258 +	    g_param_spec_uint("normal-updates", "Normal updates",
  12.259 +	    "Number of available normal updates", 0, G_MAXUINT, 0,
  12.260 +	    G_PARAM_READABLE);
  12.261 +
  12.262 +	properties[PROP_REFRESH_INTERVAL] =
  12.263 +	    g_param_spec_uint("refresh-interval", "Refresh interval",
  12.264 +	    "Interval in seconds for refreshing the package cache", 0,
  12.265 +	    G_MAXUINT, PUI_DEFAULT_REFRESH_INTERVAL, G_PARAM_READWRITE);
  12.266 +
  12.267 +	g_object_class_install_properties(object_class, PROP_LAST, properties);
  12.268 +
  12.269 +	signals[STATE_CHANGED] = g_signal_new("state-changed",
  12.270 +	    G_TYPE_FROM_CLASS(object_class),
  12.271 +	    G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0,
  12.272 +	    NULL, NULL, NULL, G_TYPE_NONE, 0);
  12.273 +
  12.274 +	signals[RESTART_REQUIRED] = g_signal_new("restart-required",
  12.275 +	    G_TYPE_FROM_CLASS(object_class),
  12.276 +	    G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0,
  12.277 +	    NULL, NULL, NULL, G_TYPE_NONE, 0);
  12.278 +}
  12.279 +
  12.280 +static void
  12.281 +pui_backend_init(PuiBackend *self)
  12.282 +{
  12.283 +	self->cancellable = g_cancellable_new();
  12.284 +	self->pk_control = pk_control_new();
  12.285 +}
  12.286 +
  12.287 +static void
  12.288 +on_get_properties_finished(GObject *object, GAsyncResult *result,
  12.289 +    gpointer user_data)
  12.290 +{
  12.291 +	PkControl	*control = PK_CONTROL(object);
  12.292 +	PuiBackend	*self;
  12.293 +	GTask		*task = user_data;
  12.294 +	GError		*error = NULL;
  12.295 +	gchar		*backend_name = NULL;
  12.296 +	PkBitfield	roles = 0;
  12.297 +	gchar		*roles_str = NULL;
  12.298 +
  12.299 +	self = g_task_get_task_data(task);
  12.300 +
  12.301 +	if (!pk_control_get_properties_finish(control, result, &error)) {
  12.302 +		g_task_return_error(task, error);
  12.303 +		goto out;
  12.304 +	}
  12.305 +
  12.306 +	/* check whether the backend supports GetUpdates */
  12.307 +	g_object_get(control, "backend-name", &backend_name, "roles", &roles,
  12.308 +	    "network-state", &self->network_state, NULL);
  12.309 +	g_debug("backend: %s", backend_name);
  12.310 +	roles_str = pk_role_bitfield_to_string(roles);
  12.311 +	g_debug("roles: %s", roles_str);
  12.312 +	g_debug("network-state: %s",
  12.313 +	    pk_network_enum_to_string(self->network_state));
  12.314 +	if (!pk_bitfield_contain(roles, PK_ROLE_ENUM_GET_UPDATES)) {
  12.315 +		error = g_error_new(PUI_BACKEND_ERROR,
  12.316 +		    PUI_BACKEND_ERROR_GET_UPDATES_NOT_IMPLEMENTED,
  12.317 +		    "Getting updates is not implemented in the %s PackageKit "
  12.318 +		    "backend", backend_name);
  12.319 +		g_task_return_error(task, error);
  12.320 +		goto out;
  12.321 +	}
  12.322 +
  12.323 +	g_task_return_boolean(task, TRUE);
  12.324 +out:
  12.325 +	g_free(roles_str);
  12.326 +	g_free(backend_name);
  12.327 +	g_object_unref(task);
  12.328 +}
  12.329 +
  12.330 +static void
  12.331 +on_notify_network_state(PkControl *pk_control, GParamSpec *pspec,
  12.332 +    gpointer user_data)
  12.333 +{
  12.334 +	PuiBackend	*self = user_data;
  12.335 +	PkNetworkEnum	network_state;
  12.336 +	guint		elapsed_time;
  12.337 +	guint		remaining_time;
  12.338 +
  12.339 +	g_object_get(pk_control, "network-state", &network_state, NULL);
  12.340 +	g_debug("network state changed: %s",
  12.341 +	    pk_network_enum_to_string(network_state));
  12.342 +	if ((self->network_state == PK_NETWORK_ENUM_OFFLINE) &&
  12.343 +	    (network_state != PK_NETWORK_ENUM_OFFLINE)) {
  12.344 +		/* schedule periodic checks when coming back online */
  12.345 +		elapsed_time = (g_get_monotonic_time() - self->last_check) /
  12.346 +		    G_USEC_PER_SEC;
  12.347 +		/*
  12.348 +		 * if more time that the check interval has passed since the
  12.349 +		 * last check, schedule a check after a short delay, otherwise
  12.350 +		 * wait until the interval has passed
  12.351 +		 */
  12.352 +		remaining_time = (elapsed_time < PUI_CHECK_UPDATES_INTERVAL) ?
  12.353 +		    PUI_CHECK_UPDATES_INTERVAL - elapsed_time :
  12.354 +		    PUI_STARTUP_DELAY;
  12.355 +		self->periodic_check_id = g_timeout_add_seconds(remaining_time,
  12.356 +		    periodic_check, self);
  12.357 +	} else if ((self->network_state != PK_NETWORK_ENUM_OFFLINE) &&
  12.358 +	    (network_state == PK_NETWORK_ENUM_OFFLINE)) {
  12.359 +		/* cancel periodic checks while offline */
  12.360 +		if (self->periodic_check_id != 0) {
  12.361 +			g_source_remove(self->periodic_check_id);
  12.362 +		}
  12.363 +	}
  12.364 +	self->network_state = network_state;
  12.365 +}
  12.366 +
  12.367 +static void
  12.368 +on_updates_changed(PkControl *control, gpointer user_data)
  12.369 +{
  12.370 +	PuiBackend	*self = user_data;
  12.371 +
  12.372 +	/*
  12.373 +	 * schedule a check after a short delay so that a rapid succession of
  12.374 +	 * signals is coalesced
  12.375 +	 */
  12.376 +	if (self->network_state != PK_NETWORK_ENUM_OFFLINE) {
  12.377 +		if (self->periodic_check_id != 0) {
  12.378 +			g_source_remove(self->periodic_check_id);
  12.379 +		}
  12.380 +		self->periodic_check_id =
  12.381 +		    g_timeout_add_seconds(PUI_STARTUP_DELAY, periodic_check,
  12.382 +		    self);
  12.383 +	}
  12.384 +}
  12.385 +
  12.386 +static void
  12.387 +on_restart_schedule(PkControl *control, gpointer user_data)
  12.388 +{
  12.389 +	PuiBackend	*self = user_data;
  12.390 +
  12.391 +	g_debug("emitting signal restart-required");
  12.392 +	g_signal_emit(self, signals[RESTART_REQUIRED], 0);
  12.393 +}
  12.394 +
  12.395 +static void
  12.396 +pui_backend_init_async(GAsyncInitable *initable, int io_priority,
  12.397 +    GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
  12.398 +{
  12.399 +	PuiBackend	*self = PUI_BACKEND(initable);
  12.400 +	GTask		*task;
  12.401 +
  12.402 +	task = g_task_new(G_OBJECT(initable), cancellable, callback, user_data);
  12.403 +	g_task_set_priority(task, io_priority);
  12.404 +	g_task_set_task_data(task, g_object_ref(self),
  12.405 +	    (GDestroyNotify)g_object_unref);
  12.406 +
  12.407 +	pk_control_get_properties_async(self->pk_control, cancellable,
  12.408 +	    on_get_properties_finished, task);
  12.409 +}
  12.410 +
  12.411 +static gboolean
  12.412 +pui_backend_init_finish(GAsyncInitable *initable, GAsyncResult *result,
  12.413 +    GError **errorp)
  12.414 +{
  12.415 +	PuiBackend	*self = PUI_BACKEND(initable);
  12.416 +	GTask		*task = G_TASK(result);
  12.417 +
  12.418 +	if (!g_task_propagate_boolean(task, errorp)) {
  12.419 +		return (FALSE);
  12.420 +	}
  12.421 +
  12.422 +	/* get notification when the network state changes */
  12.423 +	g_signal_connect(self->pk_control, "notify::network-state",
  12.424 +	    G_CALLBACK(on_notify_network_state), self);
  12.425 +	/* get notifications when the available package updates change */
  12.426 +	g_signal_connect(self->pk_control, "updates-changed",
  12.427 +	    G_CALLBACK(on_updates_changed), self);
  12.428 +	/* get notifications when an application restart is required */
  12.429 +	g_signal_connect(self->pk_control, "restart-schedule",
  12.430 +	    G_CALLBACK(on_restart_schedule), self);
  12.431 +	/* schedule first check after a small delay */
  12.432 +	self->periodic_check_id = g_timeout_add_seconds(PUI_STARTUP_DELAY,
  12.433 +	    periodic_check, self);
  12.434 +
  12.435 +	return (TRUE);
  12.436 +}
  12.437 +
  12.438 +static void
  12.439 +pui_backend_async_initable_iface_init(gpointer g_iface, gpointer iface_data)
  12.440 +{
  12.441 +	GAsyncInitableIface	*iface = g_iface;
  12.442 +
  12.443 +	iface->init_async = pui_backend_init_async;
  12.444 +	iface->init_finish = pui_backend_init_finish;
  12.445 +}
  12.446 +
  12.447 +void
  12.448 +pui_backend_new_async(GCancellable *cancellable, GAsyncReadyCallback callback,
  12.449 +    gpointer user_data)
  12.450 +{
  12.451 +	g_async_initable_new_async(PUI_TYPE_BACKEND, G_PRIORITY_DEFAULT,
  12.452 +	    cancellable, callback, user_data, NULL);
  12.453 +}
  12.454 +
  12.455 +PuiBackend *
  12.456 +pui_backend_new_finish(GAsyncResult *result, GError **errorp)
  12.457 +{
  12.458 +	GObject	*object;
  12.459 +	GObject	*source_object;
  12.460 +
  12.461 +	source_object = g_async_result_get_source_object(result);
  12.462 +	object = g_async_initable_new_finish(G_ASYNC_INITABLE(source_object),
  12.463 +	    result, errorp);
  12.464 +	g_object_unref(source_object);
  12.465 +
  12.466 +	return ((object != NULL) ? PUI_BACKEND(object) : NULL);
  12.467 +}
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/pui-backend.h	Sun May 20 11:32:57 2018 +0200
    13.3 @@ -0,0 +1,49 @@
    13.4 +/*
    13.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    13.6 + *
    13.7 + * Permission is hereby granted, free of charge, to any person obtaining
    13.8 + * a copy of this software and associated documentation files (the
    13.9 + * "Software"), to deal in the Software without restriction, including
   13.10 + * without limitation the rights to use, copy, modify, merge, publish,
   13.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   13.12 + * permit persons to whom the Software is furnished to do so, subject to
   13.13 + * the following conditions:
   13.14 + *
   13.15 + * The above copyright notice and this permission notice shall be included
   13.16 + * in all copies or substantial portions of the Software.
   13.17 + *
   13.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   13.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   13.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   13.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   13.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   13.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   13.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   13.25 + */
   13.26 +
   13.27 +#ifndef	PUI_BACKEND_H
   13.28 +#define	PUI_BACKEND_H
   13.29 +
   13.30 +#include <gio/gio.h>
   13.31 +#include <glib-object.h>
   13.32 +
   13.33 +G_BEGIN_DECLS
   13.34 +
   13.35 +#define	PUI_TYPE_BACKEND	(pui_backend_get_type())
   13.36 +
   13.37 +#define	PUI_BACKEND_ERROR	(pui_backend_error_quark())
   13.38 +
   13.39 +G_DECLARE_FINAL_TYPE(PuiBackend, pui_backend, PUI, BACKEND, GObject)
   13.40 +
   13.41 +enum {
   13.42 +    PUI_BACKEND_ERROR_GET_UPDATES_NOT_IMPLEMENTED
   13.43 +};
   13.44 +
   13.45 +GQuark		pui_backend_error_quark(void);
   13.46 +void		pui_backend_new_async(GCancellable *, GAsyncReadyCallback,
   13.47 +    gpointer);
   13.48 +PuiBackend *	pui_backend_new_finish(GAsyncResult *, GError **);
   13.49 +
   13.50 +G_END_DECLS
   13.51 +
   13.52 +#endif /* !PUI_BACKEND_H */
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/pui-common.h	Sun May 20 11:32:57 2018 +0200
    14.3 @@ -0,0 +1,43 @@
    14.4 +/*
    14.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    14.6 + *
    14.7 + * Permission is hereby granted, free of charge, to any person obtaining
    14.8 + * a copy of this software and associated documentation files (the
    14.9 + * "Software"), to deal in the Software without restriction, including
   14.10 + * without limitation the rights to use, copy, modify, merge, publish,
   14.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   14.12 + * permit persons to whom the Software is furnished to do so, subject to
   14.13 + * the following conditions:
   14.14 + *
   14.15 + * The above copyright notice and this permission notice shall be included
   14.16 + * in all copies or substantial portions of the Software.
   14.17 + *
   14.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   14.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   14.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   14.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   14.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   14.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   14.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   14.25 + */
   14.26 +
   14.27 +#ifndef	PUI_COMMON_H
   14.28 +#define	PUI_COMMON_H
   14.29 +
   14.30 +G_BEGIN_DECLS
   14.31 +
   14.32 +#ifndef	PUI_STARTUP_DELAY
   14.33 +#define	PUI_STARTUP_DELAY		(3 * 60)
   14.34 +#endif /* !PUI_STARTUP_DELAY */
   14.35 +
   14.36 +#ifndef	PUI_CHECK_UPDATES_INTERVAL
   14.37 +#define	PUI_CHECK_UPDATES_INTERVAL	(60 * 60)
   14.38 +#endif /* !PUI_CHECK_UPDATES_INTERVAL */
   14.39 +
   14.40 +#ifndef	PUI_DEFAULT_REFRESH_INTERVAL
   14.41 +#define	PUI_DEFAULT_REFRESH_INTERVAL	(24 * 60 * 60)
   14.42 +#endif /* !PUI_DEFAULT_REFRESH_INTERVAL */
   14.43 +
   14.44 +G_END_DECLS
   14.45 +
   14.46 +#endif /* !PUI_COMMON_H */
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/pui-get-updates.c	Sun May 20 11:32:57 2018 +0200
    15.3 @@ -0,0 +1,227 @@
    15.4 +/*
    15.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    15.6 + *
    15.7 + * Permission is hereby granted, free of charge, to any person obtaining
    15.8 + * a copy of this software and associated documentation files (the
    15.9 + * "Software"), to deal in the Software without restriction, including
   15.10 + * without limitation the rights to use, copy, modify, merge, publish,
   15.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   15.12 + * permit persons to whom the Software is furnished to do so, subject to
   15.13 + * the following conditions:
   15.14 + *
   15.15 + * The above copyright notice and this permission notice shall be included
   15.16 + * in all copies or substantial portions of the Software.
   15.17 + *
   15.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   15.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   15.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   15.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   15.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   15.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   15.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   15.25 + */
   15.26 +
   15.27 +#include "pui-get-updates.h"
   15.28 +
   15.29 +typedef struct {
   15.30 +	PkControl	*pk_control;
   15.31 +	PkTask		*pk_task;
   15.32 +	guint		refresh_interval;
   15.33 +} PuiGetUpdates;
   15.34 +
   15.35 +GQuark
   15.36 +pui_get_updates_error_quark(void)
   15.37 +{
   15.38 +	return (g_quark_from_static_string("pui-get-updates-error-quark"));
   15.39 +}
   15.40 +
   15.41 +static void
   15.42 +on_get_updates_finished(GObject *source_object, GAsyncResult *async_result,
   15.43 +    gpointer user_data)
   15.44 +{
   15.45 +	GTask		*task = user_data;
   15.46 +	PuiGetUpdates *get_updates;
   15.47 +	PkResults	*results = NULL;
   15.48 +	PkError		*pk_error = NULL;
   15.49 +	GError		*error = NULL;
   15.50 +	gint		error_code;
   15.51 +	GPtrArray	*package_list;
   15.52 +
   15.53 +	get_updates = g_task_get_task_data(task);
   15.54 +
   15.55 +	g_debug("get updates transaction finished");
   15.56 +	results = pk_client_generic_finish(PK_CLIENT(get_updates->pk_task),
   15.57 +	    async_result, &error);
   15.58 +	if (results == NULL) {
   15.59 +		/* pass the error on */
   15.60 +		g_task_return_error(task, error);
   15.61 +		goto out;
   15.62 +	}
   15.63 +
   15.64 +	pk_error = pk_results_get_error_code(results);
   15.65 +	if (pk_error != NULL) {
   15.66 +		/* transaction failed, return error */
   15.67 +		g_debug("failed to refresh the cache: %s",
   15.68 +		    pk_error_get_details(pk_error));
   15.69 +		if (pk_error_get_code(pk_error) ==
   15.70 +		    PK_ERROR_ENUM_TRANSACTION_CANCELLED) {
   15.71 +			error_code = PUI_GET_UPDATES_ERROR_CANCELLED;
   15.72 +		} else {
   15.73 +			error_code =
   15.74 +			    PUI_GET_UPDATES_ERROR_GET_UPDATES_FAILED;
   15.75 +		}
   15.76 +		error = g_error_new(PUI_GET_UPDATES_ERROR, error_code,
   15.77 +		    "Failed to get package updates: %s",
   15.78 +		    pk_error_get_details(pk_error));
   15.79 +		g_task_return_error(task, error);
   15.80 +		g_object_unref(pk_error);
   15.81 +		goto out;
   15.82 +	}
   15.83 +
   15.84 +	/* return results */
   15.85 +	package_list = pk_results_get_package_array(results);
   15.86 +	g_assert(package_list != NULL);
   15.87 +	g_task_return_pointer(task, package_list,
   15.88 +	    (GDestroyNotify)g_ptr_array_unref);
   15.89 +
   15.90 +out:
   15.91 +	if (results != NULL) {
   15.92 +		g_object_unref(results);
   15.93 +	}
   15.94 +	g_object_unref(task);
   15.95 +}
   15.96 +
   15.97 +static void
   15.98 +on_refresh_cache_finished(GObject *source_object, GAsyncResult *async_result,
   15.99 +    gpointer user_data)
  15.100 +{
  15.101 +	GTask		*task = user_data;
  15.102 +	PuiGetUpdates *get_updates;
  15.103 +	PkResults	*results = NULL;
  15.104 +	PkClient	*pk_client;
  15.105 +	GError		*error = NULL;
  15.106 +	PkError		*pk_error = NULL;
  15.107 +	gint		error_code;
  15.108 +
  15.109 +	get_updates = g_task_get_task_data(task);
  15.110 +	pk_client = PK_CLIENT(get_updates->pk_task);
  15.111 +
  15.112 +	g_debug("refresh cache transaction finished");
  15.113 +	results = pk_client_generic_finish(pk_client, async_result, &error);
  15.114 +	if (results == NULL) {
  15.115 +		g_task_return_error(task, error);
  15.116 +		goto out;
  15.117 +	}
  15.118 +
  15.119 +	pk_error = pk_results_get_error_code(results);
  15.120 +	if (pk_error != NULL) {
  15.121 +		/* transaction failed, return error */
  15.122 +		g_debug("failed to refresh the cache: %s",
  15.123 +		    pk_error_get_details(pk_error));
  15.124 +		if (pk_error_get_code(pk_error) ==
  15.125 +		    PK_ERROR_ENUM_TRANSACTION_CANCELLED) {
  15.126 +			error_code = PUI_GET_UPDATES_ERROR_CANCELLED;
  15.127 +		} else {
  15.128 +			error_code = PUI_GET_UPDATES_ERROR_REFRESH_FAILED;
  15.129 +		}
  15.130 +		error = g_error_new(PUI_GET_UPDATES_ERROR, error_code,
  15.131 +		    "Failed to refresh the cache: %s",
  15.132 +		    pk_error_get_details(pk_error));
  15.133 +		g_task_return_error(task, error);
  15.134 +		g_object_unref(pk_error);
  15.135 +		goto out;
  15.136 +	}
  15.137 +
  15.138 +	/* cache is up to date, get updates */
  15.139 +	pk_client_get_updates_async(pk_client,
  15.140 +	    pk_bitfield_value(PK_FILTER_ENUM_NONE),
  15.141 +	    g_task_get_cancellable(task), NULL, NULL, on_get_updates_finished,
  15.142 +	    task);
  15.143 +
  15.144 +out:
  15.145 +	if (results != NULL) {
  15.146 +		g_object_unref(results);
  15.147 +	}
  15.148 +}
  15.149 +
  15.150 +static void
  15.151 +on_get_time_since_refresh_finished(GObject *source_object,
  15.152 +    GAsyncResult *async_result, gpointer user_data)
  15.153 +{
  15.154 +	GTask		*task = user_data;
  15.155 +	PuiGetUpdates *get_updates;
  15.156 +	guint		last_refresh;
  15.157 +	GError		*error = NULL;
  15.158 +	PkClient	*pk_client;
  15.159 +
  15.160 +	get_updates = g_task_get_task_data(task);
  15.161 +	pk_client = PK_CLIENT(get_updates->pk_task);
  15.162 +
  15.163 +	last_refresh =
  15.164 +	    pk_control_get_time_since_action_finish(get_updates->pk_control,
  15.165 +	    async_result, &error);
  15.166 +	if (last_refresh == 0) {
  15.167 +		g_task_return_error(task, error);
  15.168 +		g_object_unref(task);
  15.169 +		return;
  15.170 +	}
  15.171 +	g_debug("time since last cache refresh: %us", last_refresh);
  15.172 +
  15.173 +	if (last_refresh > get_updates->refresh_interval) {
  15.174 +		/* cache is out of date, refresh first */
  15.175 +		g_debug("refreshing the cache");
  15.176 +		pk_client_refresh_cache_async(pk_client, FALSE,
  15.177 +		    g_task_get_cancellable(task), NULL, NULL,
  15.178 +		    on_refresh_cache_finished, task);
  15.179 +	} else {
  15.180 +		/* cache is up to date, get updates */
  15.181 +		g_debug("getting updates");
  15.182 +		pk_client_get_updates_async(pk_client,
  15.183 +		    pk_bitfield_value(PK_FILTER_ENUM_NONE),
  15.184 +		    g_task_get_cancellable(task), NULL, NULL,
  15.185 +		    on_get_updates_finished, task);
  15.186 +	}
  15.187 +}
  15.188 +
  15.189 +static void
  15.190 +pui_get_updates_free(gpointer data)
  15.191 +{
  15.192 +	PuiGetUpdates *get_updates = data;
  15.193 +
  15.194 +	g_object_unref(get_updates->pk_control);
  15.195 +	g_object_unref(get_updates->pk_task);
  15.196 +	g_slice_free(PuiGetUpdates, data);
  15.197 +}
  15.198 +
  15.199 +void
  15.200 +pui_get_updates_async(PkControl *pk_control, guint refresh_interval,
  15.201 +    GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
  15.202 +{
  15.203 +	PuiGetUpdates *get_updates;
  15.204 +	GTask		*task;
  15.205 +	PkClient	*pk_client;
  15.206 +
  15.207 +	get_updates = g_slice_new0(PuiGetUpdates);
  15.208 +	get_updates->pk_control = g_object_ref(pk_control);
  15.209 +	get_updates->pk_task = pk_task_new();
  15.210 +	get_updates->refresh_interval = refresh_interval;
  15.211 +
  15.212 +	pk_client = PK_CLIENT(get_updates->pk_task);
  15.213 +	pk_client_set_cache_age(pk_client, refresh_interval);
  15.214 +	pk_client_set_background(pk_client, TRUE);
  15.215 +
  15.216 +	task = g_task_new(NULL, cancellable, callback, user_data);
  15.217 +	g_task_set_task_data(task, get_updates, pui_get_updates_free);
  15.218 +
  15.219 +	/* check whether to refresh the cache before checking for updates */
  15.220 +	g_debug("getting the time since the cache was last refreshed");
  15.221 +	pk_control_get_time_since_action_async(pk_control,
  15.222 +	    PK_ROLE_ENUM_REFRESH_CACHE, cancellable,
  15.223 +	    on_get_time_since_refresh_finished, task);
  15.224 +}
  15.225 +
  15.226 +GPtrArray *
  15.227 +pui_get_updates_finish(GAsyncResult *result, GError **errorp)
  15.228 +{
  15.229 +	return (g_task_propagate_pointer(G_TASK(result), errorp));
  15.230 +}
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/pui-get-updates.h	Sun May 20 11:32:57 2018 +0200
    16.3 @@ -0,0 +1,48 @@
    16.4 +/*
    16.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    16.6 + *
    16.7 + * Permission is hereby granted, free of charge, to any person obtaining
    16.8 + * a copy of this software and associated documentation files (the
    16.9 + * "Software"), to deal in the Software without restriction, including
   16.10 + * without limitation the rights to use, copy, modify, merge, publish,
   16.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   16.12 + * permit persons to whom the Software is furnished to do so, subject to
   16.13 + * the following conditions:
   16.14 + *
   16.15 + * The above copyright notice and this permission notice shall be included
   16.16 + * in all copies or substantial portions of the Software.
   16.17 + *
   16.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   16.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   16.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   16.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   16.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   16.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   16.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   16.25 + */
   16.26 +
   16.27 +#ifndef	PUI_GET_UPDATES_H
   16.28 +#define	PUI_GET_UPDATES_H
   16.29 +
   16.30 +#include <gio/gio.h>
   16.31 +#include <glib-object.h>
   16.32 +#include <packagekit-glib2/packagekit.h>
   16.33 +
   16.34 +G_BEGIN_DECLS
   16.35 +
   16.36 +#define	PUI_GET_UPDATES_ERROR	(pui_get_updates_error_quark())
   16.37 +
   16.38 +enum {
   16.39 +    PUI_GET_UPDATES_ERROR_REFRESH_FAILED,
   16.40 +    PUI_GET_UPDATES_ERROR_GET_UPDATES_FAILED,
   16.41 +    PUI_GET_UPDATES_ERROR_CANCELLED
   16.42 +};
   16.43 +
   16.44 +GQuark		pui_get_updates_error_quark(void);
   16.45 +void		pui_get_updates_async(PkControl *, guint, GCancellable *,
   16.46 +    GAsyncReadyCallback, gpointer);
   16.47 +GPtrArray *	pui_get_updates_finish(GAsyncResult *, GError **);
   16.48 +
   16.49 +G_END_DECLS
   16.50 +
   16.51 +#endif /* !PUI_GET_UPDATES_H */
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/pui-types.c	Sun May 20 11:32:57 2018 +0200
    17.3 @@ -0,0 +1,72 @@
    17.4 +/*
    17.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    17.6 + *
    17.7 + * Permission is hereby granted, free of charge, to any person obtaining
    17.8 + * a copy of this software and associated documentation files (the
    17.9 + * "Software"), to deal in the Software without restriction, including
   17.10 + * without limitation the rights to use, copy, modify, merge, publish,
   17.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   17.12 + * permit persons to whom the Software is furnished to do so, subject to
   17.13 + * the following conditions:
   17.14 + *
   17.15 + * The above copyright notice and this permission notice shall be included
   17.16 + * in all copies or substantial portions of the Software.
   17.17 + *
   17.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   17.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   17.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   17.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   17.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   17.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   17.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   17.25 + */
   17.26 +
   17.27 +#include "pui-types.h"
   17.28 +
   17.29 +#define	PUI_DEFINE_ENUM_VALUE(valuename, valuenick) \
   17.30 +    { valuename, #valuename, #valuenick },
   17.31 +#define PUI_DEFINE_ENUM_TYPE(EnumName, enum_name, values) \
   17.32 +    GType                                                                     \
   17.33 +    enum_name##_get_type(void)                                                \
   17.34 +    {                                                                         \
   17.35 +    	static volatile gsize g_define_type_id__volatile = 0;                 \
   17.36 +    	static const GEnumValue v[] = {                                       \
   17.37 +    	    values                                                            \
   17.38 +    	    { 0 }                                                             \
   17.39 +    	};                                                                    \
   17.40 +    	GType		g_define_type_id;                                     \
   17.41 +    	if (g_once_init_enter(&g_define_type_id__volatile)) {                 \
   17.42 +    		g_define_type_id =                                            \
   17.43 +    		    g_enum_register_static(g_intern_static_string(#EnumName), \
   17.44 +    		    v);                                                       \
   17.45 +    		g_once_init_leave(&g_define_type_id__volatile,                \
   17.46 +    		    g_define_type_id);                                        \
   17.47 +    	}                                                                     \
   17.48 +    	return (g_define_type_id__volatile);                                  \
   17.49 +    }
   17.50 +
   17.51 +PUI_DEFINE_ENUM_TYPE(PuiState, pui_state,
   17.52 +    PUI_DEFINE_ENUM_VALUE(PUI_STATE_UP_TO_DATE, "up-to-date")
   17.53 +    PUI_DEFINE_ENUM_VALUE(PUI_STATE_NORMAL_UPDATES_AVAILABLE,
   17.54 +    "normal-updates-available")
   17.55 +    PUI_DEFINE_ENUM_VALUE(PUI_STATE_IMPORTANT_UPDATES_AVAILABLE,
   17.56 +    "important-updates-available")
   17.57 +    PUI_DEFINE_ENUM_VALUE(PUI_STATE_ERROR, "error"))
   17.58 +
   17.59 +gchar *
   17.60 +pui_types_enum_to_string(GType type, gint value)
   17.61 +{
   17.62 +	GTypeClass	*type_class;
   17.63 +	GEnumValue	*enum_value;
   17.64 +
   17.65 +	type_class = g_type_class_ref(type);
   17.66 +
   17.67 +	g_return_val_if_fail(G_IS_ENUM_CLASS(type_class), NULL);
   17.68 +
   17.69 +	enum_value = g_enum_get_value(G_ENUM_CLASS(type_class), value);
   17.70 +	if (enum_value == NULL) {
   17.71 +		return (g_strdup_printf("%d", value));
   17.72 +	}
   17.73 +
   17.74 +	return (g_strdup(enum_value->value_nick));
   17.75 +}
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/pui-types.h	Sun May 20 11:32:57 2018 +0200
    18.3 @@ -0,0 +1,47 @@
    18.4 +/*
    18.5 + * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
    18.6 + *
    18.7 + * Permission is hereby granted, free of charge, to any person obtaining
    18.8 + * a copy of this software and associated documentation files (the
    18.9 + * "Software"), to deal in the Software without restriction, including
   18.10 + * without limitation the rights to use, copy, modify, merge, publish,
   18.11 + * distribute, sublicense, and/or sell copies of the Software, and to
   18.12 + * permit persons to whom the Software is furnished to do so, subject to
   18.13 + * the following conditions:
   18.14 + *
   18.15 + * The above copyright notice and this permission notice shall be included
   18.16 + * in all copies or substantial portions of the Software.
   18.17 + *
   18.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   18.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   18.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   18.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   18.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   18.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   18.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   18.25 + */
   18.26 +
   18.27 +#ifndef	PUI_TYPES_H
   18.28 +#define	PUI_TYPES_H
   18.29 +
   18.30 +#include <glib-object.h>
   18.31 +
   18.32 +G_BEGIN_DECLS
   18.33 +
   18.34 +#define	PUI_TYPE_STATE	(pui_state_get_type())
   18.35 +
   18.36 +typedef enum {
   18.37 +    PUI_STATE_INITIAL,
   18.38 +    PUI_STATE_UP_TO_DATE,
   18.39 +    PUI_STATE_NORMAL_UPDATES_AVAILABLE,
   18.40 +    PUI_STATE_IMPORTANT_UPDATES_AVAILABLE,
   18.41 +    PUI_STATE_ERROR,
   18.42 +    PUI_STATE_LAST
   18.43 +} PuiState;
   18.44 +
   18.45 +GType	pui_state_get_type(void);
   18.46 +gchar *	pui_types_enum_to_string(GType, gint);
   18.47 +
   18.48 +G_END_DECLS
   18.49 +
   18.50 +#endif /* !PUI_TYPES_H */