home · contact · privacy
Improve/refactor income and ledger scripts with plomlib.
[misc] / plomlib.py
diff --git a/plomlib.py b/plomlib.py
new file mode 100644 (file)
index 0000000..9dac712
--- /dev/null
@@ -0,0 +1,119 @@
+import os
+from http.server import BaseHTTPRequestHandler 
+
+
+class PlomException(Exception):
+    pass
+
+
+class PlomDB:
+
+    def __init__(self, db_name):
+        self.db_file = db_name + ".json"
+        self.lock_file = db_name+ ".lock"
+        if os.path.exists(self.db_file):
+            with open(self.db_file, "r") as f:
+                self.read_db_file(f)
+
+    def lock(self):
+        if os.path.exists(self.lock_file):
+            raise PlomException('Sorry, lock file!')
+        f = open(self.lock_file, 'w+')
+        f.close()
+
+    def unlock(self):
+        os.remove(self.lock_file)
+
+    def backup(self):
+        import shutil
+        from datetime import datetime, timedelta
+        # collect modification times of numbered .bak files
+        bak_prefix = f'{self.db_file}.bak.'
+        backup_dates = []
+        i = 0
+        bak_as = f'{bak_prefix}{i}'
+        while os.path.exists(bak_as):
+            mod_time = os.path.getmtime(bak_as)
+            backup_dates += [str(datetime.fromtimestamp(mod_time))]
+            i += 1
+            bak_as = f'{bak_prefix}{i}'
+
+        # collect what numbered .bak files to save: the older, the fewer; for each
+        # timedelta, keep the newest file that's older
+        ages_to_keep = [timedelta(minutes=4**i) for i in range(0, 8)]
+        now = datetime.now() 
+        to_save = []
+        for age in ages_to_keep:
+            limit = now - age 
+            for i, date in enumerate(reversed(backup_dates)):
+                if datetime.strptime(date, '%Y-%m-%d %H:%M:%S.%f') < limit:
+                    unreversed_i = len(backup_dates) - i - 1
+                    if unreversed_i not in to_save:
+                        to_save += [unreversed_i]
+                    break
+
+        # remove redundant backup files 
+        j = 0
+        for i in to_save:
+            if i != j:
+                source = f'{bak_prefix}{i}'
+                target = f'{bak_prefix}{j}'
+                shutil.move(source, target)
+            j += 1
+        for i in range(j, len(backup_dates)):
+            try:
+                os.remove(f'{bak_prefix}{i}')
+            except FileNotFoundError:
+                pass
+
+        # put copy of current state at end of bak list 
+        shutil.copy(self.db_file, f'{bak_prefix}{j}')
+
+    def write_text_to_db(self, text, mode='w'):
+        self.lock()
+        self.backup()
+        with open(self.db_file, mode) as f:
+            f.write(text);
+        self.unlock()
+
+
+class PlomServer(BaseHTTPRequestHandler): 
+    header = ''
+    footer = ''
+
+    def run(self, port):
+        from http.server import HTTPServer
+        webServer = HTTPServer(('localhost', port), type(self))
+        print(f"Server started http://localhost:{port}")
+        try:
+            webServer.serve_forever()
+        except KeyboardInterrupt:
+            pass
+        webServer.server_close()
+        print("Server stopped.")
+
+    def fail_400(self, e):
+        page = f'{self.header}ERROR: {e}{self.footer}'
+        self.send_HTML(page, 400)
+
+    def send_HTML(self, html, code=200):
+        self.send_code_and_headers(code, [('Content-type', 'text/html')])
+        self.wfile.write(bytes(html, "utf-8"))
+
+    def send_code_and_headers(self, code, headers=[]):
+        self.send_response(code)
+        for fieldname, content in headers:
+            self.send_header(fieldname, content)
+        self.end_headers()
+
+
+def run_server(port, server_class):
+    from http.server import HTTPServer
+    webServer = HTTPServer(('localhost', port), server_class)
+    print(f"Server started http://localhost:{port}")
+    try:
+        webServer.serve_forever()
+    except KeyboardInterrupt:
+        pass
+    webServer.server_close()
+    print("Server stopped.")