-from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import json
import jinja2
+from plomlib import PlomDB, PlomException, run_server, PlomHandler
-hostName = "localhost"
-serverPort = 8081
+server_port = 8083
+db_path = '/home/plom/org/income.json'
-tmpl = jinja2.Template("""<html>
-<meta charset="UTF-8">
+tmpl = """
<style>
body {
font-family: monospace;
</style>
<body>
<table>
-<tr><th /><th>earned</th><th>progress</th><th>surplus</th></tr >
-{% for p in progress_bars %}
-<tr><th>{{p.title}}</th>
+<tr><th></th><th>earned</th><th>progress</th><th>surplus</th></tr >
+{% for p in progress_bars %}<tr><th>{{p.title}}</th>
<td class="countable">{{p.earned|round(2)}}</td>
-<td class="progressbar">{% if p.time_progress >= 0 %}<div class="time_progress" style="margin-left: {{p.time_progress}}px"></div>{% endif %}<div class="progress" style="background-color: {% if p.success < 0.5 %}red{% elif p.success < 1 %}yellow{% else %}green{% endif %}; width: {{p.success_income_cut}}"></div></td>
-<td class="progressbar surplusbar"><div class="diff_goal">{{p.diff_goal}}</div><div class="progressbar surplus" style="width: {{p.success_income_bonus}}" /></div></td></tr>
-{% endfor %}
-</table>
-<form action="/" method="POST">
+<td class="progressbar">{% if p.time_progress >= 0 %}<div class="time_progress" style="margin-left: {{p.time_progress}}px"></div>{% endif %}<div class="progress" style="background-color: {% if p.success < 0.5 %}red{% elif p.success < 1 %}yellow{% else %}green{% endif %}; width: {{p.success_income_cut}}px"></div></td>
+<td class="progressbar surplusbar"><div class="diff_goal">{{p.diff_goal}}</div><div class="progressbar surplus" style="width: {{p.success_income_bonus}}px" ></div></td></tr>
+{% endfor %}</table>
+
+<form action="{{homepage}}" method="POST">
<table>
<tr><th>hourly rate</th><th>worked today</th></tr>
<tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_1" value="{{workday_hourly_rate_1}}"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_1" value="{{workday_minutes_worked_1}}" step="5" /> minutes</td>
<tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_2" value="{{workday_hourly_rate_2}}"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_2" value="{{workday_minutes_worked_2}}" step="5" /> minutes</td>
<tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_3" value="{{workday_hourly_rate_3}}"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_3" value="{{workday_minutes_worked_3}}" step="5" /> minutes</td>
+</table>
<table>
<tr><th>yearly income goal</th><td><input type="number" class="year_goal" min="1" name="year_goal" value="{{year_goal}}" />€</td></tr>
<tr><th>monthly income goal</th><td class="countable">{{month_goal|round(2)}}€</td></tr>
<input type="submit" name="update" value="update inputs" />
<input type="submit" name="finish" value="finish day" />
</form>
-</body
-</html>""")
+"""
-class Database:
- timestamp_year = 0,
- timestamp_month = 0,
- timestamp_week = 0,
- year_income = 0,
- month_income = 0,
- week_income = 0,
- workday_hourly_rate_1 = 10,
- workday_hourly_rate_2 = 25,
- workday_hourly_rate_3 = 50,
- workday_minutes_worked_1 = 0,
- workday_minutes_worked_2 = 0,
- workday_minutes_worked_3 = 0,
- year_goal = 20000,
- workdays_per_month = 16
- def __init__(self):
- db_name = "_income"
- 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:
- d = json.load(f)
- for k, v in d.items():
- if not hasattr(self, k):
- raise Exception("bad key in db: " + k)
- setattr(self, k, v)
+class IncomeDB(PlomDB):
- def lock(self):
- if os.path.exists(self.lock_file):
- raise Exception('Sorry, lock file!')
- f = open(self.lock_file, 'w+')
- f.close()
+ def __init__(self):
+ # defaults
+ self.timestamp_year = 0,
+ self.timestamp_month = 0,
+ self.timestamp_week = 0,
+ self.year_income = 0,
+ self.month_income = 0,
+ self.week_income = 0,
+ self.workday_hourly_rate_1 = 10,
+ self.workday_hourly_rate_2 = 25,
+ self.workday_hourly_rate_3 = 50,
+ self.workday_minutes_worked_1 = 0,
+ self.workday_minutes_worked_2 = 0,
+ self.workday_minutes_worked_3 = 0,
+ self.year_goal = 20000,
+ self.workdays_per_month = 16
+ super().__init__(db_path)
- def unlock(self):
- os.remove(self.lock_file)
+ def read_db_file(self, f):
+ d = json.load(f)
+ for k, v in d.items():
+ if not hasattr(self, k):
+ raise PlomException("bad key in db: " + k)
+ setattr(self, k, v)
def to_dict(self):
keys = [k for k in dir(self) if (not k.startswith('_')) and (not callable(getattr(self, k)))]
def write_db(self):
self.write_text_to_db(json.dumps(self.to_dict()))
- 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):
- self.lock()
- self.backup()
- with open(self.db_file, 'w') as f:
- f.write(text);
- self.unlock()
class ProgressBar:
def __init__(self, title, earned, goal, time_progress=-1):
if time_progress > 0:
self.success = success_income / time_progress
-class MyServer(BaseHTTPRequestHandler):
+
+class IncomeProgressHandler(PlomHandler):
+
+ def app_init(self, handler):
+ default_path = '/income_progress'
+ handler.add_route('GET', default_path, self.display_income_progress)
+ handler.add_route('POST', default_path, self.post_income_update)
+ return 'income_progress', default_path
def do_POST(self):
+ self.try_do(self.post_income_update)
+
+ def post_income_update(self):
from urllib.parse import parse_qs
length = int(self.headers['content-length'])
postvars = parse_qs(self.rfile.read(length).decode(), keep_blank_values=1)
- db = Database()
+ db = IncomeDB()
db.workday_minutes_worked_1 = int(postvars['workday_minutes_worked_1'][0])
db.workday_minutes_worked_2 = int(postvars['workday_minutes_worked_2'][0])
db.workday_minutes_worked_3 = int(postvars['workday_minutes_worked_3'][0])
db.workday_minutes_worked_2 = 0
db.workday_minutes_worked_3 = 0
db.write_db()
- self.send_response(302)
- self.send_header('Location', '/')
- self.end_headers()
+ homepage = self.apps['income_progress'] if hasattr(self, 'apps') else self.homepage
+ self.redirect(homepage)
def do_GET(self):
+ self.try_do(self.display_income_progress)
+
+ def display_income_progress(self):
import datetime
import calendar
- db = Database() #load_db()
+ db = IncomeDB()
today = datetime.datetime.now()
update_db = False
if today.year != db.timestamp_year:
ProgressBar("month", month_plus, month_goal, progress_time_month),
ProgressBar("week", week_plus, week_goal, progress_time_week),
ProgressBar("workday", day_income, workday_goal)]
- page = tmpl.render(
+ homepage = self.apps['income_progress'] if hasattr(self, 'apps') else self.homepage
+ page = jinja2.Template(tmpl).render(
+ homepage = homepage,
progress_bars = progress_bars,
workday_hourly_rate_1 = db.workday_hourly_rate_1,
workday_minutes_worked_1 = db.workday_minutes_worked_1,
workday_goal = workday_goal,
workdays_per_week = workdays_per_week,
)
- self.send_response(200)
- self.send_header("Content-type", "text/html")
- self.end_headers()
- self.wfile.write(bytes(page, "utf-8"))
+ self.send_HTML(page)
- def fail_on_lockfile(self):
- if os.path.exists(lock_file):
- self.send_response(400)
- self.end_headers()
- self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
- return True
- return False
if __name__ == "__main__":
- webServer = HTTPServer((hostName, serverPort), MyServer)
- print(f"Server started http://{hostName}:{serverPort}")
- try:
- webServer.serve_forever()
- except KeyboardInterrupt:
- pass
- webServer.server_close()
- print("Server stopped.")
+ run_server(server_port, IncomeProgressHandler)