Mercurial > personal > weather-server
annotate weather_server/logfile.py @ 4:e7c8dcc5fc15
Make the weather server pip-installable and locally runnable.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 29 Sep 2019 15:10:18 -0400 |
parents | efe7a1eff167 |
children | beb42c835c52 |
rev | line source |
---|---|
0
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
1 """The part which handles writing things out and reading things in from CSV. |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
2 """ |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
3 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
4 import fcntl |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
5 import os |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
6 import threading |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
7 import typing as t |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
8 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
9 import bson |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
10 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
11 from . import common |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
12 from . import types |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
13 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
14 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
15 class Logger: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
16 """Logger which handles reading/writing a temperature log for one process. |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
17 """ |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
18 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
19 instance_lock = threading.Lock() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
20 instances: t.Dict[str, 'Logger'] = {} |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
21 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
22 @classmethod |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
23 def create(cls, filename: str) -> 'Logger': |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
24 """Creates a single shared instance of a logger for the given file.""" |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
25 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
26 return cls.instances[filename] |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
27 except KeyError: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
28 with cls.instance_lock: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
29 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
30 return cls.instances[filename] |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
31 except KeyError: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
32 cls.instances[filename] = Logger(filename) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
33 return cls.instances[filename] |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
34 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
35 def __init__(self, filename: str): |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
36 """You should probably call .create() instead.""" |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
37 self._file = _open_or_create(filename) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
38 self._data: t.Tuple[types.Reading] = () |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
39 self._last_size = 0 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
40 self._maybe_read_data() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
41 self._lock = threading.Lock() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
42 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
43 def _maybe_read_data(self) -> None: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
44 """Reads data and advances the file pointer to the end of the file.""" |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
45 # This must be called with both the file lock and _lock held. |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
46 size = self._size() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
47 if size == self._last_size: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
48 return |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
49 last_good = self._file.tell() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
50 data = list(self._data) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
51 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
52 items = bson.decode_file_iter( |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
53 self._file, codec_options=common.BSON_OPTIONS) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
54 for item in items: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
55 last_good = self._file.tell() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
56 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
57 data.append(types.Reading(**item)) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
58 except TypeError: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
59 pass # Skip this item. |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
60 except bson.InvalidBSON: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
61 pass # We have reached the last valid document. Bail. |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
62 # Seek back to immediately after the end of the last valid doc. |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
63 self._data = tuple(data) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
64 self._file.truncate(last_good) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
65 self._last_size = last_good |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
66 self._file.seek(last_good, os.SEEK_SET) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
67 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
68 def write_rows(self, readings: t.Iterable[types.Reading]) -> None: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
69 """Write a sorted series of readings, ignoring old ones.""" |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
70 with self._lock: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
71 fcntl.flock(self._file.fileno(), fcntl.LOCK_EX) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
72 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
73 self._maybe_read_data() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
74 self._file.truncate(self._file.tell()) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
75 data = list(self._data) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
76 if not data: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
77 last_time = None |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
78 else: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
79 last_time = data[-1].sample_time |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
80 for reading in readings: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
81 if not last_time or last_time < reading.sample_time: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
82 self._file.write(common.bson_encode(reading.as_dict())) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
83 data.append(reading) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
84 self._data = tuple(data) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
85 finally: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
86 self._file.flush() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
87 self._last_size = self._size() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
88 fcntl.flock(self, fcntl.LOCK_UN) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
89 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
90 def fileno(self) -> int: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
91 return self._file.fileno() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
92 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
93 def close(self): |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
94 self._file.close() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
95 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
96 @property |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
97 def data(self) -> t.Tuple[types.Reading, ...]: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
98 if self._size() != self._last_size: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
99 fcntl.flock(self, fcntl.LOCK_SH) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
100 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
101 with self._lock: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
102 self._maybe_read_data() |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
103 finally: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
104 fcntl.flock(self, fcntl.LOCK_UN) |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
105 return self._data |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
106 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
107 def _size(self) -> int: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
108 return os.stat(self.fileno()).st_size |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
109 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
110 |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
111 def _open_or_create(path: str) -> t.BinaryIO: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
112 while True: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
113 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
114 return open(path, 'r+b') |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
115 except FileNotFoundError: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
116 pass |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
117 try: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
118 return open(path, 'x+b') |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
119 except FileExistsError: |
efe7a1eff167
Create initial logger for weather server.
Paul Fisher <paul@pfish.zone>
parents:
diff
changeset
|
120 pass |