dotfiles/config/lemonbar/updaters.py
2018-09-05 09:07:37 +02:00

195 lines
5.4 KiB
Python
Executable file

#!/usr/bin/env python3
import math
import functools
import threading
import pyinotify
import os
import time
# TODO Multiple providers for the same section
class Updater:
@staticmethod
def init():
PeriodicUpdater.init()
InotifyUpdater.init()
def updateText(self, text):
print(text)
def fetcher(self):
return "{} refreshed".format(self)
def refreshData(self):
data = self.fetcher()
self.updateText(data)
class PeriodicUpdaterThread(threading.Thread):
def run(self):
counter = 0
while True:
if PeriodicUpdater.intervalsChanged \
.wait(timeout=PeriodicUpdater.intervalStep):
# ↑ sleeps here
PeriodicUpdater.intervalsChanged.clear()
counter = 0
for providerList in PeriodicUpdater.intervals.copy().values():
for provider in providerList.copy():
provider.refreshData()
else:
counter += PeriodicUpdater.intervalStep
counter = counter % PeriodicUpdater.intervalLoop
for interval in PeriodicUpdater.intervals.keys():
if counter % interval == 0:
for provider in PeriodicUpdater.intervals[interval]:
provider.refreshData()
class PeriodicUpdater(Updater):
"""
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
"""
intervals = dict()
intervalStep = None
intervalLoop = None
updateThread = PeriodicUpdaterThread(daemon=True)
intervalsChanged = threading.Event()
@staticmethod
def gcds(*args):
return functools.reduce(math.gcd, args)
@staticmethod
def lcm(a, b):
"""Return lowest common multiple."""
return a * b // math.gcd(a, b)
@staticmethod
def lcms(*args):
"""Return lowest common multiple."""
return functools.reduce(PeriodicUpdater.lcm, args)
@staticmethod
def updateIntervals():
intervalsList = list(PeriodicUpdater.intervals.keys())
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
PeriodicUpdater.intervalsChanged.set()
@staticmethod
def init():
PeriodicUpdater.updateThread.start()
def __init__(self):
Updater.__init__(self)
self.interval = None
def changeInterval(self, interval):
assert isinstance(interval, int)
if self.interval is not None:
PeriodicUpdater.intervals[self.interval].remove(self)
self.interval = interval
if interval not in PeriodicUpdater.intervals:
PeriodicUpdater.intervals[interval] = set()
PeriodicUpdater.intervals[interval].add(self)
PeriodicUpdater.updateIntervals()
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
def process_default(self, event):
# DEBUG
# from pprint import pprint
# pprint(event.__dict__)
# return
assert event.path in InotifyUpdater.paths
if 0 in InotifyUpdater.paths[event.path]:
for provider in InotifyUpdater.paths[event.path][0]:
provider.refreshData()
if event.name in InotifyUpdater.paths[event.path]:
for provider in InotifyUpdater.paths[event.path][event.name]:
provider.refreshData()
class InotifyUpdater(Updater):
"""
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
"""
wm = pyinotify.WatchManager()
paths = dict()
@staticmethod
def init():
notifier = pyinotify.ThreadedNotifier(InotifyUpdater.wm,
InotifyUpdaterEventHandler())
notifier.start()
# TODO Mask for folders
MASK = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
def addPath(self, path, refresh=True):
path = os.path.realpath(os.path.expanduser(path))
# Detect if file or folder
if os.path.isdir(path):
self.dirpath = path
# 0: Directory watcher
self.filename = 0
elif os.path.isfile(path):
self.dirpath = os.path.dirname(path)
self.filename = os.path.basename(path)
else:
raise FileNotFoundError("No such file or directory: '{}'"
.format(path))
# Register watch action
if self.dirpath not in InotifyUpdater.paths:
InotifyUpdater.paths[self.dirpath] = dict()
if self.filename not in InotifyUpdater.paths[self.dirpath]:
InotifyUpdater.paths[self.dirpath][self.filename] = set()
InotifyUpdater.paths[self.dirpath][self.filename].add(self)
# Add watch
InotifyUpdater.wm.add_watch(self.dirpath, InotifyUpdater.MASK)
if refresh:
self.refreshData()
class ThreadedUpdaterThread(threading.Thread):
def __init__(self, updater, *args, **kwargs):
self.updater = updater
threading.Thread.__init__(self, *args, **kwargs)
def run(self):
while True:
self.updater.loop()
class ThreadedUpdater(Updater):
"""
Must implement loop(), and call start()
"""
def __init__(self):
self.thread = ThreadedUpdaterThread(self, daemon=True)
def loop(self):
self.refreshData()
time.sleep(10)
def start(self):
self.thread.start()