From 56c93ac0de151d9c3f68260079c901007a492f35 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 13:05:55 +0100 Subject: [PATCH 01/20] Update modsde.py --- modsde.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modsde.py b/modsde.py index ce89a97..1dc6581 100644 --- a/modsde.py +++ b/modsde.py @@ -8,7 +8,7 @@ import re import cookielib import xml.etree.ElementTree as ET -class ModsDeChecker: +class ModsDeChecker(object): """ This class returns i3status parsable output of the number of unread posts in any bookmark in the mods.de forums. @@ -95,4 +95,4 @@ class ModsDeChecker: return {'full_text' : '%d new posts in bookmarks' % unread, 'name' : 'modsde', 'urgent' : 'true', - 'color' : self.settings['color']} \ No newline at end of file + 'color' : self.settings['color']} From 45f9ba3c66bf298c3dc66323ab9dfe25f2498991 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 13:06:12 +0100 Subject: [PATCH 02/20] Update statushandler.py --- statushandler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/statushandler.py b/statushandler.py index ef29007..560513f 100644 --- a/statushandler.py +++ b/statushandler.py @@ -4,7 +4,7 @@ import sys import json import urllib2 -class I3statusHandler: +class I3statusHandler(object): modules = [] def __init__(self): @@ -69,4 +69,4 @@ def has_internet_connection(): response=urllib2.urlopen('http://74.125.113.99',timeout=1) return True except urllib2.URLError as err: pass - return False \ No newline at end of file + return False From 71c64b2c263e5f727343f6560a13d38ffe04b095 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 15:52:52 +0100 Subject: [PATCH 03/20] As far as I can tell that IP address is down (both ping and nmap -Pn confirm this) --- statushandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statushandler.py b/statushandler.py index 560513f..969ab35 100644 --- a/statushandler.py +++ b/statushandler.py @@ -66,7 +66,7 @@ class I3statusHandler(object): def has_internet_connection(): try: - response=urllib2.urlopen('http://74.125.113.99',timeout=1) + response=urllib2.urlopen('http://173.194.69.94',timeout=1) return True except urllib2.URLError as err: pass return False From a5db47fcf82fc33ce0793d2c10f12654cb519fdb Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 16:03:52 +0100 Subject: [PATCH 04/20] Python 3 --- README.md | 14 ++++++++++++-- mailchecker.py | 2 +- modsde.py | 16 ++++++++-------- statushandler.py | 6 +++--- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 37347a5..0f56bb3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Change your i3wm config to the following: # i3bar bar { - status_command i3status | python2 ~/.config/i3status/contrib/wrapper.py + status_command i3status | python ~/.config/i3status/contrib/wrapper.py position top workspace_buttons yes } @@ -32,6 +32,15 @@ Change your i3wm config to the following: And finally adjust the settings in `~/.config/i3status/contrib/wrapper.py` as you like. +## Modules + +### thunderbirdnewmail + +Requires + +* python-dbus +* python-gobject2 + ## Contribute To contribute a script, make sure it has a function `output()` that outputs @@ -39,4 +48,5 @@ valid json code that can be interpreted by i3status. The protocol is documented here: [i3status Protocol](http://i3wm.org/docs/i3bar-protocol.html). Please add an example for how to configure it to `wrapper.py.dist`. It should be -a python class that can be registered with the `I3StatusHandler` class. \ No newline at end of file +a python class that can be registered with the `I3StatusHandler` class. + diff --git a/mailchecker.py b/mailchecker.py index 721a5f5..f367336 100644 --- a/mailchecker.py +++ b/mailchecker.py @@ -87,7 +87,7 @@ class MailChecker(object): try: self.connection.select() - except Exception,e: + except Exception as e: self.connection = None return self.connection diff --git a/modsde.py b/modsde.py index ce89a97..54bd149 100644 --- a/modsde.py +++ b/modsde.py @@ -3,9 +3,9 @@ import sys import json from datetime import datetime,timedelta -import urllib, urllib2 +import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse import re -import cookielib +import http.cookiejar import xml.etree.ElementTree as ET class ModsDeChecker: @@ -31,11 +31,11 @@ class ModsDeChecker: def __init__(self, settings = None): self.settings.update(settings) - self.cj = cookielib.CookieJar() + self.cj = http.cookiejar.CookieJar() self.last_checked = \ datetime.now() - timedelta(seconds=self.settings['pause']) - self.opener = urllib2.build_opener( - urllib2.HTTPCookieProcessor(self.cj)) + self.opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(self.cj)) def get_unread_count(self): delta = datetime.now() - self.last_checked @@ -54,8 +54,8 @@ class ModsDeChecker: self.unread_cache = int(root.attrib['newposts']) except Exception: self.cj.clear() - self.opener = urllib2.build_opener( - urllib2.HTTPCookieProcessor(self.cj)) + self.opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(self.cj)) self.logged_in = False return self.unread_cache @@ -63,7 +63,7 @@ class ModsDeChecker: def login(self): - data = urllib.urlencode({ + data = urllib.parse.urlencode({ "login_username": self.settings["username"], "login_password": self.settings["password"], "login_lifetime": "31536000" diff --git a/statushandler.py b/statushandler.py index ef29007..aba6cd7 100644 --- a/statushandler.py +++ b/statushandler.py @@ -2,7 +2,7 @@ import sys import json -import urllib2 +import urllib.request, urllib.error, urllib.parse class I3statusHandler: modules = [] @@ -66,7 +66,7 @@ class I3statusHandler: def has_internet_connection(): try: - response=urllib2.urlopen('http://74.125.113.99',timeout=1) + response=urllib.request.urlopen('http://74.125.113.99',timeout=1) return True - except urllib2.URLError as err: pass + except urllib.error.URLError as err: pass return False \ No newline at end of file From cb71fca25d8985bfe570ddf37e4548b39e034f8d Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 17:21:32 +0100 Subject: [PATCH 05/20] Also import the tb plugin --- wrapper.py.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/wrapper.py.dist b/wrapper.py.dist index 55388da..c6a3b44 100755 --- a/wrapper.py.dist +++ b/wrapper.py.dist @@ -4,6 +4,7 @@ import mailchecker import modsde import notmuchmailchecker +import thunderbirdnewmail from statushandler import I3statusHandler if __name__ == '__main__': From db94df07a3d8b3c5c02e37bd364a77c2fff2aa51 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 21:01:38 +0100 Subject: [PATCH 06/20] With small changes, comes great change Introduced asynchronous plugins that gather their data on different intervals than the mainloop. Here it is used for the modsde plugin. The statushandler has a new class Module, which acts as documentation for the API These changes let the output evenly flow, even if an async plugin hangs due to network problems or similiar issues. --- modsde.py | 98 ++++++++++++++++++++----------------------- statushandler.py | 31 ++++++++++---- thunderbirdnewmail.py | 24 ++++++++--- 3 files changed, 86 insertions(+), 67 deletions(-) diff --git a/modsde.py b/modsde.py index bb06ae1..1cdaa25 100644 --- a/modsde.py +++ b/modsde.py @@ -2,75 +2,84 @@ import sys import json -from datetime import datetime,timedelta +import time +import threading import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error, urllib.parse import re import http.cookiejar import xml.etree.ElementTree as ET +class LoginError(Exception): + pass + class ModsDeChecker(object): """ This class returns i3status parsable output of the number of unread posts in any bookmark in the mods.de forums. """ - last_checked = datetime.now() - unread_cache = 0 - login_url = 'http://login.mods.de/' + async = True + output = None + + login_url = "http://login.mods.de/" bookmark_url = "http://forum.mods.de/bb/xml/bookmarks.php" opener = None cj = None logged_in = False settings = { - 'color': '#7181fe', - 'pause': 20, - 'username': "", - 'password': "" + "color": "#7181fe", + "pause": 20, + "username": "", + "password": "", + "offset": 0, } def __init__(self, settings = None): self.settings.update(settings) self.cj = http.cookiejar.CookieJar() - self.last_checked = \ - datetime.now() - timedelta(seconds=self.settings['pause']) - self.opener = urllib.request.build_opener( - urllib.request.HTTPCookieProcessor(self.cj)) + self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj)) + + self.thread = threading.Thread(target=self.mainloop) + self.thread.daemon = True + self.thread.start() + + def mainloop(self): + while True: + unread = self.get_unread_count() + + if not unread: + self.output = None + else: + self.output = {"full_text" : "%d new posts in bookmarks" % unread, + "name" : "modsde", + "urgent" : "true", + "color" : self.settings["color"]} + + time.sleep(self.settings["pause"]) def get_unread_count(self): - delta = datetime.now() - self.last_checked + if not self.logged_in: + self.login() - if delta.total_seconds() > self.settings['pause']: - if not self.logged_in: - try: - self.login() - except Exception: - pass - - try: - f = self.opener.open(self.bookmark_url) - root = ET.fromstring(f.read()) - self.last_checked = datetime.now() - self.unread_cache = int(root.attrib['newposts']) - except Exception: - self.cj.clear() - self.opener = urllib.request.build_opener( - urllib.request.HTTPCookieProcessor(self.cj)) - self.logged_in = False - - return self.unread_cache - + try: + f = self.opener.open(self.bookmark_url) + root = ET.fromstring(f.read()) + return int(root.attrib["newposts"]) - self.settings["offset"] + except Exception: + self.cj.clear() + self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj)) + self.logged_in = False def login(self): - data = urllib.parse.urlencode({ "login_username": self.settings["username"], "login_password": self.settings["password"], "login_lifetime": "31536000" }) - response = self.opener.open(self.login_url, data) - m = re.search("http://forum.mods.de/SSO.php[^']*", response.read()) + response = self.opener.open(self.login_url, data.encode("ascii")) + m = re.search("http://forum.mods.de/SSO.php[^']*", response.read().decode("ISO-8859-15")) self.cj.clear() if m and m.group(0): @@ -79,20 +88,5 @@ class ModsDeChecker(object): for cookie in self.cj: self.cj.clear self.logged_in = True - self.opener.addheaders.append(('Cookie', - '{}={}'.format(cookie.name, cookie.value))) + self.opener.addheaders.append(('Cookie', '{}={}'.format(cookie.name, cookie.value))) return True - - return False - - def output(self): - - unread = self.get_unread_count() - - if not unread: - return None - - return {'full_text' : '%d new posts in bookmarks' % unread, - 'name' : 'modsde', - 'urgent' : 'true', - 'color' : self.settings['color']} diff --git a/statushandler.py b/statushandler.py index d97bec3..4b2e53f 100644 --- a/statushandler.py +++ b/statushandler.py @@ -4,6 +4,16 @@ import sys import json import urllib.request, urllib.error, urllib.parse +class Module(object): + output = None + async = False + + def registered(self, status_handler): + """Called when this module is registered with a status handler""" + + def run(self): + """Only called if self.async == False. Called once per tick""" + class I3statusHandler(object): modules = [] @@ -11,24 +21,24 @@ class I3statusHandler(object): pass def register_module(self, module): - """ Register a new module. """ + """Register a new module.""" # check if module implemented the # correct functions - if not hasattr(module, 'output'): - raise Exception("Module %s does not implement \ - all the needed functions!".format(module)) + #if not hasattr(module, 'output'): + # raise Exception("Module %s does not implement \ + # all the needed functions!".format(module)) self.modules.append(module) def print_line(self, message): - """ Non-buffered printing to stdout. """ + """Unbuffered printing to stdout.""" sys.stdout.write(message + '\n') sys.stdout.flush() def read_line(self): - """ Interrupted respecting reader for stdin. """ + """Interrupted respecting reader for stdin.""" # try reading a line, removing any extra whitespace try: @@ -52,13 +62,16 @@ class I3statusHandler(object): if line.startswith(','): line, prefix = line[1:], ',' - j = json.loads(line) + j = [] #json.loads(line) for module in self.modules: - output = module.output() + if not module.async: + module.tick() + + output = module.output if output: - j.insert(0, module.output()) + j.insert(0, output) # and echo back new encoded json self.print_line(prefix+json.dumps(j)) diff --git a/thunderbirdnewmail.py b/thunderbirdnewmail.py index c92ea81..96e5d53 100644 --- a/thunderbirdnewmail.py +++ b/thunderbirdnewmail.py @@ -10,6 +10,7 @@ import dbus, gobject from dbus.mainloop.glib import DBusGMainLoop import json +import threading class ThunderbirdMailChecker(object): """ @@ -17,7 +18,10 @@ class ThunderbirdMailChecker(object): the dbus-sender extension for thunderbird. """ - unread = [] + async = False + output = None + + unread = set() def __init__(self): dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) @@ -32,20 +36,28 @@ class ThunderbirdMailChecker(object): dbus.mainloop.glib.threads_init() self.context = loop.get_context() + def tick(self): + self.context.iteration(False) + def new_msg(self, id, author, subject): if id not in self.unread: - self.unread.append(id) + self.unread.add(id) + self._output() def changed_msg(self, id, event): if event == "read" and id in self.unread: self.unread.remove(id) + self._output() - def output(self): + def _output(self): self.context.iteration(False) unread = len(self.unread) - - return {'full_text' : '%d new email' % unread, + if unread: + self.output = {'full_text' : '%d new email' % unread, 'name' : 'newmail-tb', 'urgent' : True, - 'color' : '#ff0000' } if unread else None \ No newline at end of file + 'color' : '#ff0000' } + else: + self.output = None + From 807eb937fd5bf426b5d322555baf9098690ffd10 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 21:09:53 +0100 Subject: [PATCH 07/20] ... --- statushandler.py | 2 +- thunderbirdnewmail.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/statushandler.py b/statushandler.py index 4b2e53f..326fc12 100644 --- a/statushandler.py +++ b/statushandler.py @@ -11,7 +11,7 @@ class Module(object): def registered(self, status_handler): """Called when this module is registered with a status handler""" - def run(self): + def tick(self): """Only called if self.async == False. Called once per tick""" class I3statusHandler(object): diff --git a/thunderbirdnewmail.py b/thunderbirdnewmail.py index 96e5d53..d6171d3 100644 --- a/thunderbirdnewmail.py +++ b/thunderbirdnewmail.py @@ -11,6 +11,7 @@ import dbus, gobject from dbus.mainloop.glib import DBusGMainLoop import json import threading +import time class ThunderbirdMailChecker(object): """ @@ -18,7 +19,7 @@ class ThunderbirdMailChecker(object): the dbus-sender extension for thunderbird. """ - async = False + async = True output = None unread = set() @@ -36,8 +37,14 @@ class ThunderbirdMailChecker(object): dbus.mainloop.glib.threads_init() self.context = loop.get_context() - def tick(self): - self.context.iteration(False) + self.thread = threading.Thread(target=self.mainloop) + self.thread.daemon = True + self.thread.start() + + def mainloop(self): + while True: + self.context.iteration(False) + time.sleep(1) def new_msg(self, id, author, subject): if id not in self.unread: From 2c97a6749495bbd0fc734593c8991246904cb44a Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 21:24:58 +0100 Subject: [PATCH 08/20] Readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 0f56bb3..a579c53 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,20 @@ Requires * python-dbus * python-gobject2 +Settings + +* format + +### modsde + +Settings + +* username +* password +* pause (delay between updates) +* offset (subtract number of posts before output) +* format + ## Contribute To contribute a script, make sure it has a function `output()` that outputs From 484cfbbf036a4632af9045e937ba7350dcb5897b Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 21:25:14 +0100 Subject: [PATCH 09/20] Format settings --- modsde.py | 10 +++++----- thunderbirdnewmail.py | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/modsde.py b/modsde.py index 1cdaa25..3e3a39b 100644 --- a/modsde.py +++ b/modsde.py @@ -9,9 +9,6 @@ import re import http.cookiejar import xml.etree.ElementTree as ET -class LoginError(Exception): - pass - class ModsDeChecker(object): """ This class returns i3status parsable output of the number of @@ -33,6 +30,7 @@ class ModsDeChecker(object): "username": "", "password": "", "offset": 0, + "format": "%d new posts in bookmarks" } def __init__(self, settings = None): @@ -51,10 +49,12 @@ class ModsDeChecker(object): if not unread: self.output = None else: - self.output = {"full_text" : "%d new posts in bookmarks" % unread, + self.output = { + "full_text" : self.settings["format"] % unread, "name" : "modsde", "urgent" : "true", - "color" : self.settings["color"]} + "color" : self.settings["color"] + } time.sleep(self.settings["pause"]) diff --git a/thunderbirdnewmail.py b/thunderbirdnewmail.py index d6171d3..667df9e 100644 --- a/thunderbirdnewmail.py +++ b/thunderbirdnewmail.py @@ -22,9 +22,15 @@ class ThunderbirdMailChecker(object): async = True output = None + settings = { + "format": "%d new email" + } + unread = set() - def __init__(self): + def __init__(self, settings): + self.settings.update(settings) + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() bus.add_signal_receiver(self.new_msg, @@ -61,10 +67,12 @@ class ThunderbirdMailChecker(object): unread = len(self.unread) if unread: - self.output = {'full_text' : '%d new email' % unread, - 'name' : 'newmail-tb', - 'urgent' : True, - 'color' : '#ff0000' } + self.output = { + "full_text": self.settings["format"] % unread, + "name": "newmail-tb", + "urgent": True, + "color": "#ff0000", + } else: self.output = None From 4c45087a5b0d67e451da43854ea5a1e67e1dd3b1 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 21:25:21 +0100 Subject: [PATCH 10/20] ... --- statushandler.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/statushandler.py b/statushandler.py index 326fc12..bb3e6f8 100644 --- a/statushandler.py +++ b/statushandler.py @@ -62,7 +62,7 @@ class I3statusHandler(object): if line.startswith(','): line, prefix = line[1:], ',' - j = [] #json.loads(line) + j = json.loads(line) for module in self.modules: if not module.async: @@ -75,11 +75,3 @@ class I3statusHandler(object): # and echo back new encoded json self.print_line(prefix+json.dumps(j)) - - -def has_internet_connection(): - try: - response=urllib2.urlopen('http://173.194.69.94',timeout=1) - return True - except urllib2.URLError as err: pass - return False From 54c14658bcae94329e17bb6ed716e778a1dacf52 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 21:37:07 +0100 Subject: [PATCH 11/20] Common code elimination run --- modsde.py | 4 ---- statushandler.py | 13 +++++++------ thunderbirdnewmail.py | 5 ----- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/modsde.py b/modsde.py index 3e3a39b..996a494 100644 --- a/modsde.py +++ b/modsde.py @@ -38,10 +38,6 @@ class ModsDeChecker(object): self.cj = http.cookiejar.CookieJar() self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj)) - self.thread = threading.Thread(target=self.mainloop) - self.thread.daemon = True - self.thread.start() - def mainloop(self): while True: unread = self.get_unread_count() diff --git a/statushandler.py b/statushandler.py index bb3e6f8..bb64b74 100644 --- a/statushandler.py +++ b/statushandler.py @@ -3,6 +3,7 @@ import sys import json import urllib.request, urllib.error, urllib.parse +from threading import Thread class Module(object): output = None @@ -23,12 +24,6 @@ class I3statusHandler(object): def register_module(self, module): """Register a new module.""" - # check if module implemented the - # correct functions - #if not hasattr(module, 'output'): - # raise Exception("Module %s does not implement \ - # all the needed functions!".format(module)) - self.modules.append(module) def print_line(self, message): @@ -55,6 +50,12 @@ class I3statusHandler(object): self.print_line(self.read_line()) self.print_line(self.read_line()) + for module in self.modules: + if module.async: + module.thread = Thread(target=module.mainloop) + module.thread.daemon = True + module.thread.start() + while True: line, prefix = self.read_line(), '' diff --git a/thunderbirdnewmail.py b/thunderbirdnewmail.py index 667df9e..b5873b8 100644 --- a/thunderbirdnewmail.py +++ b/thunderbirdnewmail.py @@ -43,10 +43,6 @@ class ThunderbirdMailChecker(object): dbus.mainloop.glib.threads_init() self.context = loop.get_context() - self.thread = threading.Thread(target=self.mainloop) - self.thread.daemon = True - self.thread.start() - def mainloop(self): while True: self.context.iteration(False) @@ -75,4 +71,3 @@ class ThunderbirdMailChecker(object): } else: self.output = None - From 469f711dcd364b2f8020cc45665c91da9d2218a5 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 11 Feb 2013 21:43:11 +0100 Subject: [PATCH 12/20] .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5b74260..17f0368 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +*__pycache__* *.pyc wrapper.py From 922ae49aba20f19b8dc13abccdefbdd377e0eea4 Mon Sep 17 00:00:00 2001 From: enkore Date: Tue, 12 Feb 2013 01:07:26 +0100 Subject: [PATCH 13/20] Create package i3pystatus --- .gitignore | 3 +- MIT-LICENSE | 1 + statushandler.py => i3pystatus/__init__.py | 12 ++-- i3pystatus/__main__.py.dist | 57 +++++++++++++++++++ mailchecker.py => i3pystatus/mailchecker.py | 4 +- modsde.py => i3pystatus/modsde.py | 4 +- .../notmuchmailchecker.py | 0 .../thunderbird.py | 9 ++- wrapper.py.dist | 57 ------------------- 9 files changed, 79 insertions(+), 68 deletions(-) rename statushandler.py => i3pystatus/__init__.py (86%) create mode 100755 i3pystatus/__main__.py.dist rename mailchecker.py => i3pystatus/mailchecker.py (97%) rename modsde.py => i3pystatus/modsde.py (97%) rename notmuchmailchecker.py => i3pystatus/notmuchmailchecker.py (100%) rename thunderbirdnewmail.py => i3pystatus/thunderbird.py (91%) delete mode 100755 wrapper.py.dist diff --git a/.gitignore b/.gitignore index 17f0368..50a0550 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *__pycache__* *.pyc -wrapper.py +i3pystatus/__main__.py + diff --git a/MIT-LICENSE b/MIT-LICENSE index eb9397a..f11efad 100644 --- a/MIT-LICENSE +++ b/MIT-LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2012 Jan Oliver Oelerich, http://www.oelerich.org +Copyright (c) 2013 mabe, http://enkore.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/statushandler.py b/i3pystatus/__init__.py similarity index 86% rename from statushandler.py rename to i3pystatus/__init__.py index bb64b74..4f8ba44 100644 --- a/statushandler.py +++ b/i3pystatus/__init__.py @@ -5,7 +5,7 @@ import json import urllib.request, urllib.error, urllib.parse from threading import Thread -class Module(object): +class Module: output = None async = False @@ -13,15 +13,18 @@ class Module(object): """Called when this module is registered with a status handler""" def tick(self): - """Only called if self.async == False. Called once per tick""" + """Only called if async is False. Called once per tick""" -class I3statusHandler(object): + def mainloop(self): + """This is run in a separate daemon-thread if async is True""" + +class I3statusHandler: modules = [] def __init__(self): pass - def register_module(self, module): + def register(self, module): """Register a new module.""" self.modules.append(module) @@ -50,6 +53,7 @@ class I3statusHandler(object): self.print_line(self.read_line()) self.print_line(self.read_line()) + # Start threads for asynchronous modules for module in self.modules: if module.async: module.thread = Thread(target=module.mainloop) diff --git a/i3pystatus/__main__.py.dist b/i3pystatus/__main__.py.dist new file mode 100755 index 0000000..5d3448e --- /dev/null +++ b/i3pystatus/__main__.py.dist @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from i3pystatus import ( + I3statusHandler, + mailchecker, + modsde, + notmuchmailchecker, + thunderbird, +) + +status = I3statusHandler() + +# The imap checker module +mailsettings = { + 'color': '#ff0000', + 'servers': [ + { + 'host': 'www.testhost1.com', + 'port': '993', + 'ssl' : True, + 'username': 'your_username', + 'password': 'your_password', + 'pause': 20 + }, + { + 'host': 'www.testhost2.net', + 'port': '993', + 'ssl' : True, + 'username': 'your_username', + 'password': 'your_password', + 'pause': 20 + } + ] +} +mailchecker = mailchecker.MailChecker(mailsettings) +status.register_module(mailchecker) + +# the mods.de forum new bookmarks module +mdesettings = { + 'username': "your_username", + 'password': "your_password" +} +mde = modsde.ModsDeChecker(mdesettings) +status.register_module(mde) + +# the notmuch mail checker module +db_path = 'path_to_your_notmuch_database' +notmuch = notmuchmailchecker.NotmuchMailChecker(db_path) +status.register_module(notmuch) + +# the thunderbird dbus new mail checker module +tb = thunderbirdnewmail.ThunderbirdMailChecker() +status.register_module(tb) + +# start the handler +status.run() diff --git a/mailchecker.py b/i3pystatus/mailchecker.py similarity index 97% rename from mailchecker.py rename to i3pystatus/mailchecker.py index f367336..60421ff 100644 --- a/mailchecker.py +++ b/i3pystatus/mailchecker.py @@ -5,10 +5,10 @@ import sys import json from datetime import datetime,timedelta import imaplib -from statushandler import has_internet_connection +from i3pystatus import Module -class MailChecker(object): +class MailChecker(Module): """ This class handles mailservers and outputs i3status compatible json data for the accumulated unread count. The mail server diff --git a/modsde.py b/i3pystatus/modsde.py similarity index 97% rename from modsde.py rename to i3pystatus/modsde.py index 996a494..d98d10b 100644 --- a/modsde.py +++ b/i3pystatus/modsde.py @@ -9,7 +9,9 @@ import re import http.cookiejar import xml.etree.ElementTree as ET -class ModsDeChecker(object): +from i3pystatus import Module + +class ModsDeChecker(Module): """ This class returns i3status parsable output of the number of unread posts in any bookmark in the mods.de forums. diff --git a/notmuchmailchecker.py b/i3pystatus/notmuchmailchecker.py similarity index 100% rename from notmuchmailchecker.py rename to i3pystatus/notmuchmailchecker.py diff --git a/thunderbirdnewmail.py b/i3pystatus/thunderbird.py similarity index 91% rename from thunderbirdnewmail.py rename to i3pystatus/thunderbird.py index b5873b8..5e754a5 100644 --- a/thunderbirdnewmail.py +++ b/i3pystatus/thunderbird.py @@ -13,7 +13,9 @@ import json import threading import time -class ThunderbirdMailChecker(object): +from i3pystatus import Module + +class ThunderbirdMailChecker(Module): """ This class listens for dbus signals emitted by the dbus-sender extension for thunderbird. @@ -28,8 +30,9 @@ class ThunderbirdMailChecker(object): unread = set() - def __init__(self, settings): - self.settings.update(settings) + def __init__(self, settings=None): + if settings is not None: + self.settings.update(settings) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() diff --git a/wrapper.py.dist b/wrapper.py.dist deleted file mode 100755 index c6a3b44..0000000 --- a/wrapper.py.dist +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import mailchecker -import modsde -import notmuchmailchecker -import thunderbirdnewmail -from statushandler import I3statusHandler - -if __name__ == '__main__': - - status = I3statusHandler() - - # The imap checker module - mailsettings = { - 'color': '#ff0000', - 'servers': [ - { - 'host': 'www.testhost1.com', - 'port': '993', - 'ssl' : True, - 'username': 'your_username', - 'password': 'your_password', - 'pause': 20 - }, - { - 'host': 'www.testhost2.net', - 'port': '993', - 'ssl' : True, - 'username': 'your_username', - 'password': 'your_password', - 'pause': 20 - } - ] - } - mailchecker = mailchecker.MailChecker(mailsettings) - status.register_module(mailchecker) - - # the mods.de forum new bookmarks module - mdesettings = { - 'username': "your_username", - 'password': "your_password" - } - mde = modsde.ModsDeChecker(mdesettings) - status.register_module(mde) - - # the notmuch mail checker module - db_path = 'path_to_your_notmuch_database' - notmuch = notmuchmailchecker.NotmuchMailChecker(db_path) - status.register_module(notmuch) - - # the thunderbird dbus new mail checker module - tb = thunderbirdnewmail.ThunderbirdMailChecker() - status.register_module(tb) - - # start the handler - status.run() From 561e60efee395495c702f0b9954a9739df5cc5ae Mon Sep 17 00:00:00 2001 From: enkore Date: Fri, 15 Feb 2013 18:38:18 +0100 Subject: [PATCH 14/20] Consistent quotes --- i3pystatus/__init__.py | 8 ++++---- i3pystatus/__main__.py.dist | 34 +++++++++++++++++----------------- i3pystatus/modsde.py | 4 +--- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index 4f8ba44..c4ab004 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -32,7 +32,7 @@ class I3statusHandler: def print_line(self, message): """Unbuffered printing to stdout.""" - sys.stdout.write(message + '\n') + sys.stdout.write(message + "\n") sys.stdout.flush() def read_line(self): @@ -61,11 +61,11 @@ class I3statusHandler: module.thread.start() while True: - line, prefix = self.read_line(), '' + line, prefix = self.read_line(), "" # ignore comma at start of lines - if line.startswith(','): - line, prefix = line[1:], ',' + if line.startswith(","): + line, prefix = line[1:], "," j = json.loads(line) diff --git a/i3pystatus/__main__.py.dist b/i3pystatus/__main__.py.dist index 5d3448e..2b4f391 100755 --- a/i3pystatus/__main__.py.dist +++ b/i3pystatus/__main__.py.dist @@ -13,23 +13,23 @@ status = I3statusHandler() # The imap checker module mailsettings = { - 'color': '#ff0000', - 'servers': [ + "color": "#ff0000", + "servers": [ { - 'host': 'www.testhost1.com', - 'port': '993', - 'ssl' : True, - 'username': 'your_username', - 'password': 'your_password', - 'pause': 20 + "host": "www.testhost1.com", + "port": "993", + "ssl" : True, + "username": "your_username", + "password": "your_password", + "pause": 20 }, { - 'host': 'www.testhost2.net', - 'port': '993', - 'ssl' : True, - 'username': 'your_username', - 'password': 'your_password', - 'pause': 20 + "host": "www.testhost2.net", + "port": "993", + "ssl" : True, + "username": "your_username", + "password": "your_password", + "pause": 20 } ] } @@ -38,14 +38,14 @@ status.register_module(mailchecker) # the mods.de forum new bookmarks module mdesettings = { - 'username': "your_username", - 'password': "your_password" + "username": "your_username", + "password": "your_password" } mde = modsde.ModsDeChecker(mdesettings) status.register_module(mde) # the notmuch mail checker module -db_path = 'path_to_your_notmuch_database' +db_path = "path_to_your_notmuch_database" notmuch = notmuchmailchecker.NotmuchMailChecker(db_path) status.register_module(notmuch) diff --git a/i3pystatus/modsde.py b/i3pystatus/modsde.py index d98d10b..72dd8db 100644 --- a/i3pystatus/modsde.py +++ b/i3pystatus/modsde.py @@ -29,8 +29,6 @@ class ModsDeChecker(Module): settings = { "color": "#7181fe", "pause": 20, - "username": "", - "password": "", "offset": 0, "format": "%d new posts in bookmarks" } @@ -86,5 +84,5 @@ class ModsDeChecker(Module): for cookie in self.cj: self.cj.clear self.logged_in = True - self.opener.addheaders.append(('Cookie', '{}={}'.format(cookie.name, cookie.value))) + self.opener.addheaders.append(("Cookie", "{}={}".format(cookie.name, cookie.value))) return True From 8ec1972a3eafee1fde041eab11bb12a15dd1aa5c Mon Sep 17 00:00:00 2001 From: enkore Date: Fri, 15 Feb 2013 18:38:50 +0100 Subject: [PATCH 15/20] Replaced conditionals with polymorphism --- i3pystatus/__init__.py | 27 ++++++++++++++------------- i3pystatus/modsde.py | 7 ++----- i3pystatus/thunderbird.py | 7 ++----- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index c4ab004..c6cccff 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -5,18 +5,26 @@ import json import urllib.request, urllib.error, urllib.parse from threading import Thread -class Module: +class BaseModule: output = None - async = False def registered(self, status_handler): """Called when this module is registered with a status handler""" def tick(self): - """Only called if async is False. Called once per tick""" + """Called once per tick""" + +class Module(BaseModule): + pass + +class AsyncModule(BaseModule): + def registered(self, status_handler): + self.thread = Thread(target=self.mainloop) + self.thread.daemon = True + self.thread.start() def mainloop(self): - """This is run in a separate daemon-thread if async is True""" + """This is run in a separate daemon-thread""" class I3statusHandler: modules = [] @@ -28,6 +36,7 @@ class I3statusHandler: """Register a new module.""" self.modules.append(module) + module.registered(self) def print_line(self, message): """Unbuffered printing to stdout.""" @@ -53,13 +62,6 @@ class I3statusHandler: self.print_line(self.read_line()) self.print_line(self.read_line()) - # Start threads for asynchronous modules - for module in self.modules: - if module.async: - module.thread = Thread(target=module.mainloop) - module.thread.daemon = True - module.thread.start() - while True: line, prefix = self.read_line(), "" @@ -70,8 +72,7 @@ class I3statusHandler: j = json.loads(line) for module in self.modules: - if not module.async: - module.tick() + module.tick() output = module.output diff --git a/i3pystatus/modsde.py b/i3pystatus/modsde.py index 72dd8db..24f303d 100644 --- a/i3pystatus/modsde.py +++ b/i3pystatus/modsde.py @@ -9,17 +9,14 @@ import re import http.cookiejar import xml.etree.ElementTree as ET -from i3pystatus import Module +from i3pystatus import AsyncModule -class ModsDeChecker(Module): +class ModsDeChecker(AsyncModule): """ This class returns i3status parsable output of the number of unread posts in any bookmark in the mods.de forums. """ - async = True - output = None - login_url = "http://login.mods.de/" bookmark_url = "http://forum.mods.de/bb/xml/bookmarks.php" opener = None diff --git a/i3pystatus/thunderbird.py b/i3pystatus/thunderbird.py index 5e754a5..1bee801 100644 --- a/i3pystatus/thunderbird.py +++ b/i3pystatus/thunderbird.py @@ -13,17 +13,14 @@ import json import threading import time -from i3pystatus import Module +from i3pystatus import AsyncModule -class ThunderbirdMailChecker(Module): +class ThunderbirdMailChecker(AsyncModule): """ This class listens for dbus signals emitted by the dbus-sender extension for thunderbird. """ - async = True - output = None - settings = { "format": "%d new email" } From 59ab38d83b9a2928b66dc56595196eb2dadfc41e Mon Sep 17 00:00:00 2001 From: enkore Date: Fri, 15 Feb 2013 18:43:20 +0100 Subject: [PATCH 16/20] Readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a579c53..23d4ffc 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ To install it, follow these steps: cd ~/.config/i3status/ git clone git@github.com:janoliver/i3pystatus contrib - cd contrib - cp wrapper.py.dist wrapper.py + cd contrib/i3pystatus + cp __main__.py.dist __main__.py Add the following to `~/.config/i3status/config`: @@ -24,12 +24,12 @@ Change your i3wm config to the following: # i3bar bar { - status_command i3status | python ~/.config/i3status/contrib/wrapper.py + status_command cd ~/.config/i3status/contrib ; i3status | python -m i3pystatus position top workspace_buttons yes } -And finally adjust the settings in `~/.config/i3status/contrib/wrapper.py` +And finally adjust the settings in `~/.config/i3status/contrib/i3pystatus/__main__.py` as you like. ## Modules From 2c7b0fcef92efccb53a7c37b9ff08b20c26c70b1 Mon Sep 17 00:00:00 2001 From: enkore Date: Fri, 15 Feb 2013 21:06:52 +0100 Subject: [PATCH 17/20] Added yet another module type, fixed notmuch module (Yeah, I'm really lazy today) --- i3pystatus/__init__.py | 12 ++++++++++++ i3pystatus/{notmuchmailchecker.py => notmuch.py} | 6 ++++-- 2 files changed, 16 insertions(+), 2 deletions(-) rename i3pystatus/{notmuchmailchecker.py => notmuch.py} (90%) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index c6cccff..ffeee6f 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -4,6 +4,7 @@ import sys import json import urllib.request, urllib.error, urllib.parse from threading import Thread +import time class BaseModule: output = None @@ -26,6 +27,17 @@ class AsyncModule(BaseModule): def mainloop(self): """This is run in a separate daemon-thread""" +class IntervalModule(AsyncModule): + interval = 5 # seconds + + def run(self): + """Called every self.interval seconds""" + + def mainloop(self): + while True: + self.run() + time.sleep(self.interval) + class I3statusHandler: modules = [] diff --git a/i3pystatus/notmuchmailchecker.py b/i3pystatus/notmuch.py similarity index 90% rename from i3pystatus/notmuchmailchecker.py rename to i3pystatus/notmuch.py index 51e59b0..2633bd9 100644 --- a/i3pystatus/notmuchmailchecker.py +++ b/i3pystatus/notmuch.py @@ -6,7 +6,9 @@ import notmuch import json -class NotmuchMailChecker(object): +from i3pystatus import IntervalModule + +class NotmuchMailChecker(IntervalModule): """ This class uses the notmuch python bindings to check for the number of messages in the notmuch database with the tags "inbox" @@ -18,7 +20,7 @@ class NotmuchMailChecker(object): def __init__(self, db_path): self.db_path = db_path - def output(self): + def run(self): db = notmuch.Database(self.db_path) unread = notmuch.Query(db, 'tag:unread and tag:inbox').count_messages() From c93503704bcc1b80753b644ee2098a28a7d3a96f Mon Sep 17 00:00:00 2001 From: enkore Date: Fri, 15 Feb 2013 21:08:29 +0100 Subject: [PATCH 18/20] Changed modsde --- i3pystatus/modsde.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/i3pystatus/modsde.py b/i3pystatus/modsde.py index 24f303d..5df718c 100644 --- a/i3pystatus/modsde.py +++ b/i3pystatus/modsde.py @@ -9,9 +9,9 @@ import re import http.cookiejar import xml.etree.ElementTree as ET -from i3pystatus import AsyncModule +from i3pystatus import IntervalModule -class ModsDeChecker(AsyncModule): +class ModsDeChecker(IntervalModule): """ This class returns i3status parsable output of the number of unread posts in any bookmark in the mods.de forums. @@ -25,7 +25,6 @@ class ModsDeChecker(AsyncModule): settings = { "color": "#7181fe", - "pause": 20, "offset": 0, "format": "%d new posts in bookmarks" } @@ -35,21 +34,18 @@ class ModsDeChecker(AsyncModule): self.cj = http.cookiejar.CookieJar() self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj)) - def mainloop(self): - while True: - unread = self.get_unread_count() + def run(self): + unread = self.get_unread_count() - if not unread: - self.output = None - else: - self.output = { - "full_text" : self.settings["format"] % unread, - "name" : "modsde", - "urgent" : "true", - "color" : self.settings["color"] - } - - time.sleep(self.settings["pause"]) + if not unread: + self.output = None + else: + self.output = { + "full_text" : self.settings["format"] % unread, + "name" : "modsde", + "urgent" : "true", + "color" : self.settings["color"] + } def get_unread_count(self): if not self.logged_in: From c4e80b3e408b1ef2dacd8c7f2be01938c623a1c1 Mon Sep 17 00:00:00 2001 From: enkore Date: Fri, 15 Feb 2013 21:09:54 +0100 Subject: [PATCH 19/20] notmuch^2 --- i3pystatus/notmuch.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/i3pystatus/notmuch.py b/i3pystatus/notmuch.py index 2633bd9..99ff26d 100644 --- a/i3pystatus/notmuch.py +++ b/i3pystatus/notmuch.py @@ -15,23 +15,25 @@ class NotmuchMailChecker(IntervalModule): and "unread" """ - db_path = '' + db_path = "" def __init__(self, db_path): self.db_path = db_path def run(self): db = notmuch.Database(self.db_path) - unread = notmuch.Query(db, 'tag:unread and tag:inbox').count_messages() + unread = notmuch.Query(db, "tag:unread and tag:inbox").count_messages() if (unread == 0): - color = '#00FF00' - urgent = 'false' + color = "#00FF00" + urgent = "false" else: - color = '#ff0000' - urgent = 'true' + color = "#ff0000" + urgent = "true" - return {'full_text' : '%d new email%s' % (unread, ('s' if unread > 1 else '')), - 'name' : 'newmail', - 'urgent' : urgent, - 'color' : color } + self.output = { + "full_text" : "%d new email%s" % (unread, ("s" if unread > 1 else "")), + "name" : "newmail", + "urgent" : urgent, + "color" : color + } From 7e2c61c21a7522a11ba9519a4e273046aaf46c5e Mon Sep 17 00:00:00 2001 From: enkore Date: Fri, 15 Feb 2013 21:14:39 +0100 Subject: [PATCH 20/20] mailchecker ; didn't really test it yet --- i3pystatus/mailchecker.py | 84 ++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/i3pystatus/mailchecker.py b/i3pystatus/mailchecker.py index 60421ff..20a31e4 100644 --- a/i3pystatus/mailchecker.py +++ b/i3pystatus/mailchecker.py @@ -6,9 +6,9 @@ import json from datetime import datetime,timedelta import imaplib -from i3pystatus import Module +from i3pystatus import IntervalModule -class MailChecker(Module): +class MailChecker(IntervalModule): """ This class handles mailservers and outputs i3status compatible json data for the accumulated unread count. The mail server @@ -16,8 +16,8 @@ class MailChecker(Module): """ settings = { - 'color': '#ff0000', - 'servers': [] + "color": "#ff0000", + "servers": [] } servers = [] @@ -25,29 +25,29 @@ class MailChecker(Module): def __init__(self, settings = None): self.settings.update(settings) - for server in settings['servers']: + for server in settings["servers"]: srv = MailChecker.MailServer(server) self.servers.append(srv) - def output(self): - unread = 0 - for srv in self.servers: - unread += srv.get_unread_count() + def run(self): + unread = sum([server.get_unread_count() for server in self.servers]) if not unread: return None - return {'full_text' : '%d new email%s' % (unread, ('s' if unread > 1 else '')), - 'name' : 'newmail', - 'urgent' : 'true', - 'color' : self.settings['color']} + self.output = { + "full_text" : "%d new email%s" % (unread, ("s" if unread > 1 else "")), + "name" : "newmail", + "urgent" : "true", + "color" : self.settings["color"] + } class MailServer: """ This class provides the functionality to connect to a mail server and fetch the count of unread emails. When the server connection is lost, it returns 0 and - tries to reconnect. It checks every 'pause' seconds. + tries to reconnect. It checks every "pause" seconds. """ host = "" @@ -56,52 +56,36 @@ class MailChecker(Module): username = "" password = "" connection = None - pause = 30 - unread_cache = 0 - last_checked = datetime.now() def __init__(self, settings_dict): - self.host = settings_dict['host'] - self.port = settings_dict['port'] - self.username = settings_dict['username'] - self.password = settings_dict['password'] - self.pause = settings_dict['pause'] + self.host = settings_dict["host"] + self.port = settings_dict["port"] + self.username = settings_dict["username"] + self.password = settings_dict["password"] - if settings_dict['ssl']: + if settings_dict["ssl"]: self.imap_class = imaplib.IMAP4_SSL - self.last_checked = \ - datetime.now() - timedelta(seconds=self.pause) - def get_connection(self): - if not has_internet_connection(): - self.connection = None - else: - if not self.connection: - try: - self.connection = self.imap_class(self.host, self.port) - self.connection.login(self.username, self.password) - self.connection.select() - except Exception: - self.connection = None - + if not self.connection: try: + self.connection = self.imap_class(self.host, self.port) + self.connection.login(self.username, self.password) self.connection.select() - except Exception as e: + except Exception: self.connection = None + try: + self.connection.select() + except Exception as e: + self.connection = None + return self.connection def get_unread_count(self): - delta = datetime.now() - self.last_checked - - if delta.total_seconds() > self.pause: - unread = 0 - conn = self.get_connection() - if conn: - unread += len(conn.search(None,'UnSeen')[1][0].split()) - - self.unread_cache = unread - self.last_checked = datetime.now() - - return self.unread_cache + unread = 0 + conn = self.get_connection() + if conn: + unread += len(conn.search(None,"UnSeen")[1][0].split()) + + return unread