From 905d83bd2f318a56402bb1f8acb88373d17f5c9c Mon Sep 17 00:00:00 2001 From: ederag Date: Sun, 6 Jan 2019 16:09:07 +0100 Subject: [PATCH 1/7] change Fact.delta to a property Now Fact.delta is calculated upon access. This fixes durations that were not updated in the overview for the running activity. --- src/hamster/client.py | 2 -- src/hamster/lib/__init__.py | 11 ++++++++--- src/hamster/lib/stuff.py | 2 +- src/hamster/reports.py | 3 +-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/hamster/client.py b/src/hamster/client.py index 1332ab1f1..e8a9fe5e6 100644 --- a/src/hamster/client.py +++ b/src/hamster/client.py @@ -36,8 +36,6 @@ def from_dbus_fact(fact): category = fact[6], tags = fact[7], date = dt.datetime.utcfromtimestamp(fact[8]).date(), - delta = dt.timedelta(days = fact[9] // (24 * 60 * 60), - seconds = fact[9] % (24 * 60 * 60)), id = fact[0] ) diff --git a/src/hamster/lib/__init__.py b/src/hamster/lib/__init__.py index b31fde5d9..c8dd73dd4 100644 --- a/src/hamster/lib/__init__.py +++ b/src/hamster/lib/__init__.py @@ -70,7 +70,7 @@ def figure_time(str_time): class Fact(object): def __init__(self, activity="", category = "", description = "", tags = "", - start_time = None, end_time = None, id = None, delta = None, + start_time = None, end_time = None, id = None, date = None, activity_id = None, initial_fact=None): """Homogeneous chunk of activity. The category, description and tags can be either passed in explicitly @@ -108,7 +108,6 @@ def __init__(self, activity="", category = "", description = "", tags = "", self.end_time = None self.id = id self.ponies = False - self.delta = delta self.activity_id = activity_id phase = "start_time" if date else "date" @@ -135,7 +134,7 @@ def as_dict(self): 'date': calendar.timegm(date.timetuple()) if date else "", 'start_time': self.start_time if isinstance(self.start_time, str) else calendar.timegm(self.start_time.timetuple()), 'end_time': self.end_time if isinstance(self.end_time, str) else calendar.timegm(self.end_time.timetuple()) if self.end_time else "", - 'delta': self.delta.seconds + self.delta.days * 24 * 60 * 60 if self.delta else "" #duration in seconds + 'delta': self.delta.total_seconds() # ugly, but needed for report.py } @property @@ -157,6 +156,12 @@ def date(self, value): if self.end_time: self.end_time = hamsterday_time_to_datetime(value, self.end_time.time()) + @property + def delta(self): + """Duration (datetime.timedelta).""" + end_time = self.end_time if self.end_time else dt.datetime.now() + return end_time - self.start_time + def serialized_name(self): res = self.activity diff --git a/src/hamster/lib/stuff.py b/src/hamster/lib/stuff.py index 43789a7f7..99687b6a3 100644 --- a/src/hamster/lib/stuff.py +++ b/src/hamster/lib/stuff.py @@ -172,7 +172,7 @@ def duration_minutes(duration): return duration_minutes(res) elif isinstance(duration, dt.timedelta): - return duration.seconds / 60 + duration.days * 24 * 60 + return duration.total_seconds() / 60 else: return duration diff --git a/src/hamster/reports.py b/src/hamster/reports.py index 69314c802..c52b90894 100644 --- a/src/hamster/reports.py +++ b/src/hamster/reports.py @@ -160,11 +160,10 @@ def __init__(self, path): self.csv_writer.writerow([h for h in headers]) def _write_fact(self, fact): - fact.delta = stuff.duration_minutes(fact.delta) self.csv_writer.writerow([fact.activity, fact.start_time, fact.end_time, - fact.delta, + str(stuff.duration_minutes(fact.delta)), fact.category, fact.description, ", ".join(fact.tags)]) From 8b166c475b3e69e5861851fb7aaa13c651f2db72 Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 10 Jan 2019 19:14:30 +0100 Subject: [PATCH 2/7] pass dates to reports, not datetimes (partial fix to issue #373) --- src/hamster-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hamster-cli b/src/hamster-cli index e856422d1..21968eb48 100755 --- a/src/hamster-cli +++ b/src/hamster-cli @@ -215,7 +215,7 @@ class HamsterClient(object): end_time = end_time or start_time.replace(hour=23, minute=59, second=59) facts = self.storage.get_facts(start_time, end_time) - writer = reports.simple(facts, start_time, end_time, export_format) + writer = reports.simple(facts, start_time.date(), end_time.date(), export_format) print(writer.export()) From 45c5e8707aeb27dbdf6c6a69e926a9b8df576184 Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 10 Jan 2019 19:16:49 +0100 Subject: [PATCH 3/7] remove the ReportWriter.export function (fix issue #373) No longer used in hamster. And could lead to ValueError: I/O operation on closed file --- src/hamster-cli | 1 - src/hamster/reports.py | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/hamster-cli b/src/hamster-cli index 21968eb48..d30cb045f 100755 --- a/src/hamster-cli +++ b/src/hamster-cli @@ -216,7 +216,6 @@ class HamsterClient(object): facts = self.storage.get_facts(start_time, end_time) writer = reports.simple(facts, start_time.date(), end_time.date(), export_format) - print(writer.export()) def _activities(self, search=""): diff --git a/src/hamster/reports.py b/src/hamster/reports.py index c52b90894..85c182c2b 100644 --- a/src/hamster/reports.py +++ b/src/hamster/reports.py @@ -75,12 +75,11 @@ def simple(facts, start_date, end_date, format, path = None): class ReportWriter(object): #a tiny bit better than repeating the code all the time def __init__(self, path = None, datetime_format = "%Y-%m-%d %H:%M:%S"): - self.file = open(path, "w") if path else codecs.getwriter("utf8")(StringIO()) + # if path is empty or None, print to stdout + self.file = open(path, "w") if path else StringIO() + self.path = path self.datetime_format = datetime_format - def export(self): - return self.file.getvalue() - def write_report(self, facts): try: for fact in facts: @@ -100,8 +99,10 @@ def write_report(self, facts): self._finish(facts) finally: - if isinstance(self.file, IOBase): - self.file.close() + if not self.path: + # print the full report to stdout + print(self.file.getvalue()) + self.file.close() def _start(self, facts): raise NotImplementedError @@ -296,7 +297,6 @@ def _finish(self, facts): date_facts.append([str_date, by_date.get(date, [])]) date += dt.timedelta(days=1) - data = dict( title = self.title, From fbbf59adc5d84c9b62acad91a686076883dca6c6 Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 3 Jan 2019 19:23:40 +0100 Subject: [PATCH 4/7] create Fact.copy() --- src/hamster/lib/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hamster/lib/__init__.py b/src/hamster/lib/__init__.py index c8dd73dd4..2d24b0992 100644 --- a/src/hamster/lib/__init__.py +++ b/src/hamster/lib/__init__.py @@ -137,6 +137,10 @@ def as_dict(self): 'delta': self.delta.total_seconds() # ugly, but needed for report.py } + def copy(self, **kwds): + """Return an independent copy, with overrides as keyword arguments.""" + return Fact(initial_fact=self, **kwds) + @property def date(self): """hamster day, determined from start_time. From 534f5fe28a4818bb8804bc6179936e3b706157da Mon Sep 17 00:00:00 2001 From: ederag Date: Thu, 10 Jan 2019 22:12:07 +0100 Subject: [PATCH 5/7] do not replace fact.start_time and end_time with str Fix TSV export return end_time - self.start_time TypeError: unsupported operand type(s) for -: 'str' and 'str' --- src/hamster/reports.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/hamster/reports.py b/src/hamster/reports.py index 85c182c2b..ad1332a81 100644 --- a/src/hamster/reports.py +++ b/src/hamster/reports.py @@ -83,18 +83,9 @@ def __init__(self, path = None, datetime_format = "%Y-%m-%d %H:%M:%S"): def write_report(self, facts): try: for fact in facts: - fact.activity= fact.activity fact.description = (fact.description or "") fact.category = (fact.category or _("Unsorted")) - if self.datetime_format: - fact.start_time = fact.start_time.strftime(self.datetime_format) - - if fact.end_time: - fact.end_time = fact.end_time.strftime(self.datetime_format) - else: - fact.end_time = "" - self._write_fact(fact) self._finish(facts) From 4e185f59ada79ae4d493d8bc75d065a3c4216770 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 12 Jan 2019 17:50:30 +0100 Subject: [PATCH 6/7] fix ical export --- src/hamster/reports.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/hamster/reports.py b/src/hamster/reports.py index ad1332a81..cf482b309 100644 --- a/src/hamster/reports.py +++ b/src/hamster/reports.py @@ -28,6 +28,7 @@ import re import codecs from string import Template +from textwrap import dedent from hamster.lib.configuration import runtime from hamster.lib import stuff, trophies @@ -104,6 +105,7 @@ def _write_fact(self, fact): def _finish(self, facts): raise NotImplementedError + class ICalWriter(ReportWriter): """a lame ical writer, could not be bothered with finding a library""" def __init__(self, path): @@ -118,18 +120,21 @@ def _write_fact(self, fact): if fact.category == _("Unsorted"): fact.category = None - self.file.write("""BEGIN:VEVENT -CATEGORIES:%(category)s -DTSTART:%(start_time)s -DTEND:%(end_time)s -SUMMARY:%(activity)s -DESCRIPTION:%(description)s -END:VEVENT -""" % dict(fact)) + event_str = f"""\ + BEGIN:VEVENT + CATEGORIES:{fact.category} + DTSTART:{fact.start_time} + DTEND:{fact.end_time} + SUMMARY:{fact.activity} + DESCRIPTION:{fact.description} + END:VEVENT + """ + self.file.write(dedent(event_str)) def _finish(self, facts): self.file.write("END:VCALENDAR\n") + class TSVWriter(ReportWriter): def __init__(self, path): ReportWriter.__init__(self, path) From 7dfdd6d369fa6cf10b722e6313a019c220c883b3 Mon Sep 17 00:00:00 2001 From: ederag Date: Sat, 12 Jan 2019 18:09:12 +0100 Subject: [PATCH 7/7] fix xml export setAttribute expects strings, otherwise File "/usr/lib64/python3.6/xml/dom/minidom.py", line 305, in _write_data data = data.replace("&", "&").replace("<", "<"). \ TypeError: an integer is required (got type str) --- src/hamster/reports.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hamster/reports.py b/src/hamster/reports.py index cf482b309..a29f8511f 100644 --- a/src/hamster/reports.py +++ b/src/hamster/reports.py @@ -176,8 +176,8 @@ def __init__(self, path): def _write_fact(self, fact): activity = self.doc.createElement("activity") activity.setAttribute("name", fact.activity) - activity.setAttribute("start_time", fact.start_time) - activity.setAttribute("end_time", fact.end_time) + activity.setAttribute("start_time", str(fact.start_time)) + activity.setAttribute("end_time", str(fact.end_time)) activity.setAttribute("duration_minutes", str(stuff.duration_minutes(fact.delta))) activity.setAttribute("category", fact.category) activity.setAttribute("description", fact.description)