Finish data rewrite
This commit is contained in:
parent
ceb7dbf3fd
commit
e9fb29aa1b
188
src/fime/data.py
188
src/fime/data.py
@ -144,9 +144,7 @@ class Tasks:
|
|||||||
|
|
||||||
|
|
||||||
class LogCommentsData:
|
class LogCommentsData:
|
||||||
_log_key = "log"
|
_init_day = {"log": [], "comments": {}}
|
||||||
_comments_key = "comments"
|
|
||||||
_init_day = {_log_key: [], _comments_key: {}}
|
|
||||||
|
|
||||||
# Data.__setitem__ only gets triggered, when there is a direct assignment (in contrast to assignment down the tree)
|
# 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
|
# this necessitates reassigning the whole month, when triggering a save is intended
|
||||||
@ -160,12 +158,11 @@ class LogCommentsData:
|
|||||||
return
|
return
|
||||||
if month_str not in self._data:
|
if month_str not in self._data:
|
||||||
return
|
return
|
||||||
day_str = pdate.strftime("%d")
|
|
||||||
for day, log in self._data[month_str].items():
|
for day, log in self._data[month_str].items():
|
||||||
if type(log) is list:
|
if type(log) is list:
|
||||||
self._data[month_str][day] = {
|
self._data[month_str][day] = {
|
||||||
self._log_key: log,
|
"log": log,
|
||||||
self._comments_key: {}
|
"comments": {}
|
||||||
}
|
}
|
||||||
self._converted.add(month_str)
|
self._converted.add(month_str)
|
||||||
|
|
||||||
@ -177,7 +174,7 @@ class LogCommentsData:
|
|||||||
return []
|
return []
|
||||||
if day_str not in self._data[month_str]:
|
if day_str not in self._data[month_str]:
|
||||||
return []
|
return []
|
||||||
log_data = self._data[month_str][day_str][self._log_key]
|
log_data = self._data[month_str][day_str]["log"]
|
||||||
ret = []
|
ret = []
|
||||||
for entry in log_data:
|
for entry in log_data:
|
||||||
tstr, b64str = entry.split()
|
tstr, b64str = entry.split()
|
||||||
@ -186,10 +183,7 @@ class LogCommentsData:
|
|||||||
ret.append((start_time, task))
|
ret.append((start_time, task))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def set_log(self, log: List[Tuple[datetime, str]]):
|
def set_log(self, pdate: date, log: List[Tuple[datetime, str]]):
|
||||||
if not log:
|
|
||||||
return
|
|
||||||
pdate = log[0][0].date()
|
|
||||||
self._ensure_format(pdate)
|
self._ensure_format(pdate)
|
||||||
encoded = []
|
encoded = []
|
||||||
for entry in log:
|
for entry in log:
|
||||||
@ -199,8 +193,10 @@ class LogCommentsData:
|
|||||||
month_str = pdate.strftime("%Y-%m")
|
month_str = pdate.strftime("%Y-%m")
|
||||||
day_str = pdate.strftime("%d")
|
day_str = pdate.strftime("%d")
|
||||||
month = self._data.setdefault(month_str, {})
|
month = self._data.setdefault(month_str, {})
|
||||||
month.setdefault(day_str, self._init_day)[self._log_key] = encoded
|
month.setdefault(day_str, self._init_day)["log"] = encoded
|
||||||
self._data[month_str] = month
|
# trigger save if necessary
|
||||||
|
if self._data[month_str] != month:
|
||||||
|
self._data[month_str] = month
|
||||||
|
|
||||||
def add_log_entry(self, task: str):
|
def add_log_entry(self, task: str):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
@ -211,9 +207,30 @@ class LogCommentsData:
|
|||||||
month_str = now.strftime("%Y-%m")
|
month_str = now.strftime("%Y-%m")
|
||||||
day_str = now.strftime("%d")
|
day_str = now.strftime("%d")
|
||||||
month = self._data.setdefault(month_str, {})
|
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
|
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]:
|
def get_comments(self, pdate: date) -> Dict[str, str]:
|
||||||
self._ensure_format(pdate)
|
self._ensure_format(pdate)
|
||||||
month_str = pdate.strftime("%Y-%m")
|
month_str = pdate.strftime("%Y-%m")
|
||||||
@ -222,7 +239,7 @@ class LogCommentsData:
|
|||||||
return dict()
|
return dict()
|
||||||
if day_str not in self._data[month_str]:
|
if day_str not in self._data[month_str]:
|
||||||
return dict()
|
return dict()
|
||||||
comment_data = self._data[month_str][day_str][self._comments_key]
|
comment_data = self._data[month_str][day_str]["comments"]
|
||||||
ret = dict()
|
ret = dict()
|
||||||
for k, v in comment_data:
|
for k, v in comment_data:
|
||||||
k_dec = base64.b64decode(k.encode("utf-8")).decode("utf-8")
|
k_dec = base64.b64decode(k.encode("utf-8")).decode("utf-8")
|
||||||
@ -240,8 +257,10 @@ class LogCommentsData:
|
|||||||
month_str = pdate.strftime("%Y-%m")
|
month_str = pdate.strftime("%Y-%m")
|
||||||
day_str = pdate.strftime("%d")
|
day_str = pdate.strftime("%d")
|
||||||
month = self._data.setdefault(month_str, {})
|
month = self._data.setdefault(month_str, {})
|
||||||
month.setdefault(day_str, self._init_day)[self._comments_key] = encoded
|
month.setdefault(day_str, self._init_day)["comments"] = encoded
|
||||||
self._data[month_str] = month
|
# trigger save if necessary
|
||||||
|
if self._data[month_str] != month:
|
||||||
|
self._data[month_str] = month
|
||||||
|
|
||||||
|
|
||||||
class Log:
|
class Log:
|
||||||
@ -260,114 +279,94 @@ class Log:
|
|||||||
log = self._data.get_log(date.today())
|
log = self._data.get_log(date.today())
|
||||||
if not log:
|
if not log:
|
||||||
return None
|
return None
|
||||||
if log[-1] == "End":
|
if log[-1][1] == "End":
|
||||||
del log[-1]
|
del log[-1]
|
||||||
self._data.set_log(log)
|
self._data.set_log(date.today(), log)
|
||||||
if not log:
|
if not log:
|
||||||
return None
|
return None
|
||||||
return log[-1]
|
return log[-1][1]
|
||||||
|
|
||||||
def report(self):
|
def report(self):
|
||||||
return Report(self._data, date.today())
|
return Report(self._data)
|
||||||
|
|
||||||
def worklog(self):
|
def worklog(self):
|
||||||
return Worklog(self._data)
|
return Worklog(self._data)
|
||||||
|
|
||||||
|
|
||||||
class Worklog:
|
def summary(lcd: LogCommentsData, pdate: date) -> Tuple[Dict[str, timedelta], timedelta]:
|
||||||
def __init__(self, data):
|
log = lcd.get_log(pdate)
|
||||||
self._data = data
|
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:
|
class Report:
|
||||||
def __init__(self, data, pdate):
|
def __init__(self, data: LogCommentsData):
|
||||||
self._data = data
|
self._data = data
|
||||||
self._date = pdate
|
self._date = date.today()
|
||||||
self._sum_len = 0
|
self._not_log_len = 0
|
||||||
self._prev = None
|
self._prev = None
|
||||||
self._next = None
|
self._next = None
|
||||||
self._update_prev_next()
|
self._update_prev_next()
|
||||||
|
|
||||||
def report(self):
|
def report(self) -> Tuple[List[List[str]], int]:
|
||||||
tmp = []
|
log = self._data.get_log(self._date)
|
||||||
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))
|
|
||||||
if self._date == date.today():
|
if self._date == date.today():
|
||||||
tmp.append(("End", datetime.now()))
|
log.append((datetime.now(), "End"))
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
tasks_sums = {}
|
for i, t in enumerate(log):
|
||||||
total_sum = timedelta()
|
start_time, task = t
|
||||||
for i, t in enumerate(tmp):
|
if i < len(log) - 1:
|
||||||
task, start_time = t
|
end_time = log[i+1][0]
|
||||||
if i < len(tmp) - 1:
|
|
||||||
end_time = tmp[i+1][1]
|
|
||||||
duration = end_time - start_time
|
duration = end_time - start_time
|
||||||
if task != "Pause":
|
ret.append([task, start_time.strftime("%H:%M"), duration_to_str(duration)])
|
||||||
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}"])
|
|
||||||
else:
|
else:
|
||||||
ret.append([task, start_time.strftime("%H:%M"), ""])
|
ret.append([task, start_time.strftime("%H:%M"), ""])
|
||||||
|
|
||||||
ret.append(["", "", ""])
|
ret.append(["", "", ""])
|
||||||
ret.append(["", "Sums", ""])
|
ret.append(["", "Sums", ""])
|
||||||
for k, v in tasks_sums.items():
|
|
||||||
dhours, rem = divmod(v.seconds, 3600)
|
tasks_summary, total_sum = summary(self._data, self._date)
|
||||||
dmins, _ = divmod(rem, 60)
|
for task, duration in tasks_summary.items():
|
||||||
ret.append([k, "", f"{dhours:02d}:{dmins:02d}"])
|
ret.append([task, "", duration_to_str(duration)])
|
||||||
dhours, rem = divmod(total_sum.seconds, 3600)
|
ret.append(["Total sum", "", duration_to_str(total_sum)])
|
||||||
dmins, _ = divmod(rem, 60)
|
self._not_log_len = 3 + len(tasks_summary)
|
||||||
ret.append(["Total sum", "", f"{dhours:02d}:{dmins:02d}"])
|
|
||||||
self._sum_len = 3 + len(tasks_sums)
|
|
||||||
if self._date == date.today():
|
if self._date == date.today():
|
||||||
self._sum_len += 1
|
self._not_log_len += 1
|
||||||
return ret, len(ret) - (4 + len(tasks_sums))
|
editable_len = len(ret) - (4 + len(tasks_summary))
|
||||||
|
|
||||||
|
return ret, editable_len
|
||||||
|
|
||||||
def save(self, report):
|
def save(self, report):
|
||||||
report = report[:-self._sum_len]
|
report = report[:-self._not_log_len]
|
||||||
if not report:
|
if not report:
|
||||||
return
|
return
|
||||||
save_list = []
|
report = list(map(
|
||||||
for tstr, ttime, _ in report:
|
lambda x: (datetime.combine(self._date, datetime.strptime(x[1], "%H:%M").time()), x[0]),
|
||||||
b64str = base64.b64encode(tstr.encode("utf-8")).decode("utf-8")
|
report
|
||||||
save_string = f"{ttime} {b64str}"
|
))
|
||||||
save_list.append(save_string)
|
self._data.set_log(self._date, report)
|
||||||
# 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
|
|
||||||
|
|
||||||
def _update_prev_next(self):
|
def _update_prev_next(self):
|
||||||
self._prev = None
|
self._prev, self._next = self._data.get_prev_next_avail(self._date)
|
||||||
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
|
|
||||||
|
|
||||||
def prev_next_avail(self):
|
def prev_next_avail(self):
|
||||||
return self._prev is not None, self._next is not None
|
return self._prev is not None, self._next is not None
|
||||||
@ -382,3 +381,8 @@ class Report:
|
|||||||
|
|
||||||
def date(self):
|
def date(self):
|
||||||
return self._date.strftime("%Y-%m-%d")
|
return self._date.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
||||||
|
class Worklog:
|
||||||
|
def __init__(self, data: LogCommentsData):
|
||||||
|
self._data = data
|
||||||
|
@ -14,7 +14,7 @@ except ImportError:
|
|||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
import fime.icons
|
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.exceptions import FimeException
|
||||||
from fime.import_task import ImportTask
|
from fime.import_task import ImportTask
|
||||||
from fime.report import Report
|
from fime.report import Report
|
||||||
@ -29,7 +29,8 @@ class App:
|
|||||||
|
|
||||||
data = Data()
|
data = Data()
|
||||||
self.tasks = Tasks(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._active_task = self.log.last_log() or "Nothing"
|
||||||
|
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
|
Loading…
Reference in New Issue
Block a user