From 616c68b0f01cde46bdec4b7f2e5eff36a1cd909a Mon Sep 17 00:00:00 2001 From: ncoop Date: Sat, 9 Apr 2016 04:44:56 -0700 Subject: [PATCH 01/11] Notification displayed on rightclick by default Notification summary shows the count Notification icon is `software-update-available` notif_body concatenates raw or prepared output from all backends Backends that don't yet output to notif_body should have harmless empty string Also, linted according to flake8 Corrected typo --- i3pystatus/updates/__init__.py | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/i3pystatus/updates/__init__.py b/i3pystatus/updates/__init__.py index eee9e90..b5cf2e1 100644 --- a/i3pystatus/updates/__init__.py +++ b/i3pystatus/updates/__init__.py @@ -2,6 +2,7 @@ import threading from i3pystatus import SettingsBase, Module, formatp from i3pystatus.core.util import internet, require +from i3pystatus.core.desktop import DesktopNotification class Backend(SettingsBase): @@ -21,10 +22,11 @@ class Updates(Module): .. rubric:: Available formatters * `{count}` — Sum of all available updates from all backends. - * For each backend registered there is one formatter named after the backend, - multiple identical backends do not accumulate, but overwrite each other. - * For example, `{Cower}` (note capitcal C) is the number of updates reported by - the cower backend, assuming it has been registered. + * For each backend registered there is one formatter named after the + backend, multiple identical backends do not accumulate, but overwrite + each other. + * For example, `{Cower}` (note capital C) is the number of updates + reported by the cower backend, assuming it has been registered. .. rubric:: Usage example @@ -50,9 +52,11 @@ class Updates(Module): ("backends", "Required list of backends used to check for updates."), ("format", "Format used when updates are available. " "May contain formatters."), - ("format_no_updates", "String that is shown if no updates are available." - " If not set the module will be hidden if no updates are available."), - ("format_working", "Format used while update queries are run. By default the same as ``format``."), + ("format_no_updates", "String that is shown if no updates are " + "available. If not set the module will be hidden if no updates " + "are available."), + ("format_working", "Format used while update queries are run. By " + "default the same as ``format``."), "color", "color_no_updates", "color_working", @@ -69,6 +73,7 @@ class Updates(Module): color_working = None on_leftclick = "run" + on_rightclick = "report" def init(self): if not isinstance(self.backends, list): @@ -79,6 +84,7 @@ class Updates(Module): self.data = { "count": 0 } + self.notif_body = {} self.condition = threading.Condition() self.thread = threading.Thread(target=self.update_thread, daemon=True) self.thread.start() @@ -96,6 +102,8 @@ class Updates(Module): key = backend.__class__.__name__ if key not in self.data: self.data[key] = '?' + if key not in self.notif_body: + self.notif_body[key] = '?' self.output = { "full_text": formatp(self.format_working, **self.data).strip(), @@ -104,9 +112,11 @@ class Updates(Module): updates_count = 0 for backend in self.backends: - updates = backend.updates + name = backend.__class__.__name__ + updates, notif_body = backend.updates updates_count += updates - self.data[backend.__class__.__name__] = updates + self.data[name] = updates + self.notif_body[name] = notif_body or "" if updates_count == 0: self.output = {} if not self.format_no_updates else { @@ -124,3 +134,12 @@ class Updates(Module): def run(self): with self.condition: self.condition.notify() + + def report(self): + DesktopNotification( + title=formatp(self.format, **self.data).strip(), + body="\n".join(self.notif_body.values()), + icon="software-update-available", + urgency=1, + timeout=0, + ).display() From 7fb0794f45f9015102991464d43bc0a3e79b727a Mon Sep 17 00:00:00 2001 From: ncoop Date: Sat, 9 Apr 2016 22:54:26 -0700 Subject: [PATCH 02/11] Now returns both the count and a notification body --- i3pystatus/updates/dnf.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/i3pystatus/updates/dnf.py b/i3pystatus/updates/dnf.py index efd4bda..04f05e8 100644 --- a/i3pystatus/updates/dnf.py +++ b/i3pystatus/updates/dnf.py @@ -1,11 +1,14 @@ from i3pystatus.core.command import run_through_shell from i3pystatus.updates import Backend -from re import split +from re import split, sub class Dnf(Backend): """ - Gets update count for RPM-based distributions with dnf. + Gets updates for RPM-based distributions with `dnf check-update`. + + The notification body consists of the status line followed by the package + name and version for each update. https://dnf.readthedocs.org/en/latest/command_ref.html#check-update-command """ @@ -14,12 +17,14 @@ class Dnf(Backend): def updates(self): command = ["dnf", "check-update"] dnf = run_through_shell(command) + raw = dnf.out update_count = 0 if dnf.rc == 100: - lines = dnf.out.splitlines()[2:] + lines = raw.splitlines()[2:] lines = [l for l in lines if len(split("\s{2,}", l.rstrip())) == 3] update_count = len(lines) - return update_count + notif_body = sub(r"(\S+)\s+(\S+)\s+\S+\s*\n", r"\1: \2\n", raw) + return update_count, notif_body Backend = Dnf From d7d372ff5d0192cddc80d67bfa8ddd311c42b539 Mon Sep 17 00:00:00 2001 From: ncoop Date: Wed, 13 Apr 2016 12:34:44 -0700 Subject: [PATCH 03/11] Added format_summary option By default, same as format --- i3pystatus/updates/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/i3pystatus/updates/__init__.py b/i3pystatus/updates/__init__.py index b5cf2e1..5be8d40 100644 --- a/i3pystatus/updates/__init__.py +++ b/i3pystatus/updates/__init__.py @@ -19,6 +19,9 @@ class Updates(Module): Left clicking on the module will refresh the count of upgradeable packages. This may be used to dismiss the notification after updating your system. + Right clicking shows a desktop notification with a summary count and a list + of available updates. + .. rubric:: Available formatters * `{count}` — Sum of all available updates from all backends. @@ -57,6 +60,8 @@ class Updates(Module): "are available."), ("format_working", "Format used while update queries are run. By " "default the same as ``format``."), + ("format_summary", "Format for the summary line of notifications. By " + "default the same as ``format``."), "color", "color_no_updates", "color_working", @@ -68,6 +73,7 @@ class Updates(Module): format = "Updates: {count}" format_no_updates = None format_working = None + format_summary = None color = "#00DD00" color_no_updates = None color_working = None @@ -80,6 +86,8 @@ class Updates(Module): self.backends = [self.backends] if self.format_working is None: # we want to allow an empty format self.format_working = self.format + if self.format_summary is None: # we want to allow an empty format + self.format_summary = self.format self.color_working = self.color_working or self.color self.data = { "count": 0 @@ -137,7 +145,7 @@ class Updates(Module): def report(self): DesktopNotification( - title=formatp(self.format, **self.data).strip(), + title=formatp(self.format_summary, **self.data).strip(), body="\n".join(self.notif_body.values()), icon="software-update-available", urgency=1, From 51675430b1697c50410fbd70cebe16d8fee3219d Mon Sep 17 00:00:00 2001 From: ncoop Date: Wed, 13 Apr 2016 12:56:30 -0700 Subject: [PATCH 04/11] Notification icon is customizable, and can be None --- i3pystatus/updates/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/i3pystatus/updates/__init__.py b/i3pystatus/updates/__init__.py index 5be8d40..2aef974 100644 --- a/i3pystatus/updates/__init__.py +++ b/i3pystatus/updates/__init__.py @@ -62,6 +62,9 @@ class Updates(Module): "default the same as ``format``."), ("format_summary", "Format for the summary line of notifications. By " "default the same as ``format``."), + ("notification_icon", "Icon shown when reporting the list of updates. " + "Default is ``software-update-available``, and can be " + "None for no icon."), "color", "color_no_updates", "color_working", @@ -74,6 +77,7 @@ class Updates(Module): format_no_updates = None format_working = None format_summary = None + notification_icon = "software-update-available" color = "#00DD00" color_no_updates = None color_working = None @@ -147,7 +151,7 @@ class Updates(Module): DesktopNotification( title=formatp(self.format_summary, **self.data).strip(), body="\n".join(self.notif_body.values()), - icon="software-update-available", + icon=self.notification_icon, urgency=1, timeout=0, ).display() From 48273a82058478909bd5c52cabb0d74c9b6c8f38 Mon Sep 17 00:00:00 2001 From: ncoop Date: Wed, 13 Apr 2016 13:05:12 -0700 Subject: [PATCH 05/11] Unknown update list gives empty string. --- i3pystatus/updates/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3pystatus/updates/__init__.py b/i3pystatus/updates/__init__.py index 2aef974..3e2beb2 100644 --- a/i3pystatus/updates/__init__.py +++ b/i3pystatus/updates/__init__.py @@ -113,9 +113,9 @@ class Updates(Module): for backend in self.backends: key = backend.__class__.__name__ if key not in self.data: - self.data[key] = '?' + self.data[key] = "?" if key not in self.notif_body: - self.notif_body[key] = '?' + self.notif_body[key] = "" self.output = { "full_text": formatp(self.format_working, **self.data).strip(), From 5272e102082ac1eb770324538485a73a6f4733cd Mon Sep 17 00:00:00 2001 From: ncoop Date: Sat, 4 Jun 2016 20:32:33 -0700 Subject: [PATCH 06/11] Dump data by running as __main__. --- i3pystatus/updates/dnf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/i3pystatus/updates/dnf.py b/i3pystatus/updates/dnf.py index 04f05e8..360d764 100644 --- a/i3pystatus/updates/dnf.py +++ b/i3pystatus/updates/dnf.py @@ -28,3 +28,10 @@ class Dnf(Backend): return update_count, notif_body Backend = Dnf + +if __name__ == "__main__": + """ + Call this module directly; Print the update count and notification body. + """ + dnf = Dnf() + print("Updates: {}\n\n{}".format(*dnf.updates)) From b73dbb1a352f06092d8d0a869363eb8ddc0922e5 Mon Sep 17 00:00:00 2001 From: ncoop Date: Sat, 4 Jun 2016 20:53:12 -0700 Subject: [PATCH 07/11] Return early if the check threw an error. --- i3pystatus/updates/dnf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/i3pystatus/updates/dnf.py b/i3pystatus/updates/dnf.py index 360d764..31125a4 100644 --- a/i3pystatus/updates/dnf.py +++ b/i3pystatus/updates/dnf.py @@ -17,8 +17,10 @@ class Dnf(Backend): def updates(self): command = ["dnf", "check-update"] dnf = run_through_shell(command) - raw = dnf.out + if dnf.err: + return "?", dnf.err + raw = dnf.out update_count = 0 if dnf.rc == 100: lines = raw.splitlines()[2:] From ae1274d5d31298c5e80719dd6e38512ff78a49f4 Mon Sep 17 00:00:00 2001 From: ncoop Date: Sat, 4 Jun 2016 21:30:24 -0700 Subject: [PATCH 08/11] Makes update count algo less wrong. --- i3pystatus/updates/dnf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3pystatus/updates/dnf.py b/i3pystatus/updates/dnf.py index 31125a4..8ea8a77 100644 --- a/i3pystatus/updates/dnf.py +++ b/i3pystatus/updates/dnf.py @@ -24,7 +24,7 @@ class Dnf(Backend): update_count = 0 if dnf.rc == 100: lines = raw.splitlines()[2:] - lines = [l for l in lines if len(split("\s{2,}", l.rstrip())) == 3] + lines = [l for l in lines if len(split("\s+", l.rstrip())) == 3] update_count = len(lines) notif_body = sub(r"(\S+)\s+(\S+)\s+\S+\s*\n", r"\1: \2\n", raw) return update_count, notif_body From 9d907732f18807ccc6eb6f2878f6b3b4fbbd277f Mon Sep 17 00:00:00 2001 From: ncoop Date: Tue, 21 Jun 2016 15:49:56 -0700 Subject: [PATCH 09/11] Update notifications for pacman, cower, yaourt. These modules are also runnable directly from terminal. --- i3pystatus/updates/cower.py | 8 +++++++- i3pystatus/updates/dnf.py | 3 +-- i3pystatus/updates/pacman.py | 8 +++++++- i3pystatus/updates/yaourt.py | 11 +++++++++-- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/i3pystatus/updates/cower.py b/i3pystatus/updates/cower.py index 144f156..2700444 100644 --- a/i3pystatus/updates/cower.py +++ b/i3pystatus/updates/cower.py @@ -13,6 +13,12 @@ class Cower(Backend): def updates(self): command = ["cower", "-u"] cower = run_through_shell(command) - return cower.out.count('\n') + return cower.out.count('\n'), cower.out Backend = Cower + +if __name__ == "__main__": + """ + Call this module directly; Print the update count and notification body. + """ + print("Updates: {}\n\n{}".format(*Backend().updates)) diff --git a/i3pystatus/updates/dnf.py b/i3pystatus/updates/dnf.py index 8ea8a77..c57ee4b 100644 --- a/i3pystatus/updates/dnf.py +++ b/i3pystatus/updates/dnf.py @@ -35,5 +35,4 @@ if __name__ == "__main__": """ Call this module directly; Print the update count and notification body. """ - dnf = Dnf() - print("Updates: {}\n\n{}".format(*dnf.updates)) + print("Updates: {}\n\n{}".format(*Backend().updates)) diff --git a/i3pystatus/updates/pacman.py b/i3pystatus/updates/pacman.py index 8f40945..45ac537 100644 --- a/i3pystatus/updates/pacman.py +++ b/i3pystatus/updates/pacman.py @@ -12,6 +12,12 @@ class Pacman(Backend): def updates(self): command = ["checkupdates"] checkupdates = run_through_shell(command) - return checkupdates.out.count('\n') + return checkupdates.out.count("\n"), checkupdates.out Backend = Pacman + +if __name__ == "__main__": + """ + Call this module directly; Print the update count and notification body. + """ + print("Updates: {}\n\n{}".format(*Backend().updates)) diff --git a/i3pystatus/updates/yaourt.py b/i3pystatus/updates/yaourt.py index 3738ff6..e43b9e9 100644 --- a/i3pystatus/updates/yaourt.py +++ b/i3pystatus/updates/yaourt.py @@ -25,8 +25,15 @@ class Yaourt(Backend): def updates(self): command = ["yaourt", "-Qua"] checkupdates = run_through_shell(command) + out = checkupdates.out if(self.aur_only): - return len(re.findall("^aur/", checkupdates.out, flags=re.M)) - return checkupdates.out.count("\n") + out = [line for line in out if line.startswith("aur")] + return out.count("\n"), out Backend = Yaourt + +if __name__ == "__main__": + """ + Call this module directly; Print the update count and notification body. + """ + print("Updates: {}\n\n{}".format(*Backend().updates)) From f03926ed71783d91052418791615d353c21be6d7 Mon Sep 17 00:00:00 2001 From: ncoop Date: Tue, 21 Jun 2016 16:11:46 -0700 Subject: [PATCH 10/11] Put yaourt docs into code blocks; 80 char width. --- i3pystatus/updates/yaourt.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/i3pystatus/updates/yaourt.py b/i3pystatus/updates/yaourt.py index e43b9e9..9489a42 100644 --- a/i3pystatus/updates/yaourt.py +++ b/i3pystatus/updates/yaourt.py @@ -1,4 +1,3 @@ -import re from i3pystatus.core.command import run_through_shell from i3pystatus.updates import Backend @@ -6,16 +5,22 @@ from i3pystatus.updates import Backend class Yaourt(Backend): """ This module counts the available updates using yaourt. - By default it will only count aur packages. Thus it can be used with the pacman backend like this: + By default it will only count aur packages. Thus it can be used with the + pacman backend like this: - from i3pystatus.updates import pacman, yaourt - status.register("updates", backends = [pacman.Pacman(), yaourt.Yaourt()]) + .. code-block:: python - If you want to count both pacman and aur packages with this module you can set the variable - count_only_aur = False like this: + from i3pystatus.updates import pacman, yaourt + status.register("updates", backends = \ +[pacman.Pacman(), yaourt.Yaourt()]) - from i3pystatus.updates import yaourt - status.register("updates", backends = [yaourt.Yaourt(False)]) + If you want to count both pacman and aur packages with this module you can + set the variable count_only_aur = False like this: + + .. code-block:: python + + from i3pystatus.updates import yaourt + status.register("updates", backends = [yaourt.Yaourt(False)]) """ def __init__(self, aur_only=True): From 25054685154c76b79f1b30c298964c6530c6b3a7 Mon Sep 17 00:00:00 2001 From: ncoop Date: Wed, 22 Jun 2016 23:57:40 -0700 Subject: [PATCH 11/11] Notification support for aptget. --- i3pystatus/updates/aptget.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/i3pystatus/updates/aptget.py b/i3pystatus/updates/aptget.py index f73c733..432f520 100644 --- a/i3pystatus/updates/aptget.py +++ b/i3pystatus/updates/aptget.py @@ -23,10 +23,14 @@ class AptGet(Backend): command = "apt-get upgrade -s -o Dir::State::Lists=" + cache_dir apt = run_through_shell(command.split()) - update_count = 0 - for line in apt.out.split("\n"): - if line.startswith("Inst"): - update_count += 1 - return update_count + out = apt.out.splitlines() + out = [line[5:] for line in apt.out if line.startswith("Inst ")] + return out.count("\n"), out Backend = AptGet + +if __name__ == "__main__": + """ + Call this module directly; Print the update count and notification body. + """ + print("Updates: {}\n\n{}".format(*Backend().updates))