Skip to content

Commit

Permalink
React immediately when a user moves to a new terminal
Browse files Browse the repository at this point in the history
Use database notifications to close a user's session immediately when
they move to a different terminal.
  • Loading branch information
sde1000 committed Feb 2, 2024
1 parent 6760c43 commit 693ceb9
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
25 changes: 25 additions & 0 deletions quicktill/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,31 @@ def logtext(self):
return self.fullname


# When the 'transid' or 'register' column of a user is changed, send
# notification 'user_register' with the user ID as payload. This
# enables registers to detect the user arriving at a different
# register, or the user's transaction being taken over by another
# user.
add_ddl(User.__table__, """
CREATE OR REPLACE FUNCTION notify_user_change() RETURNS trigger AS $$
DECLARE
BEGIN
IF NEW.transid IS DISTINCT FROM OLD.transid
OR NEW.register IS DISTINCT FROM OLD.register THEN
PERFORM pg_notify('user_register', CAST(NEW.id AS text));
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER user_changed
AFTER UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE notify_user_change();
""", """
DROP TRIGGER user_changed ON users;
DROP FUNCTION notify_user_change();
""")


class UserToken(Base, Logged):
"""A token used by a till user to identify themselves
Expand Down
29 changes: 28 additions & 1 deletion quicktill/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from . import user
from . import plugins
from . import config
from . import listen
import logging
import datetime
from .models import Transline, Transaction, Session, StockOut, penny
Expand Down Expand Up @@ -823,6 +824,8 @@ def __init__(self, user, hotkeys, autolock=None, timeout=300):
self.user.dbuser.message = None
td.s.flush()
self._redraw()
self._notify_user_register_listener = listen.listener.listen_for(
"user_register", self._notify_user_register)
self.hook("new_page")
self._resume_pending_payment()

Expand Down Expand Up @@ -3178,6 +3181,7 @@ def entry(self):
ui.infopopup([self.user.dbuser.message],
title="Transaction information")
self._clear()
self._redraw()
self.user.dbuser.message = None
td.s.flush()
return False
Expand All @@ -3188,6 +3192,7 @@ def entry(self):
ui.infopopup(["Your transaction has gone away."],
title="Transaction gone")
self._clear()
self._redraw()
td.s.flush()
return False

Expand Down Expand Up @@ -3217,11 +3222,30 @@ def entry_noninteractive(self):
if self.transid and not self.user.dbuser.transaction:
# Transaction has been taken by another user
return False
if self.transid != self.user.dbuser.transaction.id:
if self.transid and self.transid != self.user.dbuser.transaction.id:
# This _should_ never happen but would be very bad if it did!
return False
return True

def _notify_user_register(self, userid_string):
# This is called whenever a user_register notification is sent
# by the database, which will happen whenever a user's transid
# or register column is changed. If the userid_string
# corresponds to our user, run entry_noninteractive() to check
# that the user hasn't moved to another register and that
# their transaction hasn't been taken over by someone else.
if not self.selected:
return
log.debug(f"{self=} got user_register for {userid_string}")
try:
userid = int(userid_string)
except Exception:
return
if userid == self.user.userid:
with td.orm_session():
if not self.entry_noninteractive():
self.deselect()

def hook(self, action, *args, **kwargs):
"""Check with plugins before performing an action
Expand Down Expand Up @@ -3352,6 +3376,9 @@ def deselect(self):
if self._timeout_handle:
self._timeout_handle.cancel()
self._timeout_handle = None
if self._notify_user_register_listener:
self._notify_user_register_listener.cancel()
self._notify_user_register_listener = None

def alarm(self):
# The timeout has passed. If the scrollable has the input
Expand Down
8 changes: 7 additions & 1 deletion quicktill/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,14 @@ def __init__(self):
def pagename(self):
return "Basic page"

@property
def selected(self):
"""Is this the current page?
"""
return self._basepage == self

def select(self):
if basicpage._basepage == self:
if self.selected:
return # Nothing to do
# Tell the current page we're switching away
if basicpage._basepage:
Expand Down
22 changes: 22 additions & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ What's new:
* The Twitter integration has been removed. A stub remains so that
till configurations that refer to it will still load.

* When a user moves to another terminal their session on the terminal
they left closes immediately, rather than waiting for a timeout or
a keypress.

To upgrade the database:

- run "runtill syncdb" to create the new stocktype metadata table
Expand All @@ -29,6 +33,24 @@ ALTER TABLE users
ALTER TABLE stocklines
ADD COLUMN note character varying DEFAULT ''::character varying NOT NULL;
CREATE OR REPLACE FUNCTION notify_user_change() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
IF NEW.transid IS DISTINCT FROM OLD.transid
OR NEW.register IS DISTINCT FROM OLD.register THEN
PERFORM pg_notify('user_register', CAST(NEW.id AS text));
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER user_changed
AFTER UPDATE ON users
FOR EACH ROW
EXECUTE PROCEDURE public.notify_user_change();
COMMIT;
```

Expand Down

0 comments on commit 693ceb9

Please sign in to comment.