--- /dev/null
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import os
+import json
+
+def build_page(eatable_rows, consumption_rows, eatables_selection, day_rows):
+ return """<html>
+<meta charset="UTF-8">
+<style>
+table {
+ margin-bottom: 2em;
+}
+td, th {
+ text-align: right;
+}
+</style>""" + f"""
+<body>
+<form action="/" method="POST">
+<td><input name="update" type="submit" value="update" /></td>
+<table>
+<tr><th>title</th><th>calories</th><th>sugar (g)</th><th>standard weight (g)</th><th>comments</th><th>delete</th></tr>
+{eatable_rows}
+<tr>
+<th>add:</th>
+</tr>
+<tr>
+<td><input name="title" type="text" value="" /></td>
+<td><input name="cals" type="number" min="0" step="0.1" value="0" /></td>
+<td><input name="sugar_g" type="number" min="0" step="0.1" value="0" /></td>
+<td><input name="standard_g" type="number" min="1" step="0.1" value="1" /></td>
+<td><input name="comments" type="text" value="" /></td>
+</tr>
+</table>
+<table>
+<tr><th>day</th><th>calories</th><th>sugar (g)</th></tr>
+{day_rows}
+</table>
+<table>
+<tr><th>eatable</th><th>unit count</th><th>unit weight (g)</th><th>calories</th><th>sugar (g)</th></tr>
+{consumption_rows}
+<tr>
+<th>add from DB:</th>
+</tr>
+<tr>
+<td><select name="eatable_key">{eatables_selection}</select></td>
+<td><input name="unit_count" type="number" step="1" min="0" value="0" /></td>
+<td></td>
+</tr>
+</table>
+</form>
+</body>
+</html>"""
+
+class LockFileDetected(Exception):
+ pass
+
+class Eatable:
+
+ def __init__(self, title, cals, sugar_g, standard_g=100, comments=""):
+ self.title = title
+ self.cals = cals # per 100g
+ self.sugar_g = sugar_g # per 100g
+ self.standard_g = standard_g # common unit weight
+ self.comments = comments
+
+ def to_dict(self):
+ return {
+ "title": self.title,
+ "cals": self.cals,
+ "sugar_g": self.sugar_g,
+ "standard_g": self.standard_g,
+ "comments": self.comments
+ }
+
+class Consumption:
+
+ def __init__(self, eatable_key, unit_count=None):
+ self.eatable_key = eatable_key
+ self.unit_count = unit_count
+
+ def to_dict(self):
+ return {
+ "eatable_key": self.eatable_key,
+ "unit_count": self.unit_count,
+ }
+
+class Day:
+
+ def __init__(self, calories, sugar_g):
+ self.calories = calories
+ self.sugar_g = sugar_g
+
+ def to_dict(self):
+ return {
+ "calories": self.calories,
+ "sugar_g": self.sugar_g,
+ }
+
+class Database:
+
+ def __init__(self, load_from_file=True):
+ db_name = "calories_db"
+ self.db_file = db_name + ".json"
+ self.lock_file = db_name+ ".lock"
+ self.eatables = {}
+ self.consumptions = []
+ self.days = {}
+ self.today = Day(0, 0)
+ self.today_date = ""
+ if load_from_file and os.path.exists(self.db_file):
+ with open(self.db_file, "r") as f:
+ self.from_dict(json.load(f))
+
+ def from_dict(self, d):
+ self.set_today_date(d["today_date"])
+ for k,v in d["eatables"].items():
+ self.add_eatable(k, Eatable(v["title"], v["cals"], v["sugar_g"], v["standard_g"], v["comments"]))
+ for c in d["consumptions"]:
+ self.add_consumption(Consumption(c["eatable_key"], c["unit_count"]))
+ for k,v in d["days"].items():
+ self.add_day(k, Day(v["calories"], v["sugar_g"]))
+
+ def to_dict(self):
+ d = {"eatables": {}, "consumptions": [], "days":{}, "today_date":self.today_date}
+ for k,v in self.eatables.items():
+ d["eatables"][k] = v.to_dict()
+ for c in self.consumptions:
+ d["consumptions"] += [c.to_dict()]
+ for k,v in self.days.items():
+ d["days"][k] = v.to_dict()
+ return d
+
+ def calc_consumption(self, c):
+ eatable = self.eatables[c.eatable_key]
+ calories = eatable.cals * c.unit_count
+ sugar_g = eatable.sugar_g * c.unit_count
+ # calories = float(eatable.cals / eatable.standard_g) * c.unit_count * c.unit_weight
+ # sugar_g = float(eatable.sugar_g / eatable.standard_g) * c.unit_count * c.unit_weight
+ self.today.calories += calories
+ self.today.sugar_g += sugar_g
+ return {"cals": calories, "sugar": sugar_g }
+
+ def eatables_selection(self, selection=None):
+ html = '' # if selection else '<option value="" />'
+ for k,v in self.eatables.items():
+ selected = ' selected' if k==selection else ''
+ html += '<option value="%s"%s>%s</option>' % (k, selected, v.title)
+ return html
+
+ def add_eatable(self, id_, eatable):
+ self.eatables[id_] = eatable
+
+ def add_consumption(self, consumption):
+ self.consumptions += [consumption]
+
+ def add_day(self, date, day):
+ self.days[date] = day
+
+ def set_today_date(self, today_date):
+ self.today_date = today_date
+
+ def delete(self, id_):
+ del self.eatables[id_]
+
+ def write(self):
+ import shutil
+ if os.path.exists(self.lock_file):
+ raise LockFileDetected
+ if os.path.exists(self.db_file):
+ shutil.copy(self.db_file, self.db_file + ".bak")
+ f = open(self.lock_file, "w+")
+ f.close()
+ with open(self.db_file, "w") as f:
+ json.dump(self.to_dict(), f)
+ os.remove(self.lock_file)
+
+
+class MyServer(BaseHTTPRequestHandler):
+
+ def do_POST(self):
+ from uuid import uuid4
+ from urllib.parse import parse_qs
+ import datetime
+ length = int(self.headers['content-length'])
+ postvars = parse_qs(self.rfile.read(length).decode(), keep_blank_values=1)
+ db = Database(False)
+ def decode(key, i, is_num=True):
+ if is_num:
+ return float(postvars[key][i])
+ return postvars[key][i]
+ to_delete = []
+ if 'delete' in postvars.keys():
+ for target in postvars['delete']:
+ to_delete += [target]
+ i = 0
+ if 'eatable_uuid' in postvars.keys():
+ for uuid_encoded in postvars['eatable_uuid']:
+ uuid = uuid_encoded
+ if uuid not in to_delete:
+ e = Eatable(decode("title", i, False), decode("cals", i), decode("sugar_g", i), decode("standard_g", i), decode("comments", i, False))
+ db.add_eatable(uuid, e)
+ i += 1
+ if 'title' in postvars.keys() and len(postvars['title'][i]) > 0:
+ e = Eatable(decode("title", i, False), decode("cals", i), decode("sugar_g", i), decode("standard_g", i), decode("comments", i, False))
+ db.add_eatable(str(uuid4()), e)
+ i = 0
+ if 'eatable_key' in postvars.keys():
+ for eatable_key in postvars['eatable_key']:
+ c = Consumption(decode("eatable_key", i, False), decode("unit_count", i))
+ i += 1
+ if c.unit_count == 0:
+ continue
+ db.add_consumption(c)
+ i = 0
+ if 'day_date' in postvars.keys():
+ for date in postvars['day_date']:
+ db.add_day((date), Day(decode("day_cals", i), decode("day_sugar", i)))
+ i += 1
+ if 'new_date' in postvars.keys():
+ db.set_today_date(postvars["new_date"][0])
+ if 'archive_day' in postvars.keys():
+ new_cals = postvars["new_day_cals"][0]
+ new_sugar = postvars["new_day_sugar"][0]
+ db.add_day(db.today_date, Day(float(new_cals), float(new_sugar)))
+ db.set_today_date(str(datetime.datetime.now()))#[:10])
+ db.consumptions = []
+ try:
+ db.write()
+ self.send_response(302)
+ self.send_header('Location', '/')
+ self.end_headers()
+ except LockFileDetected:
+ self.send_response(400)
+ self.end_headers()
+ self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
+
+ def do_GET(self):
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+ db = Database()
+
+ eatables = ""
+ for k,v in db.eatables.items():
+ eatables += "<tr>"\
+ "<input name=\"eatable_uuid\" type=\"hidden\" value=\"%s\" />"\
+ "<td><input name=\"title\" value=\"%s\" /></td>"\
+ "<td><input name=\"cals\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\
+ "<td><input name=\"sugar_g\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\
+ "<td><input name=\"standard_g\" type=\"number\" step=\"0.1\" min=\"1\" value=\"%1.f\" /></td>"\
+ "<td><input name=\"comments\" value=\"%s\" /></td>"\
+ "<td><input name=\"delete\" type=\"checkbox\" value=\"%s\" /></td>"\
+ "</tr>" % (k, v.title, v.cals, v.sugar_g, v.standard_g, v.comments, k)
+ consumptions = ""
+ for c in db.consumptions:
+ r = db.calc_consumption(c)
+ consumptions += "<tr />"\
+ "<td><select name=\"eatable_key\">%s</select></td>"\
+ "<td><input name=\"unit_count\" type=\"number\" min=\"0\" value=\"%d\" /></td>"\
+ "<td></td>"\
+ "<td>%.1f</td>"\
+ "<td>%.1f</td>"\
+ "</tr>" % (db.eatables_selection(c.eatable_key), c.unit_count, r["cals"], r["sugar"])
+ day_rows = ""
+ for date, day in db.days.items():
+ day_rows += "<tr>"\
+ "<td><input name=\"day_date\" type=\"hidden\" value=\"%s\" />%s</td>"\
+ "<td><input name=\"day_cals\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\
+ "<td><input name=\"day_sugar\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\
+ "</tr>" % (date, date[:10], day.calories, day.calories, day.sugar_g, day.sugar_g)
+ day_rows += "<tr>"\
+ "<th>today:</th><th></th><th></th><th>archive?</th>"\
+ "</tr>"\
+ "<tr>"\
+ "<td><input name=\"new_date\" size=8 value=\"%s\" /></td>"\
+ "<td><input name=\"new_day_cals\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\
+ "<td><input name=\"new_day_sugar\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\
+ "<td><input name=\"archive_day\" type=\"checkbox\" /></td>"\
+ "</tr>" % (db.today_date, db.today.calories, db.today.calories, db.today.sugar_g, db.today.sugar_g)
+ page = build_page(eatables, consumptions, db.eatables_selection(), day_rows)
+ self.wfile.write(bytes(page, "utf-8"))
+
+
+hostName = "localhost"
+serverPort = 8080
+if __name__ == "__main__":
+ webServer = HTTPServer((hostName, serverPort), MyServer)
+ print("Server started http://%s:%s" % (hostName, serverPort))
+ try:
+ webServer.serve_forever()
+ except KeyboardInterrupt:
+ pass
+ webServer.server_close()
+ print("Server stopped.")