summaryrefslogtreecommitdiff
path: root/jimbrella
diff options
context:
space:
mode:
authorFrederick Yin <fkfd@fkfd.me>2021-10-24 15:56:30 +0800
committerFrederick Yin <fkfd@fkfd.me>2021-10-24 15:56:30 +0800
commitdd3f59fc9ab7218670cc6d4d25bcb471a708d4ea (patch)
treeb4f1cbde08bc843c3e66c4fd038251e62111aa73 /jimbrella
parent5a118df3f90bd5ab3f39c12487ea1f578aa380ae (diff)
Implement basic I/O of AdminLog
Diffstat (limited to 'jimbrella')
-rw-r--r--jimbrella/admin_log.py125
-rw-r--r--jimbrella/routine.py1
2 files changed, 126 insertions, 0 deletions
diff --git a/jimbrella/admin_log.py b/jimbrella/admin_log.py
new file mode 100644
index 0000000..811d722
--- /dev/null
+++ b/jimbrella/admin_log.py
@@ -0,0 +1,125 @@
+import csv
+import os
+from datetime import datetime
+
+
+class AdminLog:
+ """Logs JImbrella-specific events into a file.
+
+ The file is intended to be read, deserialized, and represented in a user-friendly format to an
+ admin on the web console. The file format is csv, but the number and meanings of columns may
+ not be uniform for all rows.
+
+ For each row, there are a minimum of three columns. The first column is called the "event".
+ It describes the log entry by and large. What other columns in the row represent depends on
+ the event. The second column is always the date and time, in ISO8601 format. The last column
+ is called the "note", an optional textual note in natural language.
+
+ Here we list possible events, what information will follow them, and the scenario for each one.
+ Words surrounded by angle brackets (< >) are defined in class Database, and those in square
+ brackets ([ ]) are defined here in AdminLog.
+
+ TAKEAWAY,[date],<serial>,<tenant_name>,<tenant_id>,<tenant_phone>,<tenant_email>,[note]
+ A user borrows an umbrella normally.
+ GIVEBACK,[date],<serial>,<tenant_name>,<tenant_id>,<tenant_phone>,<tenant_email>,[note]
+ A user returns an umbrella normally.
+ OVERDUE,[date],<serial>,<tenant_name>,<tenant_id>,<tenant_phone>,<tenant_email>,[note]
+ An umbrella is judged overdue by JImbrella's routine process.
+ ADMIN_MODIFY_DB,[date],[admin_name],<serial>,[column],[past_value],[new_value],[note]
+ An admin makes modifications to one cell of the database via the web console or API.
+ If multiple cells are modified, the same number of log entries are written, although
+ they can be multiplexed in one HTTP request.
+ """
+
+ def __init__(self, path: str):
+ self.path = path
+
+ def _lock(self) -> int:
+ """Create a lockfile for admin log. Operates in the sameway as `Database._lock`."""
+ try:
+ f = open(self.path + ".lock", "x")
+ f.close()
+ return 1
+ except FileExistsError:
+ return 0
+
+ def _unlock(self) -> None:
+ """Remove lockfile created by `_lock`.
+
+ If there is no lockfile, simply ignore.
+ """
+ try:
+ os.remove(self.path + ".lock")
+ except FileNotFoundError:
+ pass
+
+ def _read(self) -> list:
+ """Deserialize admin log."""
+ # Create log if it does not yet exist
+ try:
+ f = open(self.path, "x")
+ f.close()
+ except FileExistsError:
+ pass
+
+ with open(self.path) as f:
+ reader = csv.reader(f)
+ logs = []
+ for row in reader:
+ event = row[0]
+ entry = {
+ "event": event,
+ "date": datetime.fromisoformat(row[1]),
+ "note": row[-1],
+ }
+ info = {}
+ if event in ("TAKEAWAY", "GIVEBACK", "OVERDUE"):
+ info = {
+ "serial": int(row[2]),
+ "tenant_name": row[3],
+ "tenant_id": row[4],
+ "tenant_phone": row[5],
+ "tenant_email": row[6],
+ }
+ elif event == "ADMIN_MODIFY_DB":
+ info = {
+ "admin_name": row[2],
+ "serial": int(row[3]),
+ "column": row[4],
+ "past_value": row[5],
+ "new_value": row[6],
+ }
+
+ entry.update(info)
+ logs.append(entry)
+
+ f.close()
+ return logs
+
+ def _write(self, logs: list) -> None:
+ """Serialize logs.
+
+ `logs` is a list of log entries, to be appended at the end of the log file.
+ """
+ # wait until database is locked for this write
+ while not self._lock():
+ continue
+
+ with open(self.path, "a") as f: # append only
+ writer = csv.writer(f)
+ for entry in logs:
+ event = entry["event"]
+ line = [event, entry["date"].isoformat()]
+ info = []
+ if event in ("TAKEAWAY", "GIVEBACK", "OVERDUE"):
+ info = [entry["serial"], entry["tenant_name"], entry["tenant_id"], entry["tenant_phone"], entry["tenant_email"]]
+ elif event == "ADMIN_MODIFY_DB":
+ info = [entry["admin_name"], entry["serial"], entry["column"], entry["past_value"], entry["new_value"]]
+
+ line.extend(info)
+ line.append(entry["note"])
+ writer.writerow(line)
+
+ f.close()
+
+ self._unlock()
diff --git a/jimbrella/routine.py b/jimbrella/routine.py
index 6a88d41..4d87c4c 100644
--- a/jimbrella/routine.py
+++ b/jimbrella/routine.py
@@ -1,6 +1,7 @@
import logging
from .database import Database
from .jform import JForm
+from .admin_log import AdminLog
from .config import *
from .utils import local_now
from .exceptions import *