+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)
+
+ def lock(self):
+ if os.path.exists(self.lock_file):
+ raise Exception('Sorry, lock file!')
+ f = open(self.lock_file, 'w+')
+ f.close()
+
+ def unlock(self):
+ os.remove(self.lock_file)
+
+ def to_dict(self):
+ keys = [k for k in dir(self) if (not k.startswith('_')) and (not callable(getattr(self, k)))]
+ d = {}
+ for k in keys:
+ d[k] = getattr(self, k)
+ return d
+
+ 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):
+ self.title = title
+ self.earned = earned
+ self.time_progress = int(time_progress * 100)
+ success_income = self.earned / goal
+ self.success_income_cut = int(min(success_income, 1.0) * 100)
+ self.success_income_bonus = int(max(success_income - 1.0, 0) * 100)
+ self.success = success_income + 0
+ self.diff_goal = "%.2f€" % (self.earned - goal)
+ if title != "workday":
+ self.diff_goal += "(%.2f€)" % (self.earned - (goal * time_progress))
+ if time_progress >= 0:
+ self.success = 1
+ if time_progress > 0:
+ self.success = success_income / time_progress