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.
This commit is contained in:
enkore 2013-02-11 21:01:38 +01:00
parent 80d7184e14
commit db94df07a3
3 changed files with 86 additions and 67 deletions

View File

@ -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']}

View File

@ -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))

View File

@ -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
'color' : '#ff0000' }
else:
self.output = None