diff options
author | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2018-09-14 17:46:51 +0200 |
---|---|---|
committer | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2018-09-14 17:46:51 +0200 |
commit | 52f3716dcfaa8f6da6e3c32083674b09ba6facb1 (patch) | |
tree | 0a314e25f092816262c8c3b995ee6695ff6b8256 | |
parent | 46d407b6e30d305bfcd13d54938ad8d173dd7ac5 (diff) |
Removed deltas and enhanced the fiction interface for interactive actions
-rw-r--r-- | README.rst | 2 | ||||
-rwxr-xr-x | fingerd/__init__.py | 4 | ||||
-rwxr-xr-x | fingerd/_fiction.py | 296 | ||||
-rwxr-xr-x | fingerd/_util.py | 110 |
4 files changed, 196 insertions, 216 deletions
@@ -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' |