home · contact · privacy
Refactor all accounting scripts.
[misc] / plomlib.py
1 import os
2 from http.server import BaseHTTPRequestHandler 
3
4
5 class PlomException(Exception):
6     pass
7
8
9 class PlomDB:
10
11     def __init__(self, db_name):
12         self.db_file = db_name
13         self.lock_file = db_name+ '.lock'
14         if os.path.exists(self.db_file):
15             with open(self.db_file, 'r') as f:
16                 self.read_db_file(f)
17
18     def lock(self):
19         if os.path.exists(self.lock_file):
20             raise PlomException('Sorry, lock file!')
21         f = open(self.lock_file, 'w+')
22         f.close()
23
24     def unlock(self):
25         os.remove(self.lock_file)
26
27     def backup(self):
28         import shutil
29         from datetime import datetime, timedelta
30         # collect modification times of numbered .bak files
31         bak_prefix = f'{self.db_file}.bak.'
32         backup_dates = []
33         i = 0
34         bak_as = f'{bak_prefix}{i}'
35         while os.path.exists(bak_as):
36             mod_time = os.path.getmtime(bak_as)
37             backup_dates += [str(datetime.fromtimestamp(mod_time))]
38             i += 1
39             bak_as = f'{bak_prefix}{i}'
40
41         # collect what numbered .bak files to save: the older, the fewer; for each
42         # timedelta, keep the newest file that's older
43         ages_to_keep = [timedelta(minutes=4**i) for i in range(0, 8)]
44         now = datetime.now() 
45         to_save = []
46         for age in ages_to_keep:
47             limit = now - age 
48             for i, date in enumerate(reversed(backup_dates)):
49                 if datetime.strptime(date, '%Y-%m-%d %H:%M:%S.%f') < limit:
50                     unreversed_i = len(backup_dates) - i - 1
51                     if unreversed_i not in to_save:
52                         to_save += [unreversed_i]
53                     break
54
55         # remove redundant backup files 
56         j = 0
57         for i in to_save:
58             if i != j:
59                 source = f'{bak_prefix}{i}'
60                 target = f'{bak_prefix}{j}'
61                 shutil.move(source, target)
62             j += 1
63         for i in range(j, len(backup_dates)):
64             try:
65                 os.remove(f'{bak_prefix}{i}')
66             except FileNotFoundError:
67                 pass
68
69         # put copy of current state at end of bak list 
70         shutil.copy(self.db_file, f'{bak_prefix}{j}')
71
72     def write_text_to_db(self, text, mode='w'):
73         self.lock()
74         self.backup()
75         with open(self.db_file, mode) as f:
76             f.write(text);
77         self.unlock()
78
79
80 class PlomServer(BaseHTTPRequestHandler): 
81     
82     def __init__(self, *args, **kwargs):
83         self.html_head = ['<!DOCTYPE html>\n<html>\n<meta charset="UTF-8">']
84         self.html_foot = ['</body>\n</html>']
85         self.pre_init()
86         super().__init__(*args, **kwargs)
87
88     def pre_init(self):
89         pass
90
91     def fail_400(self, e):
92         self.send_HTML(f'ERROR: {e}', 400)
93
94     def send_HTML(self, html, code=200):
95         self.send_code_and_headers(code, [('Content-type', 'text/html')])
96         header = '\n'.join(self.html_head)
97         footer = '\n'.join(self.html_foot)
98         self.wfile.write(bytes(f'{header}\n{html}\n{footer}', 'utf-8'))
99
100     def send_code_and_headers(self, code, headers=[]):
101         self.send_response(code)
102         for fieldname, content in headers:
103             self.send_header(fieldname, content)
104         self.end_headers()
105
106     def redirect(self, url='/'):
107         self.send_code_and_headers(302, [('Location', url)])
108
109
110
111 def run_server(port, server_class):
112     from http.server import HTTPServer
113     webServer = HTTPServer(('localhost', port), server_class)
114     print(f"Server started http://localhost:{port}")
115     try:
116         webServer.serve_forever()
117     except KeyboardInterrupt:
118         pass
119     webServer.server_close()
120     print("Server stopped.")