Finish data rewrite

This commit is contained in:
Fabian 2021-11-28 13:19:42 +01:00
parent ceb7dbf3fd
commit e9fb29aa1b
2 changed files with 99 additions and 94 deletions

View File

@ -144,9 +144,7 @@ class Tasks:
class LogCommentsData:
_log_key = "log"
_comments_key = "comments"
_init_day = {_log_key: [], _comments_key: {}}
_init_day = {"log": [], "comments": {}}
# Data.__setitem__ only gets triggered, when there is a direct assignment (in contrast to assignment down the tree)
# this necessitates reassigning the whole month, when triggering a save is intended
@ -160,12 +158,11 @@ class LogCommentsData:
return
if month_str not in self._data:
return
day_str = pdate.strftime("%d")
for day, log in self._data[month_str].items():
if type(log) is list:
self._data[month_str][day] = {
self._log_key: log,
self._comments_key: {}
"log": log,
"comments": {}
}
self._converted.add(month_str)
@ -177,7 +174,7 @@ class LogCommentsData:
return []
if day_str not in self._data[month_str]:
return []
log_data = self._data[month_str][day_str][self._log_key]
log_data = self._data[month_str][day_str]["log"]
ret = []
for entry in log_data:
tstr, b64str = entry.split()
@ -186,10 +183,7 @@ class LogCommentsData:
ret.append((start_time, task))
return ret
def set_log(self, log: List[Tuple[datetime, str]]):
if not log:
return
pdate = log[0][0].date()
def set_log(self, pdate: date, log: List[Tuple[datetime, str]]):
self._ensure_format(pdate)
encoded = []
for entry in log:
@ -199,8 +193,10 @@ class LogCommentsData:
month_str = pdate.strftime("%Y-%m")
day_str = pdate.strftime("%d")
month = self._data.setdefault(month_str, {})
month.setdefault(day_str, self._init_day)[self._log_key] = encoded
self._data[month_str] = month
month.setdefault(day_str, self._init_day)["log"] = encoded
# trigger save if necessary
if self._data[month_str] != month:
self._data[month_str] = month
def add_log_entry(self, task: str):
now = datetime.now()
@ -211,9 +207,30 @@ class LogCommentsData:
month_str = now.strftime("%Y-%m")
day_str = now.strftime("%d")
month = self._data.setdefault(month_str, {})
month.setdefault(day_str, self._init_day)[self._log_key].append(encoded)
month.setdefault(day_str, self._init_day)["log"].append(encoded)
self._data[month_str] = month
def get_prev_next_avail(self, pdate: date) -> Tuple[date, date]:
prev = None
next = None
for i in range(1, 32):
new_date = pdate - timedelta(days=i)
if new_date.strftime("%Y-%m") not in self._data:
break
if new_date.strftime("%d") in self._data[new_date.strftime("%Y-%m")]:
prev = new_date
break
for i in range(1, 32):
new_date = pdate + timedelta(days=i)
if new_date > date.today():
break
if new_date.strftime("%Y-%m") not in self._data:
break
if new_date.strftime("%d") in self._data[new_date.strftime("%Y-%m")]:
next = new_date
break
return prev, next
def get_comments(self, pdate: date) -> Dict[str, str]:
self._ensure_format(pdate)
month_str = pdate.strftime("%Y-%m")
@ -222,7 +239,7 @@ class LogCommentsData:
return dict()
if day_str not in self._data[month_str]:
return dict()
comment_data = self._data[month_str][day_str][self._comments_key]
comment_data = self._data[month_str][day_str]["comments"]
ret = dict()
for k, v in comment_data:
k_dec = base64.b64decode(k.encode("utf-8")).decode("utf-8")
@ -240,8 +257,10 @@ class LogCommentsData:
month_str = pdate.strftime("%Y-%m")
day_str = pdate.strftime("%d")
month = self._data.setdefault(month_str, {})
month.setdefault(day_str, self._init_day)[self._comments_key] = encoded
self._data[month_str] = month
month.setdefault(day_str, self._init_day)["comments"] = encoded
# trigger save if necessary
if self._data[month_str] != month:
self._data[month_str] = month
class Log:
@ -260,114 +279,94 @@ class Log:
log = self._data.get_log(date.today())
if not log:
return None
if log[-1] == "End":
if log[-1][1] == "End":
del log[-1]
self._data.set_log(log)
self._data.set_log(date.today(), log)
if not log:
return None
return log[-1]
return log[-1][1]
def report(self):
return Report(self._data, date.today())
return Report(self._data)
def worklog(self):
return Worklog(self._data)
class Worklog:
def __init__(self, data):
self._data = data
def summary(lcd: LogCommentsData, pdate: date) -> Tuple[Dict[str, timedelta], timedelta]:
log = lcd.get_log(pdate)
if pdate == date.today():
log.append((datetime.now(), "End"))
tasks_sums = {}
total_sum = timedelta()
for i, le in enumerate(log):
start_time, task = le
if i < len(log) - 1:
end_time = log[i+1][0]
duration = end_time - start_time
if task != "Pause":
task_sum = tasks_sums.setdefault(task, timedelta())
task_sum += duration
tasks_sums[task] = task_sum
total_sum += duration
return tasks_sums, total_sum
def duration_to_str(duration: timedelta) -> str:
dhours, rem = divmod(duration.seconds, 3600)
dmins, _ = divmod(rem, 60)
return f"{dhours:02d}:{dmins:02d}"
class Report:
def __init__(self, data, pdate):
def __init__(self, data: LogCommentsData):
self._data = data
self._date = pdate
self._sum_len = 0
self._date = date.today()
self._not_log_len = 0
self._prev = None
self._next = None
self._update_prev_next()
def report(self):
tmp = []
if self._date.strftime("%Y-%m") in self._data \
and self._date.strftime("%d") in self._data[self._date.strftime("%Y-%m")]:
for e in self._data[self._date.strftime("%Y-%m")][self._date.strftime("%d")]:
tstr, b64str = e.split()
task = base64.b64decode(b64str.encode("utf-8")).decode("utf-8")
start_time = datetime.combine(self._date, datetime.strptime(tstr, "%H:%M").time())
tmp.append((task, start_time))
def report(self) -> Tuple[List[List[str]], int]:
log = self._data.get_log(self._date)
if self._date == date.today():
tmp.append(("End", datetime.now()))
log.append((datetime.now(), "End"))
ret = []
tasks_sums = {}
total_sum = timedelta()
for i, t in enumerate(tmp):
task, start_time = t
if i < len(tmp) - 1:
end_time = tmp[i+1][1]
for i, t in enumerate(log):
start_time, task = t
if i < len(log) - 1:
end_time = log[i+1][0]
duration = end_time - start_time
if task != "Pause":
task_sum = tasks_sums.setdefault(task, timedelta())
task_sum += duration
tasks_sums[task] = task_sum
total_sum += duration
dhours, rem = divmod(duration.seconds, 3600)
dmins, _ = divmod(rem, 60)
ret.append([task, start_time.strftime("%H:%M"), f"{dhours:02d}:{dmins:02d}"])
ret.append([task, start_time.strftime("%H:%M"), duration_to_str(duration)])
else:
ret.append([task, start_time.strftime("%H:%M"), ""])
ret.append(["", "", ""])
ret.append(["", "Sums", ""])
for k, v in tasks_sums.items():
dhours, rem = divmod(v.seconds, 3600)
dmins, _ = divmod(rem, 60)
ret.append([k, "", f"{dhours:02d}:{dmins:02d}"])
dhours, rem = divmod(total_sum.seconds, 3600)
dmins, _ = divmod(rem, 60)
ret.append(["Total sum", "", f"{dhours:02d}:{dmins:02d}"])
self._sum_len = 3 + len(tasks_sums)
tasks_summary, total_sum = summary(self._data, self._date)
for task, duration in tasks_summary.items():
ret.append([task, "", duration_to_str(duration)])
ret.append(["Total sum", "", duration_to_str(total_sum)])
self._not_log_len = 3 + len(tasks_summary)
if self._date == date.today():
self._sum_len += 1
return ret, len(ret) - (4 + len(tasks_sums))
self._not_log_len += 1
editable_len = len(ret) - (4 + len(tasks_summary))
return ret, editable_len
def save(self, report):
report = report[:-self._sum_len]
report = report[:-self._not_log_len]
if not report:
return
save_list = []
for tstr, ttime, _ in report:
b64str = base64.b64encode(tstr.encode("utf-8")).decode("utf-8")
save_string = f"{ttime} {b64str}"
save_list.append(save_string)
# month dance necessary to trigger Data.__setitem__
month = self._data[self._date.strftime("%Y-%m")]
if month[self._date.strftime("%d")] == save_list: # no changes
return
month[self._date.strftime("%d")] = save_list
self._data[self._date.strftime("%Y-%m")] = month
report = list(map(
lambda x: (datetime.combine(self._date, datetime.strptime(x[1], "%H:%M").time()), x[0]),
report
))
self._data.set_log(self._date, report)
def _update_prev_next(self):
self._prev = None
self._next = None
for i in range(1, 32):
new_date = self._date - timedelta(days=i)
if new_date.strftime("%Y-%m") not in self._data:
break
if new_date.strftime("%d") in self._data[new_date.strftime("%Y-%m")]:
self._prev = new_date
break
for i in range(1, 32):
new_date = self._date + timedelta(days=i)
if new_date > date.today():
break
if new_date.strftime("%Y-%m") not in self._data:
break
if new_date.strftime("%d") in self._data[new_date.strftime("%Y-%m")]:
self._next = new_date
break
self._prev, self._next = self._data.get_prev_next_avail(self._date)
def prev_next_avail(self):
return self._prev is not None, self._next is not None
@ -382,3 +381,8 @@ class Report:
def date(self):
return self._date.strftime("%Y-%m-%d")
class Worklog:
def __init__(self, data: LogCommentsData):
self._data = data

View File

@ -14,7 +14,7 @@ except ImportError:
# noinspection PyUnresolvedReferences
import fime.icons
from fime.data import Tasks, Log, Data
from fime.data import Tasks, Log, Data, LogCommentsData
from fime.exceptions import FimeException
from fime.import_task import ImportTask
from fime.report import Report
@ -29,7 +29,8 @@ class App:
data = Data()
self.tasks = Tasks(data)
self.log = Log(data)
lcd = LogCommentsData(data)
self.log = Log(lcd)
self._active_task = self.log.last_log() or "Nothing"
self.config = Config()