aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas "Cakeisalie5" Touhey <thomas@touhey.fr>2018-09-13 22:14:53 +0200
committerThomas "Cakeisalie5" Touhey <thomas@touhey.fr>2018-09-13 22:14:53 +0200
commit6b818669142af0efb9be50491ab2e760620548c4 (patch)
treed601965d09a5fbe23013120ccd30665c4df9dd6c
parentba547f714fe8d1a7c4f494ab101aceb4832e45fd (diff)
Corrected a few things about sessions.
-rw-r--r--README.rst23
-rwxr-xr-xfingerd/__main__.py9
-rwxr-xr-xfingerd/_fiction.py72
-rwxr-xr-xfingerd/_util.py246
4 files changed, 246 insertions, 104 deletions
diff --git a/README.rst b/README.rst
index 7a92ce3..b669d4b 100644
--- a/README.rst
+++ b/README.rst
@@ -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))))