annotate weather_server/logfile.py @ 13:4eaa9d69c4e2

Extremely basic version of the graph drawer.
author Paul Fisher <paul@pfish.zone>
date Sun, 06 Oct 2019 17:09:23 -0400
parents efe7a1eff167
children beb42c835c52
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
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