Release v2.10.3
This commit is contained in:
@@ -68,7 +68,7 @@ function registerRecurringDataSourceRefreshes(options) {
|
||||
category: TASK.category,
|
||||
intervalMs: normalizeIntervalMs(location.update_interval_value, location.update_interval_unit),
|
||||
metadata: { sourceType: 'weather-location', sourceId: Number(location.id), sourceName: location.name },
|
||||
run: function () { return refreshWeatherLocation(pool, common, location, null, options.notifyPlayerScreens); }
|
||||
run: function () { return refreshWeatherLocation(pool, common, location.id, null, options.notifyPlayerScreens); }
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Refresh data sources and notify only the screens whose rendered data changed.
|
||||
|
||||
async function getAffectedScreenSlugs(connection, common, slideMatchKey, sourceId) {
|
||||
const [slideRows] = await connection.query('SELECT id, content_json FROM c_slides WHERE content_json IS NOT NULL');
|
||||
const slideIds = [];
|
||||
@@ -106,6 +108,24 @@ function hasApiSourceChanged(apiSource, responseDetails) {
|
||||
return normalizeSnapshotValue(apiSource && apiSource.last_response_json) !== normalizeSnapshotValue(responseDetails && responseDetails.responseJson);
|
||||
}
|
||||
|
||||
function isDifferentHour(previousPulledAt, currentPulledAt) {
|
||||
const previous = new Date(previousPulledAt);
|
||||
const current = new Date(currentPulledAt);
|
||||
if (!Number.isFinite(previous.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return previous.getFullYear() !== current.getFullYear()
|
||||
|| previous.getMonth() !== current.getMonth()
|
||||
|| previous.getDate() !== current.getDate()
|
||||
|| previous.getHours() !== current.getHours();
|
||||
}
|
||||
|
||||
function hasWeatherLocationChanged(location, responseDetails, refreshedAt) {
|
||||
return normalizeSnapshotValue(location && location.last_response_json) !== normalizeSnapshotValue(responseDetails && responseDetails.responseJson)
|
||||
|| isDifferentHour(location && location.last_pulled_at, refreshedAt);
|
||||
}
|
||||
|
||||
async function refreshApiSource(pool, common, apiSourceOrId, actorId, notifyPlayerScreens) {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
@@ -169,16 +189,17 @@ async function refreshWeatherLocation(pool, common, weatherLocationOrId, actorId
|
||||
pullError = String(error && error.message ? error.message : 'Unable to load weather forecast.');
|
||||
}
|
||||
|
||||
const refreshedAt = new Date();
|
||||
await connection.beginTransaction();
|
||||
await connection.query(
|
||||
'UPDATE i_weather_locations SET last_pulled_at = ?, last_pull_error = ?, last_response_json = COALESCE(?, last_response_json), modified_by = ? WHERE id = ?',
|
||||
[new Date(), pullError || null, result ? result.responseJson : null, actorId, location.id]
|
||||
[refreshedAt, pullError || null, result ? result.responseJson : null, actorId, location.id]
|
||||
);
|
||||
await connection.commit();
|
||||
|
||||
if (pullError) {
|
||||
console.error('[data-source-refresh] Weather location refresh completed with an error for location ' + location.id + ': ' + pullError);
|
||||
} else if (typeof notifyAffectedScreens === 'function') {
|
||||
} else if (hasWeatherLocationChanged(location, result, refreshedAt) && typeof notifyPlayerScreens === 'function') {
|
||||
try {
|
||||
await notifyAffectedScreens(connection, common, notifyPlayerScreens, 'weather_location_id', location.id);
|
||||
} catch (notifyError) {
|
||||
|
||||
@@ -389,6 +389,9 @@ function createUploadSyncService(options) {
|
||||
}
|
||||
|
||||
const filePath = resolveUploadFilePath(uploadDir, uploadPath);
|
||||
if (String(uploadPath || '').startsWith('/media/player-cache/')) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await fs.promises.unlink(filePath);
|
||||
} catch (error) {
|
||||
@@ -432,10 +435,6 @@ function createUploadSyncService(options) {
|
||||
const nextRelativePath = relativeDir ? path.posix.join(relativeDir, entryName) : entryName;
|
||||
const nextAbsolutePath = path.join(currentDir, entryName);
|
||||
|
||||
if (!relativeDir && entryName === 'player-cache') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.isDirectory && entry.isDirectory()) {
|
||||
await walkDirectory(nextAbsolutePath, nextRelativePath);
|
||||
continue;
|
||||
@@ -445,7 +444,11 @@ function createUploadSyncService(options) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uploadPaths.push('/media/uploads/' + nextRelativePath.replace(/\\/g, '/'));
|
||||
const normalizedRelativePath = nextRelativePath.replace(/\\/g, '/');
|
||||
if (normalizedRelativePath.startsWith('player-cache/') && !normalizedRelativePath.startsWith('player-cache/remote-images/')) {
|
||||
continue;
|
||||
}
|
||||
uploadPaths.push((normalizedRelativePath.startsWith('player-cache/') ? '/media/' : '/media/uploads/') + normalizedRelativePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,6 +933,12 @@ function createUploadSyncService(options) {
|
||||
});
|
||||
});
|
||||
const fontLibraryOperations = collectFontLibrarySyncOperations(uploadDir);
|
||||
const generatedCachePaths = await collectUploadPathsFromDirectory(uploadDir);
|
||||
generatedCachePaths.filter(function (uploadPath) {
|
||||
return String(uploadPath || '').startsWith('/media/player-cache/remote-images/');
|
||||
}).forEach(function (uploadPath) {
|
||||
uploadRefs.add(uploadPath);
|
||||
});
|
||||
Array.from(uploadRefs).forEach(function (uploadPath) {
|
||||
queuePlayerUploadSync({
|
||||
type: 'put',
|
||||
@@ -957,8 +966,12 @@ function createUploadSyncService(options) {
|
||||
if (mode === 'playlist') {
|
||||
const operation = normalizePlaylistUploadSyncOperation(taskPayload.operation || taskPayload);
|
||||
|
||||
if (operation.nextUploadRefs.length) {
|
||||
await syncUploadRefsToPlayer(operation.nextUploadRefs, operation.localUploadDir, taskPayload.playerIdentifier, taskPayload.playerInternalBaseUrl);
|
||||
const generatedCachePaths = await collectUploadPathsFromDirectory(operation.localUploadDir);
|
||||
const nextUploadRefs = operation.nextUploadRefs.concat(generatedCachePaths.filter(function (uploadPath) {
|
||||
return String(uploadPath || '').startsWith('/media/player-cache/remote-images/');
|
||||
}));
|
||||
if (nextUploadRefs.length) {
|
||||
await syncUploadRefsToPlayer(nextUploadRefs, operation.localUploadDir, taskPayload.playerIdentifier, taskPayload.playerInternalBaseUrl);
|
||||
}
|
||||
|
||||
if (operation.previousUploadRefs.length) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
function createNotifyPlayerScreens(forwardPlayerCommand) {
|
||||
// Route screen refresh commands to the physical player hosting each screen.
|
||||
|
||||
function createNotifyPlayerScreens(forwardPlayerCommand, getScreenConnections, forwardPlayerCommandToBaseUrl, resolvePlayerBaseUrl) {
|
||||
if (typeof forwardPlayerCommand !== 'function') {
|
||||
throw new Error('createNotifyPlayerScreens requires a player command sender.');
|
||||
}
|
||||
@@ -12,12 +14,29 @@ function createNotifyPlayerScreens(forwardPlayerCommand) {
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
return Promise.allSettled(uniqueSlugs.map(function (slug) {
|
||||
return Promise.allSettled(uniqueSlugs.map(async function (slug) {
|
||||
// A screen may have connections on multiple players after a remote move or reconnect.
|
||||
if (typeof getScreenConnections === 'function' && typeof forwardPlayerCommandToBaseUrl === 'function') {
|
||||
const screenState = await getScreenConnections(slug);
|
||||
const connections = Array.isArray(screenState && screenState.connections) ? screenState.connections : [];
|
||||
const playerBaseUrls = Array.from(new Set(connections.map(function (connection) {
|
||||
return String(connection && connection.playerPublicBaseUrl || '').trim().replace(/\/$/, '');
|
||||
}).filter(Boolean)));
|
||||
if (playerBaseUrls.length) {
|
||||
const resolvedPlayerBaseUrls = typeof resolvePlayerBaseUrl === 'function'
|
||||
? await Promise.all(playerBaseUrls.map(function (playerBaseUrl) { return resolvePlayerBaseUrl(playerBaseUrl); }))
|
||||
: playerBaseUrls;
|
||||
return Promise.all(resolvedPlayerBaseUrls.filter(Boolean).map(function (playerBaseUrl) {
|
||||
return forwardPlayerCommandToBaseUrl(playerBaseUrl, slug, commandOrPayload || 'refresh');
|
||||
}));
|
||||
}
|
||||
}
|
||||
return forwardPlayerCommand(slug, commandOrPayload || 'refresh');
|
||||
})).then(function (results) {
|
||||
return results.filter(function (result) {
|
||||
return result.status === 'fulfilled';
|
||||
const successful = results.filter(function (result) {
|
||||
return result.status === 'fulfilled' && (!result.value || result.value.ok !== false);
|
||||
}).length;
|
||||
return successful;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Commands and synchronization actions forwarded from the web app to players.
|
||||
|
||||
const { createRequestAuthHeaders } = require('#src/request-auth');
|
||||
const { getConfiguredPlayerIdentifier } = require('#src/data/player-registry');
|
||||
|
||||
@@ -215,6 +217,31 @@ function createPlayerActionService(options) {
|
||||
return forwardPlayerCommandToBaseUrl(resolvedPlayerInternalBaseUrl, slug, commandOrPayload, connectionId);
|
||||
}
|
||||
|
||||
async function resolvePlayerBaseUrlForPublicUrl(playerPublicBaseUrl) {
|
||||
const normalizedPublicBaseUrl = normalizeBaseUrl(playerPublicBaseUrl);
|
||||
if (!normalizedPublicBaseUrl) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const players = await fetchPlayerRegistrations(pool);
|
||||
const matchedPlayer = (Array.isArray(players) ? players : []).find(function (player) {
|
||||
return normalizeBaseUrl(player && player.public_base_url) === normalizedPublicBaseUrl;
|
||||
});
|
||||
const registeredInternalBaseUrl = normalizeBaseUrl(matchedPlayer && matchedPlayer.internal_base_url);
|
||||
if (registeredInternalBaseUrl) {
|
||||
return registeredInternalBaseUrl;
|
||||
}
|
||||
} catch (_error) {
|
||||
}
|
||||
|
||||
if (isLocalLikeBaseUrl(normalizedPublicBaseUrl) && configuredPlayerInternalBaseUrl) {
|
||||
return configuredPlayerInternalBaseUrl;
|
||||
}
|
||||
|
||||
return normalizedPublicBaseUrl;
|
||||
}
|
||||
|
||||
async function forwardAnnouncementRefresh(slug) {
|
||||
const resolvedPlayerInternalBaseUrl = await getPlayerInternalBaseUrl();
|
||||
const targetBaseUrls = Array.from(new Set([
|
||||
@@ -367,6 +394,7 @@ function createPlayerActionService(options) {
|
||||
|
||||
return {
|
||||
forwardPlayerCommand: forwardPlayerCommand,
|
||||
resolvePlayerBaseUrlForPublicUrl: resolvePlayerBaseUrlForPublicUrl,
|
||||
forwardAnnouncementRefresh: forwardAnnouncementRefresh,
|
||||
getScreenConnections: getScreenConnections,
|
||||
forwardPlayerCommandToBaseUrl: forwardPlayerCommandToBaseUrl,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Load and normalize browser region scripts for server-rendered player pages.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Start background services, scheduled tasks, and web application dependencies.
|
||||
|
||||
async function initializeWebServer(options) {
|
||||
const common = options && options.common;
|
||||
const pool = options && options.pool;
|
||||
|
||||
Reference in New Issue
Block a user