aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas "Cakeisalie5" Touhey <thomas@touhey.fr>2018-09-14 17:46:51 +0200
committerThomas "Cakeisalie5" Touhey <thomas@touhey.fr>2018-09-14 17:46:51 +0200
commit52f3716dcfaa8f6da6e3c32083674b09ba6facb1 (patch)
tree0a314e25f092816262c8c3b995ee6695ff6b8256
parent46d407b6e30d305bfcd13d54938ad8d173dd7ac5 (diff)
Removed deltas and enhanced the fiction interface for interactive actions
-rw-r--r--README.rst2
-rwxr-xr-xfingerd/__init__.py4
-rwxr-xr-xfingerd/_fiction.py296
-rwxr-xr-xfingerd/_util.py110
4 files changed, 196 insertions, 216 deletions
diff --git a/README.rst b/README.rst
index 6218ce4..f7240f1 100644
--- a/README.rst
+++ b/README.rst
@@ -138,7 +138,7 @@ What is left to do
For v0.2:
-- correct the user listing formatting (idle and login time).
+- add a user update action.
- correct the idle simulation function with a better one.
- correct error-handling in configuration files decoding.
- add support for plans.
diff --git a/fingerd/__init__.py b/fingerd/__init__.py
index da21799..48c21f5 100755
--- a/fingerd/__init__.py
+++ b/fingerd/__init__.py
@@ -13,13 +13,13 @@
"""
from ._util import ConfigurationError, BindError, \
- FingerUser, FingerUserDelta, FingerFormatter, \
+ FingerUser, FingerSession, FingerFormatter, \
FingerInterface, FingerLogger
from ._fiction import FingerFiction, FingerFictionInterface
from ._server import FingerServer
__all__ = ["version",
- "FingerUser", "FingerUserDelta", "FingerFormatter",
+ "FingerUser", "FingerSession", "FingerFormatter",
"FingerInterface", "FingerLogger", "FingerServer", "FingerFiction",
"FingerNativeInterface", "FingerFictionInterface",
"ConfigurationError", "BindError"]
diff --git a/fingerd/_fiction.py b/fingerd/_fiction.py
index 7e44a39..aa9a2c2 100755
--- a/fingerd/_fiction.py
+++ b/fingerd/_fiction.py
@@ -11,8 +11,7 @@ import itertools as _itertools
from datetime import datetime as _dt, timedelta as _td
from ._util import FingerInterface as _FingerInterface, \
- FingerUser as _FingerUser, FingerUserDelta as _FingerUserDelta, \
- FingerSession as _FingerSession, FingerSessionDelta as _FingerSessionDelta
+ FingerUser as _FingerUser, FingerSession as _FingerSession
__all__ = ["FingerFictionInterface", "FingerFiction"]
@@ -43,16 +42,6 @@ class _FictionalSession(_FingerSession):
if getattr(self, x) is not None)
return f"{self.__class__.__name__}({', '.join(p)})"
- def __add__(self, delta):
- if isinstance(delta, _FictionalSessionDelta):
- if delta.idle_since is not None:
- self.idle_since = delta.idle_since
- if delta.active_since is not None:
- self.active_since = delta.active_since
-
- super().__add__(delta)
- return self
-
@property
def name(self):
""" Session name (for identifying). """
@@ -110,21 +99,6 @@ class _FictionalSession(_FingerSession):
self.__is_idle = False
self.__idle_last = value
-class _FictionalSessionDelta(_FingerSessionDelta):
- """ User delta for the fingerd fictional user. """
-
- def __init__(self, *_, idle_since = None, active_since = None, \
- idle = None, **kwargs):
- super().__init__(**kwargs)
- self.idle_since = idle_since
- self.active_since = active_since
-
- def __repr__(self):
- p = ('name', 'line', 'host', 'idle_since', 'active_since')
- p = (f"{x} = {repr(getattr(self, x))}" for x in p \
- if getattr(self, x) is not None)
- return f"{self.__class__.__name__}({', '.join(p)})"
-
# ---
# Parse a delta string.
# ---
@@ -166,43 +140,38 @@ def _parse_delta(raw):
class _Action:
""" Base class for actions in a fiction. """
- def __init__(self, time):
- self.__time = time
-
- @property
- def time(self):
- """ The time offset to the actions. """
-
- return self.__time
+ pass
class _UserCreationAction(_Action):
""" A user has been created. """
- def __init__(self, offset, user):
- super().__init__(offset)
+ def __init__(self, user):
+ super().__init__()
self.__user = user
def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'user'))
+ p = (f"{x} = {repr(getattr(self, x))}" for x in ('user'))
return f"{self.__class__.__name__}({', '.join(p)})"
@property
def user(self):
""" The user. """
- return self.__user
+ return _copy.deepcopy(self.__user)
class _UserEditionAction(_Action):
""" A user status has been changed. """
- def __init__(self, offset, login, delta):
- super().__init__(offset)
+ def __init__(self, login, name, home, shell):
+ super().__init__()
self.__login = login
- self.__delta = delta
+ self.__name = name
+ self.__home = home
+ self.__shell = shell
def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login',
- 'delta'))
+ p = (f"{x} = {repr(getattr(self, x))}" for x in ('login',
+ 'name', 'home', 'shell'))
return f"{self.__class__.__name__}({', '.join(p)})"
@property
@@ -212,22 +181,33 @@ class _UserEditionAction(_Action):
return self.__login
@property
- def delta(self):
- """ The properties to be modified. """
+ def name(self):
+ """ The user's name. """
- return self.__delta
+ return self.__name
+
+ @property
+ def home(self):
+ """ The user's home. """
+
+ return self.__home
+
+ @property
+ def shell(self):
+ """ The user's shell. """
+
+ return self.__shell
class _UserLoginAction(_Action):
""" A user has logged in. """
- def __init__(self, offset, login, session):
- super().__init__(offset)
+ def __init__(self, login, session):
+ super().__init__()
self.__login = login
self.__session = session
def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login',
- 'session'))
+ p = (f"{x} = {repr(getattr(self, x))}" for x in ('login', 'session'))
return f"{self.__class__.__name__}({', '.join(p)})"
@property
@@ -245,14 +225,13 @@ class _UserLoginAction(_Action):
class _UserLogoutAction(_Action):
""" A user has logged out. """
- def __init__(self, offset, login, name):
- super().__init__(offset)
+ def __init__(self, login, name):
+ super().__init__()
self.__login = login
self.__name = name
def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login',
- 'name'))
+ p = (f"{x} = {repr(getattr(self, x))}" for x in ('login', 'name'))
return f"{self.__class__.__name__}({', '.join(p)})"
@property
@@ -270,15 +249,15 @@ class _UserLogoutAction(_Action):
class _UserIdleToggleAction(_Action):
""" A user has changed its idle status. """
- def __init__(self, offset, login, name, idle):
- super().__init__(offset)
+ def __init__(self, login, name, idle):
+ super().__init__()
self.__login = login
self.__name = name
self.__idle = bool(idle)
def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login',
- 'name', 'idle'))
+ p = (f"{x} = {repr(getattr(self, x))}" for x in ('login', 'name', \
+ 'idle'))
return f"{self.__class__.__name__}({', '.join(p)})"
@property
@@ -299,47 +278,15 @@ class _UserIdleToggleAction(_Action):
return self.__idle
-class _UserSessionEditionAction(_Action):
- """ A user has changed their session status. """
-
- def __init__(self, offset, login, name, delta):
- super().__init__(offset)
- self.__login = login
- self.__name = name
- self.__delta = delta
-
- def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login',
- 'name', 'delta'))
- return f"{self.__class__.__name__}({', '.join(p)})"
-
- @property
- def login(self):
- """ The user's login. """
-
- return self.__login
-
- @property
- def name(self):
- """ The name of the session to create. """
-
- return self.__name
-
- @property
- def delta(self):
- """ The session delta. """
-
- return self.__delta
-
class _UserDeletionAction(_Action):
""" A user has been deleted. """
- def __init__(self, offset, login):
- super().__init__(offset)
+ def __init__(self, login):
+ super().__init__()
self.__login = login
def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login'))
+ p = (f"{x} = {repr(getattr(self, x))}" for x in ('login'))
return f"{self.__class__.__name__}({', '.join(p)})"
@property
@@ -348,10 +295,6 @@ class _UserDeletionAction(_Action):
return self.__login
- def __repr__(self):
- p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login'))
- return f"{self.__class__.__name__}({', '.join(p)})"
-
# ---
# Finger interface to actions.
# ---
@@ -412,6 +355,7 @@ class FingerFiction:
if end_time is None or end_time > time:
end_type = typ
end_time = time
+ continue
elif typ == 'create':
# User creation.
@@ -424,13 +368,13 @@ class FingerFiction:
if 'office' in data:
user.office = data['office']
- actions.append(_UserCreationAction(time, user))
+ action = _UserCreationAction(user)
elif typ == 'delete':
# User deletion.
login = data['login']
- actions.append(_UserDeletionAction(time, login))
+ action = _UserDeletionAction(login)
elif typ == 'login':
# User login.
@@ -441,44 +385,47 @@ class FingerFiction:
session.line = data['line']
session.host = data.get('host')
- actions.append(_UserLoginAction(time, login, session))
+ action = _UserLoginAction(login, session)
elif typ == 'logout':
# User logout.
login = data['login']
name = data.get('session')
- actions.append(_UserLogoutAction(time, login, name))
+ action = _UserLogoutAction(login, name)
elif typ in ('idle', 'active'):
# Idle change status.
login = data['login']
+ session = data.get('session')
+ is_idle = (typ == 'idle')
- actions.append(_UserIdleToggleAction(time, login,
- data.get('session'), typ == 'idle'))
+ action = _UserIdleToggleAction(login, session, is_idle)
else:
raise Exception("invalid action type")
+ actions.append((time, action))
+
# Sort and check the actions.
- actions.sort(key = lambda x: x.time)
+ actions.sort(key = lambda x: x[0])
if end_type is None:
# If no ending was given in the script file, we ought to
# interrupt 10 seconds after the last action.
end_type = 'interrupt'
- end_time = actions[-1].time + _td(seconds = 10)
+ end_time = actions[-1][0] + _td(seconds = 10)
else:
# Otherwise, we are removing actions that are after the ending.
- actions = [a for a in actions if a.time <= end_time]
+ actions = [a for a in actions if a[0] <= end_time]
# FIXME: check that incompatible actions, such as double creation
# for a user, doesn't occur.
self.__type = end_type
- self.__start = actions[0].time if actions else _td()
+ self.__start = actions[0][0] if actions else _td()
self.__duration = end_time
self.__actions = actions
@@ -489,9 +436,13 @@ class FingerFiction:
to = self.__duration
if since is None:
since = self.__start - _td(seconds = 1)
+ if since > to:
+ raise ValueError(f"`since` ({since}) should be " \
+ f"before `to` ({to}).")
- return [action for action in self.__actions \
- if since < action.time <= to]
+ for time, action in self.__actions:
+ if since < time <= to:
+ yield time, action
@property
def type(self):
@@ -532,6 +483,95 @@ class FingerFictionInterface(_FingerInterface):
self.update()
return [_copy.deepcopy(u) for u in self.__users.values() if check(u)]
+ def apply(self, action, time = _dt.now()):
+ """ Apply an action. """
+
+ if isinstance(action, _UserCreationAction):
+ # Create user `action.user`.
+
+ user = _copy.deepcopy(action.user)
+ if user.login is None:
+ raise ValueError("missing login")
+ if user.login in self.__users:
+ raise ValueError("already got a user with that login")
+
+ self.__users[user.login] = user
+ elif isinstance(action, _UserEditionAction):
+ # Edit user `action.user` with delta `action.delta`.
+
+ if action.login is None:
+ raise ValueError("missing login")
+ if not action.login in self.__users:
+ raise ValueError(f"got no user with login {repr(action.login)}")
+
+ self.__users[action.login] += action.delta
+ elif isinstance(action, _UserDeletionAction):
+ # Delete user with login `action.login`.
+
+ if action.login is None:
+ raise ValueError("missing login")
+ if not action.login in self.__users:
+ raise ValueError(f"got no user with login {repr(action.login)}")
+
+ del self.__users[action.login]
+ elif isinstance(action, _UserLoginAction):
+ # Login as user `action.login` with session `action.session`.
+
+ session = _copy.deepcopy(action.session)
+ session.start = self.__start + time
+
+ if action.login is None:
+ raise ValueError("missing login")
+
+ try:
+ user = self.__users[action.login]
+ except KeyError:
+ raise ValueError(f"got no user with login {repr(action.login)}") \
+ from None
+
+ user.sessions.add(session)
+ user.last_login = session.start
+ elif isinstance(action, _UserLogoutAction):
+ # Logout as user `action.login` from session `action.session`.
+
+ if action.login is None:
+ raise ValueError("missing login")
+
+ try:
+ user = self.__users[action.login]
+ except KeyError:
+ raise ValueError(f"got no user with login {repr(action.login)}") \
+ from None
+
+ try:
+ del user.sessions[action.name]
+ except (KeyError, IndexError):
+ raise ValueError(f"got no session {repr(action.name)} " \
+ f"for user {repr(action.login)}") from None
+ elif isinstance(action, _UserIdleToggleAction):
+ # Make user with login `action.login` idle.
+
+ if action.login is None:
+ raise ValueError("missing login")
+
+ try:
+ user = self.__users[action.login]
+ except KeyError:
+ raise ValueError(f"got no user with login {repr(action.login)}") \
+ from None
+
+ try:
+ session = user.sessions[action.name]
+ except (KeyError, IndexError):
+ raise ValueError(f"got no session {repr(action.name)} " \
+ f"for user {repr(action.login)}") from None
+
+ since = self.__start + time
+ if action.idle:
+ session.idle_since = since
+ else:
+ session.active_since = since
+
def __get_time(self):
""" Get the current timedelta using the origin and duration. """
@@ -555,46 +595,20 @@ class FingerFictionInterface(_FingerInterface):
""" Update the state according to the script. """
time = self.__get_time()
- actions = []
# Check if we need to reset (no users left and apply the actions
# from before the start).
if self.__current is None or self.__current > time:
self.__users = {}
- self.__current = _td(0)
+ self.__current = None
- actions += self.__fiction.get(self.__current)
+ # Then apply the actions up to the current time.
- # Then get the actions up to the current time.
+ for time, action in self.__fiction.get(to = time,
+ since = self.__current):
+ self.apply(action, time)
- actions += self.__fiction.get(time, self.__current)
self.__current = time
- # And apply the actions.
-
- for a in actions:
- if isinstance(a, _UserCreationAction):
- self.__users[a.user.login] = _copy.deepcopy(a.user)
- elif isinstance(a, _UserEditionAction):
- self.__users[a.login] += a.delta
- elif isinstance(a, _UserDeletionAction):
- del self.__users[a.login]
- elif isinstance(a, _UserLoginAction):
- session = _copy.deepcopy(a.session)
- self.__users[a.login].sessions.add(session)
-
- last_login = self.__start + a.time
- self.__users[a.login].last_login = last_login
- elif isinstance(a, _UserLogoutAction):
- del self.__users[a.login].sessions[a.name]
- elif isinstance(a, _UserIdleToggleAction):
- session = self.__users[a.login].sessions[a.name]
- if a.idle:
- session.idle_since = self.__start + a.time
- else:
- session.active_since = self.__start + a.time
- elif isinstance(a, _UserSessionEditionAction):
- self.__users[a.login].sessions[a.name] += a.delta
-
# End of file.
diff --git a/fingerd/_util.py b/fingerd/_util.py
index a185e68..7841d7b 100755
--- a/fingerd/_util.py
+++ b/fingerd/_util.py
@@ -8,7 +8,7 @@
"""
__all__ = ["ConfigurationError", "BindError",
- "FingerUser", "FingerUserDelta", "FingerSession", "FingerSessionDelta",
+ "FingerUser", "FingerSession",
"FingerFormatter", "FingerInterface", "FingerLogger"]
import sys as _sys, copy as _copy
@@ -59,19 +59,6 @@ class FingerUser:
if getattr(self, x) is not None)
return f"{self.__class__.__name__}({', '.join(p)})"
- def __add__(self, delta):
- if not isinstance(delta, FingerUserDelta):
- raise NotImplemented
-
- if delta.name != None:
- self.name = delta.name
- if delta.home != None:
- self.home = delta.home
- if delta.shell != None:
- self.shell = delta.shell
-
- return self
-
@property
def login(self):
""" Login name, e.g. 'cake'. """
@@ -139,20 +126,6 @@ class FingerUser:
return self.__sessions
-class FingerUserDelta:
- """ Differences between two states for a user. """
-
- def __init__(self, *_, name = None, home = None, shell = None):
- self.name = name
- self.home = home
- self.shell = shell
-
- def __repr__(self):
- p = ('name', 'home', 'shell', 'office')
- p = (f"{x} = {repr(getattr(self, x))}" for x in p \
- if getattr(self, x) is not None)
- return f"{self.__class__.__name__}({', '.join(p)})"
-
class _FingerSessionManager:
""" Session manager. """
@@ -166,7 +139,7 @@ class _FingerSessionManager:
return bool(self.__sessions)
def __iter__(self):
- return iter(self.__sessions)
+ return iter(self.__sessions[::-1])
def __delitem__(self, key):
if key is None:
@@ -283,18 +256,6 @@ class FingerSession:
if getattr(self, x) is not None)
return f"{self.__class__.__name__}({', '.join(p)})"
- def __add__(self, value):
- if isinstance(value, FingerSessionDelta):
- if value.line is not None:
- self.line = value.line
- if value.host is not None:
- self.host = value.host
- if value.idle is not None:
- self.idle = value.idle
- return self
-
- raise NotImplementedError
-
@property
def start(self):
""" The session start time. """
@@ -303,7 +264,7 @@ class FingerSession:
@start.setter
def start(self, value):
- self.__start = _dt(value)
+ self.__start = value if isinstance(value, _dt) else _dt(value)
@property
def line(self):
@@ -325,30 +286,16 @@ class FingerSession:
def host(self, value):
self.__host = None if value is None else str(value)
-class FingerSessionDelta:
- """ Session delta. """
-
- def __init__(self, *_, line = None, host = None, idle = None):
- self.line = line
- self.host = host
- self.idle = idle
-
- def __repr__(self):
- p = ('line', 'host', 'idle')
- p = (f"{x} = {repr(getattr(self, x))}" for x in p \
- if getattr(self, x) is not None)
- return f"{self.__class__.__name__}({', '.join(p)})"
-
# ---
# Formatter base class.
# ---
def _format_idle(idle):
def _iter_idle(idle):
- days = int( idle.days)
- hours = int( idle.seconds / 3600)
- mins = int((idle.seconds % 3600) / 60)
- secs = int( idle.seconds % 60)
+ days = int(idle.days)
+ hours = int(idle.seconds / 3600)
+ mins = int(idle.seconds % 3600 / 60)
+ secs = int(idle.seconds % 60)
if days:
yield f"{days} day{('', 's')[days > 1]}"
@@ -361,6 +308,24 @@ def _format_idle(idle):
return f"{' '.join(_iter_idle(idle))} idle"
+def _format_time(d):
+ if d < _td():
+ return ""
+
+ days = int(d.days)
+ hours = int(d.seconds / 3600)
+ mins = int(d.seconds % 3600 / 60)
+
+ if days:
+ return f"{days}d"
+ elif hours or mins:
+ return f"{hours:02}:{mins:02}"
+
+ return ""
+
+def _format_when(d):
+ return d.strftime("%a %H:%M")
+
class FingerFormatter:
""" Answer formatter.
Formats the answer following RFC 1288. """
@@ -440,25 +405,26 @@ class FingerFormatter:
_login = lambda u, s: u.login
_name = lambda u, s: u.name
_line = lambda u, s: s.line if s and s.line else ''
- _idle = lambda u, s: "=TODO="
- _logt = lambda u, s: "=TODO="
+ _idle = lambda u, s: _format_time(now - s.idle) if s else ''
+ _logt = lambda u, s: _format_when(s.start) if s else ''
_offic = lambda u, s: u.office if u.office else ''
columns = (
- ('Login',) + tuple(_login(u, s) for u, s in lst),
- ('Name',) + tuple( _name(u, s) for u, s in lst),
- ('TTY',) + tuple( _line(u, s) for u, s in lst),
- ('Idle',) + tuple( _idle(u, s) for u, s in lst),
- ('Login Time',) + tuple( _logt(u, s) for u, s in lst),
- ('Office',) + tuple(_offic(u, s) for u, s in lst))
+ ('Login',) + tuple(_login(u, s) for u, s in lst),
+ ('Name',) + tuple( _name(u, s) for u, s in lst),
+ ('TTY',) + tuple( _line(u, s) for u, s in lst),
+ ('Idle',) + tuple( _idle(u, s) for u, s in lst),
+ ('When',) + tuple( _logt(u, s) for u, s in lst),
+ ('Office',) + tuple(_offic(u, s) for u, s in lst))
- sizes = tuple(max(map(len, c)) + 1 \
- for i, c in enumerate(columns))
+ sizes = tuple(max(map(len, c)) + 1 for i, c in enumerate(columns))
+ align = ('<', '<', '<', '^', '^', '<')
lines = []
for line in range(len(columns[0])):
- lines.append(' '.join('{:<{}}'.format(columns[i][line][:sizes[i]],
- sizes[i]) for i in range(len(columns))))
+ lines.append(' '.join(\
+ f"{columns[i][line][:sizes[i]]:{align[i]}{sizes[i]}}" \
+ for i in range(len(columns))))
return '\r\n'.join(lines) + '\r\n'