summaryrefslogtreecommitdiff
path: root/jimbrella
diff options
context:
space:
mode:
authorFrederick Yin <fkfd@fkfd.me>2021-10-27 09:31:04 +0800
committerFrederick Yin <fkfd@fkfd.me>2021-10-27 10:10:06 +0800
commit09bd022f5a713c24ab376f0d0901aba436dc151a (patch)
tree07aea89c37f1dd0927da9c56df36a7bb4e97932f /jimbrella
parentcbf419cb79f4a0d06c65167f81f10f503fff391d (diff)
Separate Lockfile into new class
Diffstat (limited to 'jimbrella')
-rw-r--r--jimbrella/admin_log.py41
-rw-r--r--jimbrella/database.py35
-rw-r--r--jimbrella/lockfile.py33
3 files changed, 55 insertions, 54 deletions
diff --git a/jimbrella/admin_log.py b/jimbrella/admin_log.py
index 811d722..4cc9b16 100644
--- a/jimbrella/admin_log.py
+++ b/jimbrella/admin_log.py
@@ -1,6 +1,7 @@
import csv
import os
from datetime import datetime
+from .lockfile import Lockfile
class AdminLog:
@@ -33,25 +34,7 @@ class AdminLog:
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
+ self.lockfile = Lockfile(self.path)
def _read(self) -> list:
"""Deserialize admin log."""
@@ -102,7 +85,7 @@ class AdminLog:
`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():
+ while not self.lockfile.lock():
continue
with open(self.path, "a") as f: # append only
@@ -112,9 +95,21 @@ class AdminLog:
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"]]
+ 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"]]
+ info = [
+ entry["admin_name"],
+ entry["serial"],
+ entry["column"],
+ entry["past_value"],
+ entry["new_value"],
+ ]
line.extend(info)
line.append(entry["note"])
@@ -122,4 +117,4 @@ class AdminLog:
f.close()
- self._unlock()
+ self.lockfile.unlock()
diff --git a/jimbrella/database.py b/jimbrella/database.py
index 37f6c82..fd8f35c 100644
--- a/jimbrella/database.py
+++ b/jimbrella/database.py
@@ -1,6 +1,7 @@
import csv
import os
from datetime import datetime, timedelta
+from .lockfile import Lockfile
from .utils import human_datetime, human_timedelta
from .config import DUE_HOURS
from .exceptions import *
@@ -43,33 +44,7 @@ class Database:
| jForm answer sheet. is blank otherwise.
"""
self.path = path
-
- def _lock(self) -> int:
- """Create a lockfile for database.
-
- If the database is called "db.csv", the lockfile, located in the same
- directory, would be called "db.csv.lock".
-
- Returns 1 if the lockfile is successfully created, 0 if the lockfile
- already exists (strong hint of unrecovered crash or potential race
- condition - caller of this method should abort whatever it is doing)
- """
- 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
+ self.lockfile = Lockfile(self.path)
def _read(self) -> list:
"""Deserialize database."""
@@ -106,9 +81,7 @@ class Database:
backup = f.read()
f.close()
- # wait until database is locked for this write
- while not self._lock():
- continue
+ self.lockfile.lock()
f = open(self.path, "w")
try:
@@ -140,7 +113,7 @@ class Database:
f.close()
raise e
- self._unlock()
+ self.lockfile.unlock()
def _update(self, update: dict) -> list:
"""Update status of one umbrella, and return the entire updated database."""
diff --git a/jimbrella/lockfile.py b/jimbrella/lockfile.py
new file mode 100644
index 0000000..b898142
--- /dev/null
+++ b/jimbrella/lockfile.py
@@ -0,0 +1,33 @@
+import os
+
+
+class Lockfile:
+ """Prevent unwanted concurrency for file I/O.
+
+ For a file named "<file>", create or remove a lockfile named "<file>.lock".
+
+ When a process is modifying the file, call `lock(). When the modification
+ is done, call `unlock()`.
+ """
+
+ def __init__(self, filepath):
+ self.filepath = filepath
+ self.lockpath = filepath + ".lock"
+
+ def lock(self):
+ """Continue attempting to lock until locked."""
+ locked = False
+ while not locked:
+ try:
+ f = open(self.lockpath, "x")
+ f.close()
+ locked = True
+ except FileExistsError:
+ continue
+
+ def unlock(self):
+ """Remove lockfile."""
+ try:
+ os.remove(self.lockpath)
+ except FileNotFoundError:
+ return