diff options
author | Frederick Yin <fkfd@fkfd.me> | 2021-10-24 15:56:30 +0800 |
---|---|---|
committer | Frederick Yin <fkfd@fkfd.me> | 2021-10-24 15:56:30 +0800 |
commit | dd3f59fc9ab7218670cc6d4d25bcb471a708d4ea (patch) | |
tree | b4f1cbde08bc843c3e66c4fd038251e62111aa73 /jimbrella | |
parent | 5a118df3f90bd5ab3f39c12487ea1f578aa380ae (diff) |
Implement basic I/O of AdminLog
Diffstat (limited to 'jimbrella')
-rw-r--r-- | jimbrella/admin_log.py | 125 | ||||
-rw-r--r-- | jimbrella/routine.py | 1 |
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 * |