|
| 1 | +import os |
1 | 2 | import pickle |
| 3 | +import tempfile |
2 | 4 | from datetime import datetime, timezone |
3 | 5 |
|
4 | 6 |
|
5 | 7 | # store temporary values. unreadable and uneditable. |
6 | 8 | class AppState: |
7 | 9 | interval = 20 # [s] |
8 | 10 |
|
9 | | - last_write_time = datetime.now(timezone.utc) |
10 | 11 | pickle_file = "state.pickle" |
11 | | - values = None |
12 | 12 |
|
13 | 13 | def __init__(self): |
| 14 | + self.last_write_time = datetime.now(timezone.utc) |
| 15 | + self.values = {} |
| 16 | + |
14 | 17 | try: |
15 | 18 | with open(self.pickle_file, "rb") as f: |
16 | 19 | self.values = pickle.load(f) |
17 | 20 | except FileNotFoundError: |
18 | 21 | self.values = {} |
| 22 | + except ( |
| 23 | + EOFError, |
| 24 | + OSError, |
| 25 | + pickle.UnpicklingError, |
| 26 | + AttributeError, |
| 27 | + ValueError, |
| 28 | + TypeError, |
| 29 | + ) as exc: |
| 30 | + self.values = {} |
| 31 | + self._backup_corrupted_state_file(exc) |
19 | 32 |
|
20 | 33 | def write(self): |
21 | | - with open(self.pickle_file, "wb") as f: |
22 | | - pickle.dump(self.values, f) |
| 34 | + directory = os.path.dirname(self.pickle_file) or "." |
| 35 | + fd, tmp_path = tempfile.mkstemp( |
| 36 | + prefix=".state.", |
| 37 | + suffix=".tmp", |
| 38 | + dir=directory, |
| 39 | + ) |
| 40 | + try: |
| 41 | + with os.fdopen(fd, "wb") as f: |
| 42 | + pickle.dump(self.values, f) |
| 43 | + f.flush() |
| 44 | + os.fsync(f.fileno()) |
| 45 | + os.replace(tmp_path, self.pickle_file) |
| 46 | + finally: |
| 47 | + if os.path.exists(tmp_path): |
| 48 | + os.remove(tmp_path) |
| 49 | + |
| 50 | + def _backup_corrupted_state_file(self, exc): |
| 51 | + if not os.path.exists(self.pickle_file): |
| 52 | + return |
| 53 | + |
| 54 | + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S") |
| 55 | + backup_path = f"{self.pickle_file}.corrupt-{timestamp}" |
| 56 | + suffix = 1 |
| 57 | + while os.path.exists(backup_path): |
| 58 | + backup_path = f"{self.pickle_file}.corrupt-{timestamp}-{suffix}" |
| 59 | + suffix += 1 |
| 60 | + |
| 61 | + try: |
| 62 | + os.replace(self.pickle_file, backup_path) |
| 63 | + except OSError as backup_exc: |
| 64 | + return |
| 65 | + |
23 | 66 |
|
24 | 67 | def set_value(self, key, value, force_apply=False): |
25 | 68 | self.values[key] = value |
@@ -81,5 +124,3 @@ def delete(self): |
81 | 124 | s.write() |
82 | 125 |
|
83 | 126 | print(s.values) |
84 | | - |
85 | | - |
|
0 commit comments