From 03658b6896548ce17b869bf6a4864b2376d3ed5e Mon Sep 17 00:00:00 2001 From: perast Date: Tue, 17 Nov 2020 16:35:11 +0100 Subject: [PATCH 1/5] Some fixed for launcher views Able to save login --- cdn/static/web/launcher/layout.html | 10 +++++--- cdn/static/web/launcher/login_form.html | 34 ++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/cdn/static/web/launcher/layout.html b/cdn/static/web/launcher/layout.html index c087606..836a9e3 100644 --- a/cdn/static/web/launcher/layout.html +++ b/cdn/static/web/launcher/layout.html @@ -33,10 +33,12 @@ {% block content %}{% endblock %} {% block scripts %}{% endblock %} diff --git a/cdn/static/web/launcher/login_form.html b/cdn/static/web/launcher/login_form.html index a50649d..b94df03 100644 --- a/cdn/static/web/launcher/login_form.html +++ b/cdn/static/web/launcher/login_form.html @@ -20,7 +20,17 @@
- +
+ + +
+
+
+
+
+
@@ -38,3 +48,25 @@ {% endblock %} +{% block scripts %} + +{% endblock %} From da62faae74e2f1410bf52d511badaf54ad7bc8f9 Mon Sep 17 00:00:00 2001 From: perast Date: Tue, 17 Nov 2020 16:49:10 +0100 Subject: [PATCH 2/5] Forget login when unchecking checkbox --- cdn/static/web/launcher/login_form.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cdn/static/web/launcher/login_form.html b/cdn/static/web/launcher/login_form.html index b94df03..dcfe364 100644 --- a/cdn/static/web/launcher/login_form.html +++ b/cdn/static/web/launcher/login_form.html @@ -58,6 +58,10 @@ localStorage.setItem('username', username.value); localStorage.setItem('password', password.value); } + else if(!checkbox.checked) { + localStorage.removeItem('username') + localStorage.removeItem('password'); + } } var usernameVal = localStorage.getItem('username'); From 6846b57c952c033c148ac2b3a48d56d18c5cf586 Mon Sep 17 00:00:00 2001 From: perast Date: Wed, 18 Nov 2020 09:53:51 +0100 Subject: [PATCH 3/5] Changed all database calls to db.session --- zwift_offline.py | 96 ++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/zwift_offline.py b/zwift_offline.py index ab0c730..096aa36 100644 --- a/zwift_offline.py +++ b/zwift_offline.py @@ -463,7 +463,8 @@ def insert_protobuf_into_db(table_name, msg): columns = ', '.join(list(msg_dict.keys())) placeholders = ':'+', :'.join(list(msg_dict.keys())) query = 'INSERT INTO %s (%s) VALUES (%s)' % (table_name, columns, placeholders) - db.engine.execute(query, msg_dict) + db.session.execute(query, msg_dict) + db.session.commit() # XXX: can't be used to 'nullify' a column value @@ -480,7 +481,8 @@ def update_protobuf_in_db(table_name, msg, id): placeholders = ':'+', :'.join(list(msg_dict.keys())) setters = ', '.join('{}=:{}'.format(key, key) for key in msg_dict) query = 'UPDATE %s SET %s WHERE id=%s' % (table_name, setters, id) - db.engine.execute(query, msg_dict) + db.session.execute(query, msg_dict) + db.session.commit() def row_to_protobuf(row, msg, exclude_fields=[]): @@ -503,7 +505,7 @@ def get_id(table_name): # I think activity id is actually only uint32. On the off chance it's # int32, stick with 31 bits. ident = int(random.getrandbits(31)) - row = db.engine.execute("SELECT id FROM %s WHERE id = %s" % (table_name, ident)).first() + row = db.session.execute(sqlalchemy.text("SELECT id FROM %s WHERE id = %s" % (table_name, ident))).first() if not row: break return ident @@ -647,9 +649,10 @@ def api_profiles_me(): # However, without it, anyone "upgrading" to multiplayer mode will lose their existing data. # TODO: need a warning in README that switching to multiplayer mode and back to single player will lose your existing data. if profile.id != profile_id: - db.engine.execute('UPDATE activity SET player_id = ? WHERE player_id = ?', (str(profile_id), str(profile.id))) - db.engine.execute('UPDATE goal SET player_id = ? WHERE player_id = ?', (str(profile_id), str(profile.id))) - db.engine.execute('UPDATE segment_result SET player_id = ? WHERE player_id = ?', (str(profile_id), str(profile.id))) + db.session.execute(sqlalchemy.text('UPDATE activity SET player_id = %s WHERE player_id = %s' % (profile_id, profile.id))) + db.session.execute(sqlalchemy.text('UPDATE goal SET player_id = %s WHERE player_id = %s' % (profile_id, profile.id))) + db.session.execute(sqlalchemy.text('UPDATE segment_result SET player_id = %s WHERE player_id = %s' % (profile_id, profile.id))) + db.session.commit() profile.id = profile_id elif current_user.player_id != profile.id: # Update AnonUser's player_id to match @@ -700,7 +703,7 @@ def api_profiles_activities(player_id): # request.method == 'GET' activities = activity_pb2.Activities() # Select every column except 'fit' - despite being a blob python 3 treats it like a utf-8 string and tries to decode it - rows = db.engine.execute("SELECT id, player_id, f3, name, f5, f6, start_date, end_date, distance, avg_heart_rate, max_heart_rate, avg_watts, max_watts, avg_cadence, max_cadence, avg_speed, max_speed, calories, total_elevation, strava_upload_id, strava_activity_id, f23, fit_filename, f29, date FROM activity WHERE player_id = ?", (str(player_id),)) + rows = db.session.execute(sqlalchemy.text("SELECT id, player_id, f3, name, f5, f6, start_date, end_date, distance, avg_heart_rate, max_heart_rate, avg_watts, max_watts, avg_cadence, max_cadence, avg_speed, max_speed, calories, total_elevation, strava_upload_id, strava_activity_id, f23, fit_filename, f29, date FROM activity WHERE player_id = %s" % str(player_id))) for row in rows: activity = activities.activities.add() row_to_protobuf(row, activity, exclude_fields=['fit']) @@ -710,7 +713,8 @@ def api_profiles_activities(player_id): #a.avg_watts == 0 and a.calories == 0 and a.distance == 0 and a.max_cadence == 0 and #a.max_heart_rate == 0 and a.max_speed == 0 and a.max_watts == 0): if a.distance == 0: - db.engine.execute("DELETE FROM activity WHERE id = %s" % a.id) + db.session.execute(sqlalchemy.text("DELETE FROM activity WHERE id = %s" % a.id)) + db.session.commit() activities.activities.remove(a) return activities.SerializeToString(), 200 @@ -910,12 +914,11 @@ def fill_in_goal_progress(goal, player_id): else: # monthly first_dt, last_dt = get_month_range(now) if goal.type == 0: # distance - distance = db.engine.execute("""SELECT SUM(distance) FROM activity - WHERE player_id = ? - AND strftime('%s', start_date) >= strftime('%s', ?) - AND strftime('%s', start_date) <= strftime('%s', ?) - AND end_date IS NOT NULL""", - (str(player_id), first_dt, last_dt)).first()[0] + distance = db.session.execute(sqlalchemy.text("""SELECT SUM(distance) FROM activity + WHERE player_id = %s + AND strftime('%s', start_date) >= strftime('%s', '%s') + AND strftime('%s', start_date) <= strftime('%s', '%s') + AND end_date IS NOT NULL""" % (player_id, '%s', '%s', first_dt, '%s', '%s', last_dt))).first()[0] if distance: goal.actual_distance = distance goal.actual_duration = distance @@ -924,13 +927,12 @@ def fill_in_goal_progress(goal, player_id): goal.actual_duration = 0.0 else: # duration - duration = db.engine.execute("""SELECT SUM(julianday(end_date) - julianday(start_date)) + duration = db.session.execute(sqlalchemy.text("""SELECT SUM(julianday(end_date) - julianday(start_date)) FROM activity - WHERE player_id = ? - AND strftime('%s', start_date) >= strftime('%s', ?) - AND strftime('%s', start_date) <= strftime('%s', ?) - AND end_date IS NOT NULL""", - (str(player_id), first_dt, last_dt)).first()[0] + WHERE player_id = %s + AND strftime('%s', start_date) >= strftime('%s', '%s') + AND strftime('%s', start_date) <= strftime('%s', '%s') + AND end_date IS NOT NULL""" % (player_id, '%s', '%s', first_dt, '%s', '%s', last_dt))).first()[0] if duration: goal.actual_duration = duration*1440 # convert from days to minutes goal.actual_distance = duration*1440 @@ -968,7 +970,7 @@ def api_profiles_goals(player_id): # request.method == 'GET' goals = goal_pb2.Goals() - rows = db.engine.execute("SELECT * FROM goal WHERE player_id = ?", (str(player_id),)) + rows = db.session.execute(sqlalchemy.text("SELECT * FROM goal WHERE player_id = %s" % player_id)) for row in rows: goal = goals.goals.add() row_to_protobuf(row, goal) @@ -989,7 +991,8 @@ def api_profiles_goals_id(player_id, goal_id): if player_id != current_user.player_id: return '', 401 goal_id = int(goal_id) & 0xffffffffffffffff - db.engine.execute("DELETE FROM goal WHERE id = ?", (str(goal_id),)) + db.session.execute(sqlalchemy.text("DELETE FROM goal WHERE id = %s" % goal_id)) + db.session.commit() return '', 200 @@ -1204,30 +1207,25 @@ def relay_periodic_info(): def add_segment_results(segment_id, player_id, only_best, from_date, to_date, results): - where_stmt = "WHERE segment_id = ?" - where_args = [str(segment_id)] + where_stmt = ("WHERE segment_id = %s" % segment_id) rows = None if player_id: - where_stmt += " AND player_id = ?" - where_args.append(player_id) + where_stmt += (" AND player_id = %s" % player_id) if from_date: - where_stmt += " AND strftime('%s', finish_time_str) > strftime('%s', ?)" - where_args.append(from_date) + where_stmt += (" AND strftime('%s', finish_time_str) > strftime('%s', '%s')" % ('%s', '%s', from_date)) if to_date: - where_stmt += " AND strftime('%s', finish_time_str) < strftime('%s', ?)" - where_args.append(to_date) + where_stmt += (" AND strftime('%s', finish_time_str) < strftime('%s', '%s')" % ('%s', '%s', to_date)) if only_best: - where_stmt += " AND world_time > ?" #Only include results from max 1 hour ago - where_args.append(world_time()-(60*60*1000)) - rows = db.engine.execute("""SELECT s1.* FROM segment_result s1 + where_stmt += (" AND world_time > '%s'" % (world_time()-(60*60*1000))) + rows = db.session.execute(sqlalchemy.text("""SELECT s1.* FROM segment_result s1 JOIN (SELECT s.player_id, MIN(Cast(s.elapsed_ms AS INTEGER)) AS min_time FROM segment_result s %s GROUP BY s.player_id) s2 ON s2.player_id = s1.player_id AND s2.min_time = CAST(s1.elapsed_ms AS INTEGER) GROUP BY s1.player_id, s1.elapsed_ms ORDER BY CAST(s1.elapsed_ms AS INTEGER) - LIMIT 1000""" % where_stmt, where_args) + LIMIT 1000""" % where_stmt)) else: - rows = db.engine.execute("SELECT * FROM segment_result %s" % where_stmt, where_args) + rows = db.session.execute(sqlalchemy.text("SELECT * FROM segment_result %s" % where_stmt)) for row in rows: result = results.segment_results.add() row_to_protobuf(row, result, ['f3', 'f4', 'segment_id', 'event_subgroup_id', 'finish_time_str', 'f14', 'f17', 'f18']) @@ -1294,9 +1292,7 @@ def relay_worlds_leave(world_id): @app.teardown_request def teardown_request(exception): - db.close_all_sessions() db.session.close() - db.engine.dispose() if exception != None: print('Exception: %s' % exception) @@ -1307,14 +1303,16 @@ def init_database(): with open(DATABASE_INIT_SQL, 'r') as f: sql_statements = f.read().split('\n\n') for sql_statement in sql_statements: - db.engine.execute(sql_statement) - db.engine.execute('INSERT INTO version VALUES (?)', (DATABASE_CUR_VER,)) + db.session.execute(sql_statement) + db.session.commit() + db.session.execute('INSERT INTO version VALUES (:ver)', {'ver': DATABASE_CUR_VER}) + db.session.commit() return # Migrate database if necessary if not os.access(DATABASE_PATH, os.W_OK): logging.error("zwift-offline.db is not writable. Unable to upgrade database!") return - version = db.engine.execute('SELECT version FROM version').first()[0] + version = db.session.execute('SELECT version FROM version').first()[0] if version == DATABASE_CUR_VER: return # Database needs to be upgraded, try to back it up first @@ -1329,17 +1327,19 @@ def init_database(): if version < 1: # Adjust old world_time values in segment results to new rough estimate of Zwift's logging.info("Upgrading zwift-offline.db to version 2") - db.engine.execute('UPDATE segment_result SET world_time = world_time-1414016075000') - db.engine.execute('UPDATE version SET version = 2') + db.session.execute('UPDATE segment_result SET world_time = world_time-1414016075000') + db.session.execute('UPDATE version SET version = 2') + db.session.commit() if version == 1: logging.info("Upgrading zwift-offline.db to version 2") - db.engine.execute('UPDATE segment_result SET world_time = cast(world_time/64.4131403573055-1414016075 as int)*1000') - db.engine.execute('UPDATE version SET version = 2') + db.session.execute('UPDATE segment_result SET world_time = cast(world_time/64.4131403573055-1414016075 as int)*1000') + db.session.execute('UPDATE version SET version = 2') + db.session.commit() def check_columns(): - rows = db.engine.execute(sqlalchemy.text("PRAGMA table_info(user)")) + rows = db.session.execute(sqlalchemy.text("PRAGMA table_info(user)")) should_have_columns = User.metadata.tables['user'].columns current_columns = list() for row in rows: @@ -1356,7 +1356,8 @@ def check_columns(): defaulttext = "" else: defaulttext = " DEFAULT %s" % column.default.arg - db.engine.execute(sqlalchemy.text("ALTER TABLE user ADD %s %s %s%s;" % (column.name, str(column.type), nulltext, defaulttext))) + db.session.execute(sqlalchemy.text("ALTER TABLE user ADD %s %s %s%s;" % (column.name, str(column.type), nulltext, defaulttext))) + db.session.commit() def send_server_back_online_message(): @@ -1368,7 +1369,6 @@ def send_server_back_online_message(): def before_first_request(): init_database() db.create_all(app=app) - db.session.commit() check_columns() send_message_thread = threading.Thread(target=send_server_back_online_message) send_message_thread.start() @@ -1512,8 +1512,6 @@ def run_standalone(passed_online, passed_global_pace_partners, passed_global_bot if not MULTIPLAYER: login_manager.anonymous_user = AnonUser login_manager.init_app(app) - db.close_all_sessions() - db.engine.dispose() @login_manager.user_loader def load_user(uid): From bead4d3a992b1b9e8fbd633a1c697a7dbc356ea8 Mon Sep 17 00:00:00 2001 From: perast Date: Wed, 18 Nov 2020 11:03:32 +0100 Subject: [PATCH 4/5] Fixed issue with deleting activities --- zwift_offline.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/zwift_offline.py b/zwift_offline.py index 096aa36..267d895 100644 --- a/zwift_offline.py +++ b/zwift_offline.py @@ -704,19 +704,18 @@ def api_profiles_activities(player_id): activities = activity_pb2.Activities() # Select every column except 'fit' - despite being a blob python 3 treats it like a utf-8 string and tries to decode it rows = db.session.execute(sqlalchemy.text("SELECT id, player_id, f3, name, f5, f6, start_date, end_date, distance, avg_heart_rate, max_heart_rate, avg_watts, max_watts, avg_cadence, max_cadence, avg_speed, max_speed, calories, total_elevation, strava_upload_id, strava_activity_id, f23, fit_filename, f29, date FROM activity WHERE player_id = %s" % str(player_id))) + should_remove = list() for row in rows: activity = activities.activities.add() row_to_protobuf(row, activity, exclude_fields=['fit']) a = activity - #If all values for saved activity is 0, remove it from DB and results - #if (a.avg_cadence == 0 and a.avg_heart_rate == 0 and a.avg_speed == 0 and - #a.avg_watts == 0 and a.calories == 0 and a.distance == 0 and a.max_cadence == 0 and - #a.max_heart_rate == 0 and a.max_speed == 0 and a.max_watts == 0): - if a.distance == 0: - db.session.execute(sqlalchemy.text("DELETE FROM activity WHERE id = %s" % a.id)) - db.session.commit() - activities.activities.remove(a) - + #Remove activities with less than 100m distance + if a.distance < 100: + should_remove.append(a) + for a in should_remove: + db.session.execute(sqlalchemy.text("DELETE FROM activity WHERE id = %s" % a.id)) + db.session.commit() + activities.activities.remove(a) return activities.SerializeToString(), 200 @@ -913,12 +912,14 @@ def fill_in_goal_progress(goal, player_id): first_dt, last_dt = get_week_range(now) else: # monthly first_dt, last_dt = get_month_range(now) + + common_sql = ("""FROM activity + WHERE player_id = %s + AND strftime('%s', start_date) >= strftime('%s', '%s') + AND strftime('%s', start_date) <= strftime('%s', '%s')""" % + (player_id, '%s', '%s', first_dt, '%s', '%s', last_dt)) if goal.type == 0: # distance - distance = db.session.execute(sqlalchemy.text("""SELECT SUM(distance) FROM activity - WHERE player_id = %s - AND strftime('%s', start_date) >= strftime('%s', '%s') - AND strftime('%s', start_date) <= strftime('%s', '%s') - AND end_date IS NOT NULL""" % (player_id, '%s', '%s', first_dt, '%s', '%s', last_dt))).first()[0] + distance = db.session.execute(sqlalchemy.text('SELECT SUM(distance) %s' % common_sql)).first()[0] if distance: goal.actual_distance = distance goal.actual_duration = distance @@ -927,12 +928,7 @@ def fill_in_goal_progress(goal, player_id): goal.actual_duration = 0.0 else: # duration - duration = db.session.execute(sqlalchemy.text("""SELECT SUM(julianday(end_date) - julianday(start_date)) - FROM activity - WHERE player_id = %s - AND strftime('%s', start_date) >= strftime('%s', '%s') - AND strftime('%s', start_date) <= strftime('%s', '%s') - AND end_date IS NOT NULL""" % (player_id, '%s', '%s', first_dt, '%s', '%s', last_dt))).first()[0] + duration = db.session.execute(sqlalchemy.text('SELECT SUM(julianday(end_date) - julianday(start_date)) %s' % common_sql)).first()[0] if duration: goal.actual_duration = duration*1440 # convert from days to minutes goal.actual_distance = duration*1440 @@ -1370,6 +1366,7 @@ def before_first_request(): init_database() db.create_all(app=app) check_columns() + db.session.close() send_message_thread = threading.Thread(target=send_server_back_online_message) send_message_thread.start() From 9352be11ea4a8205450a48eebfdaae3f76e9a64c Mon Sep 17 00:00:00 2001 From: perast Date: Wed, 18 Nov 2020 11:13:29 +0100 Subject: [PATCH 5/5] Fixed updating goals as well --- zwift_offline.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zwift_offline.py b/zwift_offline.py index 267d895..b549b05 100644 --- a/zwift_offline.py +++ b/zwift_offline.py @@ -967,15 +967,18 @@ def api_profiles_goals(player_id): # request.method == 'GET' goals = goal_pb2.Goals() rows = db.session.execute(sqlalchemy.text("SELECT * FROM goal WHERE player_id = %s" % player_id)) + need_update = list() for row in rows: goal = goals.goals.add() row_to_protobuf(row, goal) end_dt = datetime.datetime.fromtimestamp(goal.period_end_date / 1000) now = get_utc_date_time() if end_dt < now: - set_goal_end_date(goal, now) - update_protobuf_in_db('goal', goal, goal.id) + need_update.append(goal) fill_in_goal_progress(goal, player_id) + for goal in need_update: + set_goal_end_date(goal, now) + update_protobuf_in_db('goal', goal, goal.id) return goals.SerializeToString(), 200