WebHost: update trackers only if they're visible. ()

This commit is contained in:
Fabian Dill 2024-06-01 17:07:58 +02:00 committed by GitHub
parent 13bc121c27
commit da33d1576a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 122 additions and 69 deletions
WebHostLib

View File

@ -27,7 +27,7 @@ const adjustTableHeight = () => {
* @returns {string} * @returns {string}
*/ */
const secondsToHours = (seconds) => { const secondsToHours = (seconds) => {
let hours = Math.floor(seconds / 3600); let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds - (hours * 3600)) / 60).toString().padStart(2, '0'); let minutes = Math.floor((seconds - (hours * 3600)) / 60).toString().padStart(2, '0');
return `${hours}:${minutes}`; return `${hours}:${minutes}`;
}; };
@ -38,18 +38,18 @@ window.addEventListener('load', () => {
info: false, info: false,
dom: "t", dom: "t",
stateSave: true, stateSave: true,
stateSaveCallback: function(settings, data) { stateSaveCallback: function (settings, data) {
delete data.search; delete data.search;
localStorage.setItem(`DataTables_${settings.sInstance}_/tracker`, JSON.stringify(data)); localStorage.setItem(`DataTables_${settings.sInstance}_/tracker`, JSON.stringify(data));
}, },
stateLoadCallback: function(settings) { stateLoadCallback: function (settings) {
return JSON.parse(localStorage.getItem(`DataTables_${settings.sInstance}_/tracker`)); return JSON.parse(localStorage.getItem(`DataTables_${settings.sInstance}_/tracker`));
}, },
footerCallback: function(tfoot, data, start, end, display) { footerCallback: function (tfoot, data, start, end, display) {
if (tfoot) { if (tfoot) {
const activityData = this.api().column('lastActivity:name').data().toArray().filter(x => !isNaN(x)); const activityData = this.api().column('lastActivity:name').data().toArray().filter(x => !isNaN(x));
Array.from(tfoot?.children).find(td => td.classList.contains('last-activity')).innerText = Array.from(tfoot?.children).find(td => td.classList.contains('last-activity')).innerText =
(activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None'; (activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None';
} }
}, },
columnDefs: [ columnDefs: [
@ -123,49 +123,64 @@ window.addEventListener('load', () => {
event.preventDefault(); event.preventDefault();
} }
}); });
const tracker = document.getElementById('tracker-wrapper').getAttribute('data-tracker'); const target_second = parseInt(document.getElementById('tracker-wrapper').getAttribute('data-second')) + 3;
const target_second = document.getElementById('tracker-wrapper').getAttribute('data-second') + 3; console.log("Target second of refresh: " + target_second);
function getSleepTimeSeconds(){ function getSleepTimeSeconds() {
// -40 % 60 is -40, which is absolutely wrong and should burn // -40 % 60 is -40, which is absolutely wrong and should burn
var sleepSeconds = (((target_second - new Date().getSeconds()) % 60) + 60) % 60; var sleepSeconds = (((target_second - new Date().getSeconds()) % 60) + 60) % 60;
return sleepSeconds || 60; return sleepSeconds || 60;
} }
let update_on_view = false;
const update = () => { const update = () => {
const target = $("<div></div>"); if (document.hidden) {
console.log("Updating Tracker..."); console.log("Document reporting as not visible, not updating Tracker...");
target.load(location.href, function (response, status) { update_on_view = true;
if (status === "success") { } else {
target.find(".table").each(function (i, new_table) { update_on_view = false;
const new_trs = $(new_table).find("tbody>tr"); const target = $("<div></div>");
const footer_tr = $(new_table).find("tfoot>tr"); console.log("Updating Tracker...");
const old_table = tables.eq(i); target.load(location.href, function (response, status) {
const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop(); if (status === "success") {
const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft(); target.find(".table").each(function (i, new_table) {
old_table.clear(); const new_trs = $(new_table).find("tbody>tr");
if (footer_tr.length) { const footer_tr = $(new_table).find("tfoot>tr");
$(old_table.table).find("tfoot").html(footer_tr); const old_table = tables.eq(i);
} const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop();
old_table.rows.add(new_trs); const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft();
old_table.draw(); old_table.clear();
$(old_table.settings()[0].nScrollBody).scrollTop(topscroll); if (footer_tr.length) {
$(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll); $(old_table.table).find("tfoot").html(footer_tr);
}); }
$("#multi-stream-link").replaceWith(target.find("#multi-stream-link")); old_table.rows.add(new_trs);
} else { old_table.draw();
console.log("Failed to connect to Server, in order to update Table Data."); $(old_table.settings()[0].nScrollBody).scrollTop(topscroll);
console.log(response); $(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll);
} });
}) $("#multi-stream-link").replaceWith(target.find("#multi-stream-link"));
setTimeout(update, getSleepTimeSeconds()*1000); } else {
console.log("Failed to connect to Server, in order to update Table Data.");
console.log(response);
}
})
}
updater = setTimeout(update, getSleepTimeSeconds() * 1000);
} }
setTimeout(update, getSleepTimeSeconds()*1000); let updater = setTimeout(update, getSleepTimeSeconds() * 1000);
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
adjustTableHeight(); adjustTableHeight();
tables.draw(); tables.draw();
}); });
window.addEventListener('visibilitychange', () => {
if (!document.hidden && update_on_view) {
console.log("Page became visible, tracker should be refreshed.");
clearTimeout(updater);
update();
}
});
adjustTableHeight(); adjustTableHeight();
}); });

View File

@ -10,7 +10,7 @@
{% include "header/dirtHeader.html" %} {% include "header/dirtHeader.html" %}
{% include "multitrackerNavigation.html" %} {% include "multitrackerNavigation.html" %}
<div id="tracker-wrapper" data-tracker="{{ room.tracker | suuid }}"> <div id="tracker-wrapper" data-tracker="{{ room.tracker | suuid }}" data-second="{{ saving_second }}">
<div id="tracker-header-bar"> <div id="tracker-header-bar">
<input placeholder="Search" id="search" /> <input placeholder="Search" id="search" />

View File

@ -3,8 +3,9 @@ import collections
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, NamedTuple, Counter from typing import Any, Callable, Dict, List, Optional, Set, Tuple, NamedTuple, Counter
from uuid import UUID from uuid import UUID
from email.utils import parsedate_to_datetime
from flask import render_template from flask import render_template, make_response, Response, request
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from MultiServer import Context, get_saving_second from MultiServer import Context, get_saving_second
@ -297,46 +298,41 @@ class TrackerData:
return self._multidata.get("spheres", []) return self._multidata.get("spheres", [])
def _process_if_request_valid(incoming_request, room: Optional[Room]) -> Optional[Response]:
if not room:
abort(404)
if_modified = incoming_request.headers.get("If-Modified-Since", None)
if if_modified:
if_modified = parsedate_to_datetime(if_modified)
# if_modified has less precision than last_activity, so we bring them to same precision
if if_modified >= room.last_activity.replace(microsecond=0):
return make_response("", 304)
@app.route("/tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>") @app.route("/tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>")
def get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool = False) -> str: def get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool = False) -> Response:
key = f"{tracker}_{tracked_team}_{tracked_player}_{generic}" key = f"{tracker}_{tracked_team}_{tracked_player}_{generic}"
tracker_page = cache.get(key) response: Optional[Response] = cache.get(key)
if tracker_page: if response:
return tracker_page return response
timeout, tracker_page = get_timeout_and_tracker(tracker, tracked_team, tracked_player, generic)
cache.set(key, tracker_page, timeout)
return tracker_page
@app.route("/generic_tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>")
def get_generic_game_tracker(tracker: UUID, tracked_team: int, tracked_player: int) -> str:
return get_player_tracker(tracker, tracked_team, tracked_player, True)
@app.route("/tracker/<suuid:tracker>", defaults={"game": "Generic"})
@app.route("/tracker/<suuid:tracker>/<game>")
@cache.memoize(timeout=TRACKER_CACHE_TIMEOUT_IN_SECONDS)
def get_multiworld_tracker(tracker: UUID, game: str):
# Room must exist. # Room must exist.
room = Room.get(tracker=tracker) room = Room.get(tracker=tracker)
if not room:
abort(404)
tracker_data = TrackerData(room) response = _process_if_request_valid(request, room)
enabled_trackers = list(get_enabled_multiworld_trackers(room).keys()) if response:
if game not in _multiworld_trackers: return response
return render_generic_multiworld_tracker(tracker_data, enabled_trackers)
return _multiworld_trackers[game](tracker_data, enabled_trackers) timeout, last_modified, tracker_page = get_timeout_and_player_tracker(room, tracked_team, tracked_player, generic)
response = make_response(tracker_page)
response.last_modified = last_modified
cache.set(key, response, timeout)
return response
def get_timeout_and_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool) -> Tuple[int, str]: def get_timeout_and_player_tracker(room: Room, tracked_team: int, tracked_player: int, generic: bool)\
# Room must exist. -> Tuple[int, datetime.datetime, str]:
room = Room.get(tracker=tracker)
if not room:
abort(404)
tracker_data = TrackerData(room) tracker_data = TrackerData(room)
# Load and render the game-specific player tracker, or fallback to generic tracker if none exists. # Load and render the game-specific player tracker, or fallback to generic tracker if none exists.
@ -346,7 +342,48 @@ def get_timeout_and_tracker(tracker: UUID, tracked_team: int, tracked_player: in
else: else:
tracker = render_generic_tracker(tracker_data, tracked_team, tracked_player) tracker = render_generic_tracker(tracker_data, tracked_team, tracked_player)
return (tracker_data.get_room_saving_second() - datetime.datetime.now().second) % 60 or 60, tracker return ((tracker_data.get_room_saving_second() - datetime.datetime.now().second)
% TRACKER_CACHE_TIMEOUT_IN_SECONDS or TRACKER_CACHE_TIMEOUT_IN_SECONDS, room.last_activity, tracker)
@app.route("/generic_tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>")
def get_generic_game_tracker(tracker: UUID, tracked_team: int, tracked_player: int) -> Response:
return get_player_tracker(tracker, tracked_team, tracked_player, True)
@app.route("/tracker/<suuid:tracker>", defaults={"game": "Generic"})
@app.route("/tracker/<suuid:tracker>/<game>")
def get_multiworld_tracker(tracker: UUID, game: str) -> Response:
key = f"{tracker}_{game}"
response: Optional[Response] = cache.get(key)
if response:
return response
# Room must exist.
room = Room.get(tracker=tracker)
response = _process_if_request_valid(request, room)
if response:
return response
timeout, last_modified, tracker_page = get_timeout_and_multiworld_tracker(room, game)
response = make_response(tracker_page)
response.last_modified = last_modified
cache.set(key, response, timeout)
return response
def get_timeout_and_multiworld_tracker(room: Room, game: str)\
-> Tuple[int, datetime.datetime, str]:
tracker_data = TrackerData(room)
enabled_trackers = list(get_enabled_multiworld_trackers(room).keys())
if game in _multiworld_trackers:
tracker = _multiworld_trackers[game](tracker_data, enabled_trackers)
else:
tracker = render_generic_multiworld_tracker(tracker_data, enabled_trackers)
return ((tracker_data.get_room_saving_second() - datetime.datetime.now().second)
% TRACKER_CACHE_TIMEOUT_IN_SECONDS or TRACKER_CACHE_TIMEOUT_IN_SECONDS, room.last_activity, tracker)
def get_enabled_multiworld_trackers(room: Room) -> Dict[str, Callable]: def get_enabled_multiworld_trackers(room: Room) -> Dict[str, Callable]:
@ -416,6 +453,7 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
videos=tracker_data.get_room_videos(), videos=tracker_data.get_room_videos(),
item_id_to_name=tracker_data.item_id_to_name, item_id_to_name=tracker_data.item_id_to_name,
location_id_to_name=tracker_data.location_id_to_name, location_id_to_name=tracker_data.location_id_to_name,
saving_second=tracker_data.get_room_saving_second(),
) )