diff options
author | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2018-09-13 22:14:53 +0200 |
---|---|---|
committer | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2018-09-13 22:14:53 +0200 |
commit | 6b818669142af0efb9be50491ab2e760620548c4 (patch) | |
tree | d601965d09a5fbe23013120ccd30665c4df9dd6c | |
parent | ba547f714fe8d1a7c4f494ab101aceb4832e45fd (diff) |
Corrected a few things about sessions.
-rw-r--r-- | README.rst | 23 | ||||
-rwxr-xr-x | fingerd/__main__.py | 9 | ||||
-rwxr-xr-x | fingerd/_fiction.py | 72 | ||||
-rwxr-xr-x | fingerd/_util.py | 246 |
4 files changed, 246 insertions, 104 deletions
@@ -136,24 +136,17 @@ shell changes: What is left to do ------------------ -- separate users and sessions, so that there can be several sessions - displayed as in these examples: +For v0.2: - .. code-block:: +- correct the user listing formatting (idle and login time). +- correct the docs with the new user/session distinction. +- correct the idle simulation function with a better one. +- add support for plans. +- add tests. - Login Name Tty Idle Login Time Office Office Phone - cake Thomas Touhey pts/0 Sep 12 22:51 (XXXX::XXXX) - cake Thomas Touhey pts/1 Sep 12 22:51 (XXXX::XXXX) +For further versions: - .. code-block:: - - Login: cake Name: Thomas Touhey - Directory: /home/cake Shell: /bin/zsh - On since Wed Sep 12 22:51 (CEST) on pts/0 from XXXX::XXXX - 8 seconds idle - On since Wed Sep 12 22:51 (CEST) on pts/1 from XXXX::XXXX - 4 seconds idle - No Plan. +- add a POSIX native interface. .. _RFC 742: https://tools.ietf.org/html/rfc742 .. _RFC 1288: https://tools.ietf.org/html/rfc1288 diff --git a/fingerd/__main__.py b/fingerd/__main__.py index 571841a..beb874d 100755 --- a/fingerd/__main__.py +++ b/fingerd/__main__.py @@ -45,7 +45,14 @@ def get_server(config_path): exit(1) iface = _FingerNativeInterface() elif iface == 'actions': - fic = _FingerFiction(c['actions']['ending'], c['actions']['end']) + try: + end_type = c['actions']['ending'] + end_time = c['actions']['end'] + except KeyError: + end_type = None + end_time = None + + fic = _FingerFiction(end_type, end_time) fic.load(c['actions']['path']) iface = _FingerFictionInterface(fic) diff --git a/fingerd/_fiction.py b/fingerd/_fiction.py index f52ba8e..83c3817 100755 --- a/fingerd/_fiction.py +++ b/fingerd/_fiction.py @@ -19,21 +19,26 @@ __all__ = ["FingerFictionInterface", "FingerFiction"] _toml = None # --- -# User for the generated. +# Users and sessions for the fictions. # --- +class _FictionalUser(_FingerUser): + """ User for the fingerd fiction """ + + pass + class _FictionalSession(_FingerSession): """ Session for the fingerd fictional user. """ - def __init__(self, *_, name = None, is_idle = False, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, name = None, is_idle = False, **kwargs): + super().__init__(*args, **kwargs) self.__name = None if name is None else str(name) self.__is_idle = is_idle self.__idle_last = self.start def __repr__(self): - p = ('name', 'start', 'line', 'office', 'wd', 'shell', 'is_idle') + p = ('name', 'start', 'line', 'host', 'is_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)})" @@ -66,7 +71,7 @@ class _FictionalSession(_FingerSession): # Generate a number of seconds and return it. x = (_dt.now() - self.__idle_last).seconds - s = lambda x: math.sin(x * (pi / 2)) + s = lambda x: _math.sin(x * (_math.pi / 2)) randsecs = int(abs(s(x) + s(x / 4))) return _dt.now() - _td(seconds = randsecs) @@ -100,7 +105,7 @@ class _FictionalSessionDelta(_FingerSessionDelta): self.is_idle = is_idle def __repr__(self): - p = ('name', 'wd', 'line', 'logged_in', 'is_idle') + p = ('name', 'line', 'host', 'is_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)})" @@ -110,7 +115,7 @@ class _FictionalSessionDelta(_FingerSessionDelta): # --- __delta0_re = _re.compile(r"(-?)(([0-9]+[a-z]+)+)") -__delta1_re = _re.compile(r"([0-9])+([a-z]+)") +__delta1_re = _re.compile(r"([0-9]+)([a-z]+)") def _parse_delta(raw): try: @@ -162,16 +167,16 @@ class _UserCreationAction(_Action): super().__init__(offset) self.__user = user + def __repr__(self): + p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'user')) + return f"{self.__class__.__name__}({', '.join(p)})" + @property def user(self): """ The user. """ return self.__user - def __repr__(self): - p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'user')) - return f"{self.__class__.__name__}({', '.join(p)})" - class _UserEditionAction(_Action): """ A user status has been changed. """ @@ -180,6 +185,11 @@ class _UserEditionAction(_Action): self.__login = login self.__delta = delta + def __repr__(self): + p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login', + 'delta')) + return f"{self.__class__.__name__}({', '.join(p)})" + @property def login(self): """ The user's login. """ @@ -192,11 +202,6 @@ class _UserEditionAction(_Action): return self.__delta - def __repr__(self): - p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login', - 'delta')) - return f"{self.__class__.__name__}({', '.join(p)})" - class _UserLoginAction(_Action): """ A user has logged in. """ @@ -205,6 +210,11 @@ class _UserLoginAction(_Action): self.__login = login self.__session = session + def __repr__(self): + p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login', + 'session')) + return f"{self.__class__.__name__}({', '.join(p)})" + @property def login(self): """ The user's login. """ @@ -225,6 +235,11 @@ class _UserLogoutAction(_Action): self.__login = login self.__name = name + def __repr__(self): + p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login', + 'name')) + return f"{self.__class__.__name__}({', '.join(p)})" + @property def login(self): """ The user's login. """ @@ -246,6 +261,11 @@ class _UserSessionEditionAction(_Action): 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. """ @@ -271,6 +291,10 @@ class _UserDeletionAction(_Action): super().__init__(offset) self.__login = login + def __repr__(self): + p = (f"{x} = {repr(getattr(self, x))}" for x in ('time', 'login')) + return f"{self.__class__.__name__}({', '.join(p)})" + @property def login(self): """ The user's login. """ @@ -345,12 +369,14 @@ class FingerFiction: elif typ == 'create': # User creation. - user = _FingerUser(login = data['login'], + user = _FictionalUser(login = data['login'], name = data.get('name')) if 'shell' in data: user.shell = data['shell'] if 'home' in data: user.home = data['home'] + if 'office' in data: + user.office = data['office'] actions.append(_UserCreationAction(time, user)) elif typ == 'delete': @@ -366,8 +392,8 @@ class FingerFiction: session = _FictionalSession() session.name = data.get('session') - session.office = data['office'] session.line = data['line'] + session.host = data.get('host') actions.append(_UserLoginAction(time, login, session)) elif typ == 'logout': @@ -390,7 +416,7 @@ class FingerFiction: delta = _FictionalSessionDelta(is_idle = False) actions.append(_UserSessionEditionAction(time, login, - delta)) + data.get('session'), delta)) else: raise Exception("invalid action type") @@ -423,10 +449,10 @@ class FingerFiction: if to is None: to = self.__duration if since is None: - since = self.__start + since = self.__start - _td(seconds = 1) return [action for action in self.__actions \ - if since <= action.time <= to] + if since < action.time <= to] @property def type(self): @@ -522,9 +548,7 @@ class FingerFictionInterface(_FingerInterface): del self.__users[a.login] elif isinstance(a, _UserLoginAction): session = _copy.deepcopy(a.session) - user = self.__users[a.login] - session += user - user.sessions.append(session) + self.__users[a.login].sessions.add(session) elif isinstance(a, _UserLogoutAction): del self.__users[a.login].sessions[a.name] elif isinstance(a, _UserSessionEditionAction): diff --git a/fingerd/_util.py b/fingerd/_util.py index a2f151e..30d3fad 100755 --- a/fingerd/_util.py +++ b/fingerd/_util.py @@ -11,8 +11,9 @@ __all__ = ["ConfigurationError", "BindError", "FingerUser", "FingerUserDelta", "FingerSession", "FingerSessionDelta", "FingerFormatter", "FingerInterface", "FingerLogger"] -import sys as _sys, multiprocessing as _multip -from datetime import datetime as _dt +import sys as _sys, copy as _copy +import multiprocessing as _multip +from datetime import datetime as _dt, timedelta as _td # --- # Exceptions. @@ -43,8 +44,9 @@ class FingerUser: self.__name = '' self.__home = None self.__shell = None + self.__office = None self.__last_login = None - self.__sessions = [] + self.__sessions = _FingerSessionManager() self.login = login self.name = name @@ -121,6 +123,16 @@ class FingerUser: self.__shell = None if value is None else str(value) @property + def office(self): + """ The user's office. """ + + return self.__office + + @office.setter + def office(self, value): + self.__office = None if value is None else str(value) + + @property def sessions(self): """ Current sessions. """ @@ -135,48 +147,146 @@ class FingerUserDelta: self.shell = shell def __repr__(self): - p = ('name', 'home', 'shell') + 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. """ + + def __init__(self): + self.__sessions = [] + + def __repr__(self): + return repr(self.__sessions) + + def __bool__(self): + return bool(self.__sessions) + + def __iter__(self): + return iter(self.__sessions) + + def __delitem__(self, key): + if key is None: + self.__sessions.pop(0) + else: + for i in [i for i, x in enumerate(l) if key == x.name][::-1]: + self.__sessions.pop(i) + + def __getitem__(self, key): + if key is None: + try: + return self.__sessions[0] + except IndexError: + raise KeyError("could not get latest session") from None + + if type(key) is int: + try: + return self.__sessions[key] + except IndexError: + msg = f"could not get session #{repr(key)}" + raise IndexError(msg) from None + + try: + return next(x for x in self.__sessions if key == x.name) + except StopIteration: + raise KeyError(f"could not get session {repr(key)}") from None + + def __setitem__(self, key, value): + if not isinstance(value, FingerSession): + raise TypeError("can only add sessions into a session manager") + value = _copy.deepcopy(value) + + if key is None: + # Check if except the first session, the key of the session, + # if any, does not override another key. + + if value.name is not None: + try: + next(i for i, x in self.__sessions[1:] \ + if value.name == x.name) + except StopIteration: + pass + else: + msg = "value.name overrides another session's key" + raise ValueError(msg) from None + + try: + self.__sessions[0] = value + return + except IndexError: + raise KeyError("could not set latest session") from None + + if type(key) is int: + # Check if except the key-th session, the key of the session, + # if any, does not override another key. + + if value.name is not None: + try: + next(i for i, x in self.__sessions \ + if i != key and value.name == x.name) + except StopIteration: + pass + else: + msg = "value.name overrides another session's key" + raise ValueError(msg) from None + + try: + self.__sessions[key] = value + return + except IndexError: + msg = f"could not set session #{repr(key)}" + raise IndexError(msg) from None + + value.name = key + + try: + i = next(i for i, x in enumerate(self.__sessions) \ + if key == x.name) + except StopIteration: + raise KeyError(f"could not set session {repr(key)}") from None + + self.__sessions[i] = value + + def add(self, session): + """ Add a session. """ + + if not isinstance(session, FingerSession): + raise TypeError("can only insert sessions into a session manager") + if session.name is not None: + try: + next(i for i, x in self.__sessions \ + if session.name == x.name) + except StopIteration: + pass + else: + msg = "session.name overrides another session's key" + raise ValueError(msg) from None + + self.__sessions.insert(0, session) + class FingerSession: """ Session for a user. """ - def __init__(self, *_, time = _dt.now(), user = None): - user = FingerUser(user) - + def __init__(self, *_, time = _dt.now()): self.__start = time if isinstance(time, _dt) else _dt(time) self.__line = None - self.__office = None - self.__wd = None - self.__shell = None + self.__host = None self.__idle = self.__start - if user is not None: - self += user - def __repr__(self): - p = ('start', 'line', 'office', 'wd', 'shell', 'idle') + p = ('start', 'line', 'orig', '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)})" def __add__(self, value): - if isinstance(value, FingerUser): - self.wd = value.home - self.shell = value.shell - return self - if isinstance(value, FingerSessionDelta): - if value.office is not None: - self.office = value.office if value.line is not None: self.line = value.line - if value.wd is not None: - self.wd = value.wd - if value.shell is not None: - self.shell = value.shell + if value.host is not None: + self.host = value.host if value.idle is not None: self.idle = value.idle return self @@ -204,48 +314,25 @@ class FingerSession: self.__line = None if value is None else str(value) @property - def office(self): - """ The office's name. """ + def host(self): + """ The host from which the user is connected. """ - return self.__office + return self.__host - @office.setter - def office(self, value): - self.__office = None if value is None else str(value) - - @property - def wd(self): - """ The current (working) directory. """ - - return self.__wd - - @wd.setter - def wd(self, value): - self.__wd = None if value is None else str(value) - - @property - def shell(self): - """ The current shell. """ - - return self.__shell - - @shell.setter - def shell(self, value): - self.__shell = None if value is None else str(value) + @host.setter + def host(self, value): + self.__host = None if value is None else str(value) class FingerSessionDelta: """ Session delta. """ - def __init__(self, *_, office = None, line = None, wd = None, - shell = None, idle = None): - self.office = office + def __init__(self, *_, line = None, host = None, idle = None): self.line = line - self.wd = wd - self.shell = shell + self.host = host self.idle = idle def __repr__(self): - p = ('wd', 'shell', 'idle') + 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)})" @@ -254,6 +341,24 @@ class FingerSessionDelta: # 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) + + if days: + yield f"{days} day{('', 's')[days > 1]}" + if hours: + yield f"{hours} hour{('', 's')[hours > 1]}" + if mins: + yield f"{mins} minute{('', 's')[mins > 1]}" + if secs: + yield f"{secs} second{('', 's')[secs > 1]}" + + return f"{' '.join(_iter_idle(idle))} idle" + class FingerFormatter: """ Answer formatter. Formats the answer following RFC 1288. """ @@ -281,8 +386,23 @@ class FingerFormatter: def format_user(self, user, verbose = False): """ Formats user information. """ - return 'Login name: {:<27} In real life: {}\r\n'.format(user.login, - user.name) + res = f"Login name: {user.login[:27]:<27} Name: {user.name}\r\n" + res += f"Directory: {user.home[:28]:<28} Shell: {user.shell}\r\n" + res += f"Office: {user.office if user.office else ''}\r\n" + + for se in user.sessions: + since = se.start.strftime("%a %b %e %R") + tz = str(_dt.utcnow().astimezone().tzinfo) + res += f"On since {since} ({tz}) on {se.line}" + if se.host is not None: + res += f" from {se.host}" + res += "\r\n" + + idle = _dt.now() - se.idle + if idle >= _td(seconds = 4): + res += f" {_format_idle(idle)}\r\n" + + return res def format_user_sep(self): """ Formats the user separator. """ @@ -309,7 +429,7 @@ class FingerFormatter: _line = lambda u, s: s.line if s and s.line else '' _idle = lambda u, s: "=TODO=" _logt = lambda u, s: "=TODO=" - _offic = lambda u, s: s.office if s and s.office else '' + _offic = lambda u, s: u.office if u.office else '' columns = ( ('Login',) + tuple(_login(u, s) for u, s in lst), @@ -318,14 +438,12 @@ class FingerFormatter: ('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)) - columns_min = (8, 14, 6, 5, 12, 10) - columns_max = (8, 14, 6, 5, 12, 10) - sizes = tuple(max(max(map(len, c)), columns_min[i]) + 1 \ + sizes = tuple(max(map(len, c)) + 1 \ for i, c in enumerate(columns)) lines = [] - for line in range(1 + len(users)): + for line in range(len(columns[0])): lines.append(' '.join('{:<{}}'.format(columns[i][line][:sizes[i]], sizes[i]) for i in range(len(columns)))) |