From 23ad78d5922559ff7cd10c39c2c2d3f5dad435ee Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Sun, 25 Aug 2024 02:20:47 +1000 Subject: [PATCH] Record in database when end time is "--" We need this so that reverse sync reproduces these values correctly. To handle this in the database, we have a composite type (dashed: boolean, value: timestamp). Value is always valid and is equivalent to the old timestamp column, but must be equal to start_time if dashed is true. The only place we directly reference this column outside sheetsync is thrimshim, where we always consider the value only. --- common/common/database.py | 1 + postgres/schema.sql | 14 +++++++++++++- sheetsync/sheetsync/sheets.py | 15 ++++++++++----- sheetsync/sheetsync/streamlog.py | 15 ++++++++++++++- thrimshim/thrimshim/main.py | 6 ++++++ 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/common/common/database.py b/common/common/database.py index deb0bd1..5eae526 100644 --- a/common/common/database.py +++ b/common/common/database.py @@ -17,6 +17,7 @@ from psycogreen.gevent import patch_psycopg COMPOSITE_TYPES = [ "video_range", "video_transition", + "end_time", ] COLUMN_CASTS = { "video_ranges": "video_range[]", diff --git a/postgres/schema.sql b/postgres/schema.sql index e08b113..3bf2612 100644 --- a/postgres/schema.sql +++ b/postgres/schema.sql @@ -26,12 +26,24 @@ CREATE TYPE thumbnail_mode as ENUM ( 'CUSTOM' ); +-- The end time for an event can be unset, "--" or a timestamp. +-- If dashed is true, value should be the same as start time (which may be NULL if start time is unset). +-- Otherwise value is the value (which may be NULL if end time is unset). +-- dashed should be non-NULL. +CREATE TYPE end_time AS ( + dashed BOOLEAN, + value TIMESTAMP +) + CREATE TABLE events ( id TEXT PRIMARY KEY, sheet_name TEXT NOT NULL, event_start TIMESTAMP, - event_end TIMESTAMP, + event_end end_time DEFAULT ROW(false, NULL) CHECK ( + event_end.dashed IS NOT NULL + AND (event_end.dashed != TRUE OR event_end.value IS NOT DISTINCT FROM event_start) + ), category TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', submitter_winner TEXT NOT NULL DEFAULT '', diff --git a/sheetsync/sheetsync/sheets.py b/sheetsync/sheetsync/sheets.py index 166a7d6..4ea2a60 100644 --- a/sheetsync/sheetsync/sheets.py +++ b/sheetsync/sheetsync/sheets.py @@ -288,7 +288,7 @@ class SheetsEventsMiddleware(SheetsMiddleware): } self.column_encode = { "event_start": self.encode_bustime, - "event_end": self.encode_bustime, + "event_end": lambda v: ("--" if v[0] else self.encode_bustime(v[1])), "poster_moment": ENCODE_CHECKMARK, "image_links": lambda v: " ".join(v), "tags": lambda v: ", ".join(v), @@ -336,9 +336,14 @@ class SheetsEventsMiddleware(SheetsMiddleware): + row_dict['tags'] ) - # As a special case, treat an end time of "--" as equal to the start time. - if row_dict["event_end"] == "--": - row_dict["event_end"] = row_dict["event_start"] + # As a special case, transform event_end into (dashed, value) form + # where value = event_start if dashed is true. + event_end = row_dict["event_end"] + row_dict["event_end"] = ( + (True, row_dict["event_start"]) + if event_end == "--" + else (False, event_end) + ) # Set edit link if marked for editing and start/end set. # This prevents accidents / clicking the wrong row and provides @@ -384,4 +389,4 @@ class SheetsArchiveMiddleware(SheetsEventsMiddleware): } def show_edit_url(self, row): - return row['event_start'] is not None and row['event_end'] is not None + return row['event_start'] is not None and row['event_end'][1] is not None diff --git a/sheetsync/sheetsync/streamlog.py b/sheetsync/sheetsync/streamlog.py index 42a7877..bc6f759 100644 --- a/sheetsync/sheetsync/streamlog.py +++ b/sheetsync/sheetsync/streamlog.py @@ -105,7 +105,11 @@ class StreamLogEventsMiddleware(Middleware): # Omitted columns act as the identity function. self.column_decode = { 'event_start': parse_utc_only, - 'event_end': lambda v: parse_utc_only(v["time"]) if v["type"] == "Time" else None, + 'event_end': lambda v: ( + parse_utc_only(v["time"]) if v["type"] == "Time" + else "--" if v["type"] == "NoTime" + else None + ), 'category': lambda v: v["name"], 'state': lambda v: v.upper() if v else None, 'error': lambda v: None if v == '' else v, @@ -161,6 +165,15 @@ class StreamLogEventsMiddleware(Middleware): # Tab name is sheet name output["sheet_name"] = row["tab"]["name"] if row["tab"] else "unknown" + # Transform event_end into (dashed, value) form + # where value = event_start if dashed is true. + event_end = output["event_end"] + output["event_end"] = ( + (True, output["event_start"]) + if event_end == "--" + else (False, event_end) + ) + # Implicit tags implicit_tags = [ output['category'], diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 0fbd897..27ef889 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -180,6 +180,8 @@ def get_row(ident): response['id'] = str(response['id']) if response["video_channel"] is None: response["video_channel"] = app.default_channel + # Unwrap end time (dashed, value) to just value + response["event_end"] = response["event_end"][1] title_header = "" if is_archive else app.title_header response["title_prefix"] = title_header response["title_max_length"] = MAX_TITLE_LENGTH - len(title_header) @@ -394,6 +396,9 @@ def update_row(ident, editor=None): # check whether row has been changed in the sheet since editing has begun changes = '' for column in sheet_columns: + if column == "event_end": + # convert (dashed, value) to value + old_row[column] = old_row[column][1] if isinstance(old_row[column], datetime.datetime): old_row[column] = old_row[column].isoformat() def normalize(value): @@ -408,6 +413,7 @@ def update_row(ident, editor=None): return 'Sheet columns have changed since editing has begun. Please review changes\n' + changes, 409 if new_row['state'] == 'MODIFIED': + missing = [] # Modifying published rows is more limited, we ignore all other fields. for column in set(modifiable_columns) & set(non_null_columns): if new_row.get(column) is None: