home · contact · privacy
Add todo accounting.
[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         if not os.path.exists(self.db_file):
31             return
32
33         # collect modification times of numbered .bak files
34         print('DEBUG BACKUP')
35         bak_prefix = f'{self.db_file}.bak.'
36         # backup_dates = []
37         mtimes_to_paths = {}
38         for path in [path for path in os.listdir(os.path.dirname(bak_prefix))
39                      if path.startswith(os.path.basename(bak_prefix))]:
40             path = os.path.dirname(bak_prefix) + f'/{path}'
41             mod_time = os.path.getmtime(path)
42             print(f'DEBUG pre-exists: {path} {mod_time}')
43             mtimes_to_paths[str(datetime.fromtimestamp(mod_time))] = path
44             # backup_dates += [str(datetime.fromtimestamp(mod_time))]
45
46         for mtime in sorted(mtimes_to_paths.keys()):
47             print(f'DEBUG mtimes_to_paths: {mtime}:{mtimes_to_paths[mtime]}')
48
49         # collect what numbered .bak files to save: the older, the fewer; for each
50         # timedelta, keep the newest file that's older
51         ages_to_keep = [timedelta(minutes=4**i) for i in range(0, 8)]
52         print(f'DEBUG ages_to_keep: {ages_to_keep}')
53         now = datetime.now() 
54         to_save = {}
55         for age in ages_to_keep:
56             limit = now - age 
57             for mtime in reversed(sorted(mtimes_to_paths.keys())):
58                 print(f'DEBUG checking if {mtime} < {limit} ({now} - {age})')
59                 if datetime.strptime(mtime, '%Y-%m-%d %H:%M:%S.%f') < limit:
60                     print('DEBUG it is, adding!')
61                     to_save[mtime] = mtimes_to_paths[mtime]
62                     break
63
64         for path in [path for path in mtimes_to_paths.values()
65                      if path not in to_save.values()]:
66             print(f'DEBUG removing {path} cause not in to_save')
67             os.remove(path)
68
69         i = 0
70         for mtime in sorted(to_save.keys()):
71             source = to_save[mtime]
72             target = f'{bak_prefix}{i}'
73             print(f'DEBUG to_save {source} -> {target}')
74             if source != target:
75                 shutil.move(source, target)
76             i += 1
77
78         # put copy of current state at end of bak list 
79         print(f'DEBUG saving current state to {bak_prefix}{i}')
80         shutil.copy(self.db_file, f'{bak_prefix}{i}')
81
82     def write_text_to_db(self, text, mode='w'):
83         self.lock()
84         self.backup()
85         with open(self.db_file, mode) as f:
86             f.write(text);
87         self.unlock()
88
89
90 class PlomHandler(BaseHTTPRequestHandler): 
91     homepage = '/'
92     html_head = '<!DOCTYPE html>\n<html>\n<meta charset="UTF-8">'
93     html_foot = '</body>\n</html>'
94     
95     def fail_400(self, e):
96         self.send_HTML(f'ERROR: {e}', 400)
97
98     def send_HTML(self, html, code=200):
99         self.send_code_and_headers(code, [('Content-type', 'text/html')])
100         self.wfile.write(bytes(f'{self.html_head}\n{html}\n{self.html_foot}', 'utf-8'))
101
102     def send_code_and_headers(self, code, headers=[]):
103         self.send_response(code)
104         for fieldname, content in headers:
105             self.send_header(fieldname, content)
106         self.end_headers()
107
108     def redirect(self, url='/'):
109         self.send_code_and_headers(302, [('Location', url)])
110
111     def try_do(self, do_method):
112         try:
113             do_method() 
114         except PlomException as e:
115             self.fail_400(e)
116
117
118
119 def run_server(port, handler_class):
120     from http.server import HTTPServer
121     webServer = HTTPServer(('localhost', port), handler_class)
122     print(f"Server started http://localhost:{port}")
123     try:
124         webServer.serve_forever()
125     except KeyboardInterrupt:
126         pass
127     webServer.server_close()
128     print("Server stopped.")