Compare commits
2
Commits
52a643a868
...
aef63ca444
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aef63ca444 | ||
|
|
10d15dfddc |
@@ -929,14 +929,17 @@
|
||||
|
||||
<!-- Sidebar: Status -->
|
||||
<div class="sidebar" style="min-width: 200px; display:flex; flex-direction:column; height:100%;">
|
||||
<!-- Version Control (Hidden since we don't use it anymore) -->
|
||||
<div class="version-panel" style="display:none;">
|
||||
<!-- Station Update -->
|
||||
<div class="version-panel">
|
||||
<div class="version-panel-header">
|
||||
<span>Audio Version</span>
|
||||
<button class="btn-icon" onclick="fetchVersion()" title="Refresh Version">
|
||||
<span>Station Update</span>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<button class="btn" id="publish-update-btn" onclick="publishStationUpdate()" title="Publish Station Update">发布更新</button>
|
||||
<button class="btn-icon" onclick="fetchStationUpdate()" title="Refresh Update Status">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-display" id="current-version">Loading...</div>
|
||||
</div>
|
||||
|
||||
@@ -1072,6 +1075,7 @@
|
||||
let currentPath = 'root';
|
||||
let stationCodeMap = {};
|
||||
let broadcastingStations = new Set();
|
||||
let currentStationUpdate = null;
|
||||
|
||||
// --- WebSocket ---
|
||||
const connStatus = document.getElementById('conn-status');
|
||||
@@ -1118,6 +1122,11 @@
|
||||
|
||||
// --- Renderers ---
|
||||
function updateState(state) {
|
||||
if (state.stationUpdate) {
|
||||
currentStationUpdate = state.stationUpdate;
|
||||
renderStationUpdate(state.stationUpdate);
|
||||
}
|
||||
|
||||
if (state.stations) {
|
||||
const list = document.getElementById('station-list');
|
||||
list.innerHTML = '';
|
||||
@@ -1136,6 +1145,12 @@
|
||||
const hasCC = Array.isArray(st.tags) && st.tags.includes('CC');
|
||||
const stationTarget = st.target || st.name;
|
||||
const memberCount = Number(st.memberCount || 1);
|
||||
const stationVersion = st.version || '未知版本';
|
||||
const targetVersion = currentStationUpdate?.targetVersion || '';
|
||||
const needsUpdate = !!targetVersion && stationVersion !== targetVersion;
|
||||
const updateBadge = needsUpdate
|
||||
? `<span style="font-size:0.65rem; background:rgba(245, 158, 11, 0.12); color:#fbbf24; padding:1px 5px; border-radius:4px; margin-left:6px; font-weight:normal; border:1px solid rgba(245, 158, 11, 0.25)">待更新 ${targetVersion}</span>`
|
||||
: '';
|
||||
const div = document.createElement('div');
|
||||
div.className = 'status-item';
|
||||
div.id = `station-item-${st.name.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
||||
@@ -1145,8 +1160,9 @@
|
||||
<div class="item-title">
|
||||
${st.name}
|
||||
${hasCC ? '<span style="font-size:0.65rem; background:rgba(16, 185, 129, 0.1); color:#34d399; padding:1px 5px; border-radius:4px; margin-left:6px; font-weight:normal; border:1px solid rgba(16, 185, 129, 0.2)">CC</span>' : ''}
|
||||
${updateBadge}
|
||||
</div>
|
||||
<div class="item-sub">${st.code}${st.platform ? ` - Platform ${st.platform}` : ''}${memberCount > 1 ? ` · Nodes ${memberCount}` : ''}</div>
|
||||
<div class="item-sub">${st.code}${st.platform ? ` - Platform ${st.platform}` : ''}${memberCount > 1 ? ` · Nodes ${memberCount}` : ''} · ${stationVersion}</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div class="status-dot ${dotClass}" id="station-dot-${st.name.replace(/[^a-zA-Z0-9]/g, '_')}" data-online="${isOnline}" data-name="${st.name}"></div>
|
||||
@@ -1688,20 +1704,54 @@
|
||||
|
||||
// Init
|
||||
refreshFiles();
|
||||
fetchVersion();
|
||||
fetchStationUpdate();
|
||||
|
||||
async function fetchVersion() {
|
||||
try {
|
||||
const res = await fetch('/api/version');
|
||||
const data = await res.json();
|
||||
if (data.version) {
|
||||
document.getElementById('current-version').textContent = data.version;
|
||||
function renderStationUpdate(info) {
|
||||
const el = document.getElementById('current-version');
|
||||
const localVersion = info?.localVersion || '未知';
|
||||
const targetVersion = info?.targetVersion || '未发布';
|
||||
const publishedAt = info?.publishedAt ? new Date(info.publishedAt).toLocaleString() : '未发布';
|
||||
el.innerHTML = `
|
||||
<div>服务器脚本:${localVersion}</div>
|
||||
<div>已发布版本:${targetVersion}</div>
|
||||
<div style="margin-top:4px;color:var(--text-muted)">发布时间:${publishedAt}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchStationUpdate() {
|
||||
try {
|
||||
const res = await fetch('/api/station-update');
|
||||
const data = await res.json();
|
||||
currentStationUpdate = data;
|
||||
renderStationUpdate(data);
|
||||
} catch (e) {
|
||||
document.getElementById('current-version').textContent = 'Error';
|
||||
}
|
||||
}
|
||||
|
||||
async function publishStationUpdate() {
|
||||
if (!confirm('发布后,所有在线主机会立即下载最新 startup 并自动重启;离线主机会在下次加载时立即更新。确定继续吗?')) return;
|
||||
|
||||
const btn = document.getElementById('publish-update-btn');
|
||||
const originalText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '发布中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/station-updates/publish', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (!data.success) throw new Error(data.message || 'Publish failed');
|
||||
currentStationUpdate = data;
|
||||
renderStationUpdate(data);
|
||||
showToast('更新已发布', `目标版本 ${data.targetVersion || data.localVersion},已通知 ${data.notifiedStations || 0}/${data.onlineStations || 0} 台在线主机。`);
|
||||
} catch (e) {
|
||||
alert(`发布失败:${e.message || e}`);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Resizers ---
|
||||
const resizerV = document.getElementById('resizer-v');
|
||||
const resizerH = document.getElementById('resizer-h');
|
||||
|
||||
+147
-4
@@ -49,6 +49,10 @@ const audioGeneralLegacyDir = path.join(audioRootDir, 'general');
|
||||
const audioStationDir = path.join(audioRootDir, 'station');
|
||||
const listFilePath = path.join(appRootDir, 'list.txt');
|
||||
const tempBroadcastDir = path.join(appRootDir, 'temp-broadcast');
|
||||
const stationScriptPath = path.join(appRootDir, 'station_broadcast.lua');
|
||||
const stationUpdateScriptPath = path.join(appRootDir, 'update_station_broadcast.lua');
|
||||
const STATION_SCRIPT_DOWNLOAD_PATH = '/downloads/station_broadcast.lua';
|
||||
const STATION_UPDATE_SCRIPT_DOWNLOAD_PATH = '/downloads/update_station_broadcast.lua';
|
||||
const STREAMABLE_AUDIO_EXTENSIONS = ['.wav', '.mp3', '.ogg', '.flac', '.m4a', '.aac', '.opus'];
|
||||
const MUSIC_AUDIO_EXTENSIONS = [...STREAMABLE_AUDIO_EXTENSIONS, '.dfpwm'];
|
||||
|
||||
@@ -190,6 +194,11 @@ let activeTrains = [];
|
||||
let stationLogs = [];
|
||||
let audioVersion = String(Date.now());
|
||||
let versions = [];
|
||||
let stationUpdate = {
|
||||
version: '',
|
||||
releaseId: '',
|
||||
publishedAt: 0
|
||||
};
|
||||
|
||||
const DATA_FILE = path.join(__dirname, 'server-data.json');
|
||||
|
||||
@@ -199,6 +208,94 @@ function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function getLocalStationScriptVersion() {
|
||||
try {
|
||||
if (!fs.existsSync(stationScriptPath)) return '';
|
||||
const content = fs.readFileSync(stationScriptPath, 'utf8');
|
||||
const match = content.match(/local\s+APP_VERSION_TEXT\s*=\s*["']([^"']+)["']/);
|
||||
return match ? String(match[1]).trim() : '';
|
||||
} catch (e) {
|
||||
log('ERROR', `Failed to read station script version: ${e.message}`);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function buildAbsoluteUrl(req, pathname) {
|
||||
const proto = String(req?.headers?.['x-forwarded-proto'] || req?.protocol || 'http').split(',')[0].trim();
|
||||
const host = String(req?.headers?.['x-forwarded-host'] || req?.headers?.host || '').split(',')[0].trim();
|
||||
if (!host) return pathname;
|
||||
return `${proto}://${host}${pathname}`;
|
||||
}
|
||||
|
||||
function getStationUpdateSummary(req) {
|
||||
return {
|
||||
localVersion: getLocalStationScriptVersion(),
|
||||
targetVersion: stationUpdate.version || '',
|
||||
releaseId: stationUpdate.releaseId || '',
|
||||
publishedAt: Number(stationUpdate.publishedAt || 0),
|
||||
downloadPath: STATION_SCRIPT_DOWNLOAD_PATH,
|
||||
downloadUrl: buildAbsoluteUrl(req, STATION_SCRIPT_DOWNLOAD_PATH)
|
||||
};
|
||||
}
|
||||
|
||||
function maybeSendStationUpdate(ws, stationName, stationVersion) {
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
||||
if (!stationUpdate.releaseId || !stationUpdate.version) return false;
|
||||
if (String(stationVersion || '').trim() === String(stationUpdate.version || '').trim()) return false;
|
||||
|
||||
try {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'station_update',
|
||||
version: stationUpdate.version,
|
||||
releaseId: stationUpdate.releaseId,
|
||||
publishedAt: Number(stationUpdate.publishedAt || 0),
|
||||
downloadPath: STATION_SCRIPT_DOWNLOAD_PATH
|
||||
}));
|
||||
log('INFO', `Queued station update for "${stationName}" -> ${stationUpdate.version}`);
|
||||
return true;
|
||||
} catch (e) {
|
||||
log('ERROR', `Failed to push station update to "${stationName}": ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function publishStationUpdate(req) {
|
||||
const version = getLocalStationScriptVersion() || `build-${Date.now()}`;
|
||||
const releaseId = String(Date.now());
|
||||
stationUpdate = {
|
||||
version,
|
||||
releaseId,
|
||||
publishedAt: Date.now()
|
||||
};
|
||||
saveData();
|
||||
|
||||
let onlineStations = 0;
|
||||
let notifiedStations = 0;
|
||||
for (const [name, station] of Object.entries(stations)) {
|
||||
const ws = station?.ws;
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) continue;
|
||||
onlineStations++;
|
||||
if (maybeSendStationUpdate(ws, name, station.version)) {
|
||||
notifiedStations++;
|
||||
}
|
||||
}
|
||||
|
||||
const summary = getStationUpdateSummary(req);
|
||||
broadcastUpdate();
|
||||
broadcastToConsole({
|
||||
type: 'event',
|
||||
title: '站点更新已发布',
|
||||
message: `目标版本 ${summary.targetVersion || version},已通知 ${notifiedStations}/${onlineStations} 台在线主机。`
|
||||
});
|
||||
log('INFO', `Station update published: ${version} (${notifiedStations}/${onlineStations} online stations notified)`);
|
||||
|
||||
return {
|
||||
...summary,
|
||||
onlineStations,
|
||||
notifiedStations
|
||||
};
|
||||
}
|
||||
|
||||
function getInterruptModeForPriority(priority) {
|
||||
if (priority >= 2) return 'all';
|
||||
if (priority >= 1) return 'ifLower';
|
||||
@@ -281,6 +378,7 @@ function getStationGroups() {
|
||||
code,
|
||||
platform,
|
||||
lastSeen: st.lastSeen || 0,
|
||||
version: st.version || '',
|
||||
source: 'cc',
|
||||
tags: ['CC'],
|
||||
memberNames: [st.name],
|
||||
@@ -295,6 +393,9 @@ function getStationGroups() {
|
||||
group.memberCount = group.memberNames.length;
|
||||
if ((st.lastSeen || 0) > (group.lastSeen || 0)) {
|
||||
group.lastSeen = st.lastSeen || 0;
|
||||
group.version = st.version || group.version || '';
|
||||
} else if (!group.version && st.version) {
|
||||
group.version = st.version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,6 +926,13 @@ function loadData() {
|
||||
if (data.activeTrains) activeTrains = data.activeTrains;
|
||||
if (data.audioVersion) audioVersion = String(data.audioVersion);
|
||||
if (Array.isArray(data.versions)) versions = data.versions;
|
||||
if (data.stationUpdate && typeof data.stationUpdate === 'object') {
|
||||
stationUpdate = {
|
||||
version: String(data.stationUpdate.version || ''),
|
||||
releaseId: String(data.stationUpdate.releaseId || ''),
|
||||
publishedAt: Number(data.stationUpdate.publishedAt || 0)
|
||||
};
|
||||
}
|
||||
refreshStationIndex();
|
||||
log('INFO', 'Data loaded from disk');
|
||||
} catch (e) {
|
||||
@@ -846,7 +954,8 @@ function saveData() {
|
||||
stations: stationsToSave,
|
||||
activeTrains,
|
||||
audioVersion,
|
||||
versions
|
||||
versions,
|
||||
stationUpdate
|
||||
};
|
||||
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
||||
} catch (e) {
|
||||
@@ -1672,9 +1781,10 @@ function handleMessage(ws, data) {
|
||||
// Send initial state
|
||||
ws.send(JSON.stringify({
|
||||
type: 'init_state',
|
||||
stations,
|
||||
stations: getMergedStationList(),
|
||||
activeTrains,
|
||||
logs: stationLogs
|
||||
logs: stationLogs,
|
||||
stationUpdate: getStationUpdateSummary()
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@@ -1733,6 +1843,7 @@ function handleHeartbeat(ws, data) {
|
||||
};
|
||||
normalizeStationRecord(stations[cleanName]);
|
||||
log('INFO', `Station auto-registered via heartbeat: "${cleanName}"${data.stationVersion ? ` ${data.stationVersion}` : ''}`);
|
||||
maybeSendStationUpdate(ws, cleanName, data.stationVersion || stations[cleanName].version);
|
||||
broadcastUpdate();
|
||||
} else {
|
||||
stations[cleanName].lastSeen = Date.now();
|
||||
@@ -1745,6 +1856,7 @@ function handleHeartbeat(ws, data) {
|
||||
broadcastUpdate();
|
||||
}
|
||||
normalizeStationRecord(stations[cleanName]);
|
||||
maybeSendStationUpdate(ws, cleanName, data.stationVersion || stations[cleanName].version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1771,6 +1883,7 @@ function handleStationRegister(ws, data) {
|
||||
stationName: cleanName
|
||||
}));
|
||||
|
||||
maybeSendStationUpdate(ws, cleanName, stationVersion);
|
||||
saveData();
|
||||
broadcastUpdate();
|
||||
}
|
||||
@@ -2066,11 +2179,26 @@ function broadcastUpdate() {
|
||||
const state = {
|
||||
type: 'state_update',
|
||||
stations: list,
|
||||
activeTrains
|
||||
activeTrains,
|
||||
stationUpdate: getStationUpdateSummary()
|
||||
};
|
||||
broadcastToConsole(state);
|
||||
}
|
||||
|
||||
app.get(STATION_SCRIPT_DOWNLOAD_PATH, (req, res) => {
|
||||
if (!fs.existsSync(stationScriptPath)) {
|
||||
return res.status(404).send('station_broadcast.lua not found');
|
||||
}
|
||||
return res.sendFile(stationScriptPath);
|
||||
});
|
||||
|
||||
app.get(STATION_UPDATE_SCRIPT_DOWNLOAD_PATH, (req, res) => {
|
||||
if (!fs.existsSync(stationUpdateScriptPath)) {
|
||||
return res.status(404).send('update_station_broadcast.lua not found');
|
||||
}
|
||||
return res.sendFile(stationUpdateScriptPath);
|
||||
});
|
||||
|
||||
app.get('/api/streams', (req, res) => {
|
||||
const streams = [];
|
||||
const seen = new Set();
|
||||
@@ -2193,6 +2321,21 @@ app.get('/api/version', (req, res) => {
|
||||
res.json({ version: audioVersion });
|
||||
});
|
||||
|
||||
app.get('/api/station-update', (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
...getStationUpdateSummary(req)
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/station-updates/publish', (req, res) => {
|
||||
const result = publishStationUpdate(req);
|
||||
res.json({
|
||||
success: true,
|
||||
...result
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/publish-version', (req, res) => {
|
||||
const version = normalizeVersionString(req.body?.version) || String(Date.now());
|
||||
const snap = snapshotFiles();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local STATION_BROADCAST_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/branch/main/station_broadcast.lua"
|
||||
local STATION_BROADCAST_URL = "http://stabadmin.fse-media.group/downloads/station_broadcast.lua"
|
||||
local UPDATE_SCRIPT_URL = "http://stabadmin.fse-media.group/downloads/update_station_broadcast.lua"
|
||||
|
||||
local function fetch(url)
|
||||
if not http then
|
||||
@@ -64,7 +65,8 @@ local function downloadTo(url, path)
|
||||
end
|
||||
|
||||
local targets = {
|
||||
{ url = STATION_BROADCAST_URL, path = shell.resolve("/startup") }
|
||||
{ url = STATION_BROADCAST_URL, path = shell.resolve("/startup") },
|
||||
{ url = UPDATE_SCRIPT_URL, path = shell.resolve("/update_station_broadcast.lua") }
|
||||
}
|
||||
|
||||
for _, t in ipairs(targets) do
|
||||
@@ -76,4 +78,5 @@ for _, t in ipairs(targets) do
|
||||
end
|
||||
|
||||
print("Install complete.")
|
||||
print("Update helper installed as /update_station_broadcast.lua")
|
||||
print("Run 'reboot' to start StationBroadcast on boot.")
|
||||
|
||||
+180
-2
@@ -2,11 +2,12 @@
|
||||
-- ============================================================================
|
||||
-- 1. Configuration & Constants
|
||||
-- ============================================================================
|
||||
local APP_VERSION_TEXT = "v1.0.44"
|
||||
local APP_VERSION_TEXT = "v1.0.45"
|
||||
|
||||
local CONFIG = {
|
||||
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
||||
API_URL = "http://stabadmin.fse-media.group",
|
||||
STARTUP_PATH = "/startup",
|
||||
SYNC_INTERVAL = 30,
|
||||
AUDIO_BASE = "Audio/",
|
||||
AUDIO_STATION = "Audio/Station/",
|
||||
@@ -55,7 +56,8 @@ local State = {
|
||||
currentPlaying = nil,
|
||||
currentPriority = -1,
|
||||
interruptSeq = 0,
|
||||
tickRate = 20
|
||||
tickRate = 20,
|
||||
updateInProgress = false
|
||||
}
|
||||
|
||||
-- ============================================================================
|
||||
@@ -1813,6 +1815,162 @@ function Utils.getStationAudio(code, langPrefix)
|
||||
return langPrefix .. code
|
||||
end
|
||||
|
||||
local AutoUpdate = {}
|
||||
|
||||
function AutoUpdate.fetch(url)
|
||||
if not http then
|
||||
return nil, "HTTP API unavailable"
|
||||
end
|
||||
|
||||
if http.checkURL then
|
||||
local ok, err = http.checkURL(url)
|
||||
if not ok then
|
||||
return nil, err or "URL blocked"
|
||||
end
|
||||
end
|
||||
|
||||
local res, err = http.get(url)
|
||||
if not res then
|
||||
return nil, err or "request failed"
|
||||
end
|
||||
|
||||
local body = res.readAll()
|
||||
res.close()
|
||||
|
||||
if type(body) ~= "string" or #body == 0 then
|
||||
return nil, "empty response"
|
||||
end
|
||||
|
||||
return body
|
||||
end
|
||||
|
||||
function AutoUpdate.writeFile(path, content)
|
||||
local dir = fs.getDir(path)
|
||||
if dir and dir ~= "" and not fs.exists(dir) then
|
||||
fs.makeDir(dir)
|
||||
end
|
||||
|
||||
local handle = fs.open(path, "w")
|
||||
if not handle then
|
||||
return nil, "cannot open " .. path
|
||||
end
|
||||
|
||||
handle.write(content)
|
||||
handle.close()
|
||||
return true
|
||||
end
|
||||
|
||||
function AutoUpdate.getStartupPath()
|
||||
if shell and shell.resolve then
|
||||
return shell.resolve(CONFIG.STARTUP_PATH)
|
||||
end
|
||||
return CONFIG.STARTUP_PATH
|
||||
end
|
||||
|
||||
function AutoUpdate.getDownloadUrl(info)
|
||||
if type(info) == "table" then
|
||||
local direct = Utils.trim(info.downloadUrl)
|
||||
if direct ~= "" then
|
||||
return direct
|
||||
end
|
||||
|
||||
local path = Utils.trim(info.downloadPath)
|
||||
if path ~= "" then
|
||||
return CONFIG.API_URL .. path
|
||||
end
|
||||
end
|
||||
|
||||
return CONFIG.API_URL .. "/downloads/station_broadcast.lua"
|
||||
end
|
||||
|
||||
function AutoUpdate.fetchInfo()
|
||||
local body, err = AutoUpdate.fetch(CONFIG.API_URL .. "/api/station-update")
|
||||
if not body then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local ok, data = pcall(textutils.unserializeJSON, body)
|
||||
if not ok or type(data) ~= "table" then
|
||||
return nil, "invalid update response"
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function AutoUpdate.getTargetVersion(info)
|
||||
if type(info) ~= "table" then
|
||||
return ""
|
||||
end
|
||||
return Utils.trim(info.targetVersion or info.version)
|
||||
end
|
||||
|
||||
function AutoUpdate.needsUpdate(info)
|
||||
local targetVersion = AutoUpdate.getTargetVersion(info)
|
||||
return targetVersion ~= "" and targetVersion ~= APP_VERSION_TEXT
|
||||
end
|
||||
|
||||
function AutoUpdate.apply(info, reason)
|
||||
if State.updateInProgress then
|
||||
return false, "update already in progress"
|
||||
end
|
||||
|
||||
local targetVersion = AutoUpdate.getTargetVersion(info)
|
||||
if targetVersion == "" then
|
||||
return false, "missing target version"
|
||||
end
|
||||
|
||||
local downloadUrl = AutoUpdate.getDownloadUrl(info)
|
||||
local startupPath = AutoUpdate.getStartupPath()
|
||||
|
||||
State.updateInProgress = true
|
||||
Utils.log("WARN", "Applying update " .. targetVersion .. " via " .. tostring(reason or "manual"))
|
||||
|
||||
if Audio and Audio.stop then
|
||||
pcall(Audio.stop, "all")
|
||||
end
|
||||
if State.ws then
|
||||
pcall(State.ws.close)
|
||||
State.ws = nil
|
||||
end
|
||||
|
||||
local body, err = AutoUpdate.fetch(downloadUrl)
|
||||
if not body then
|
||||
State.updateInProgress = false
|
||||
return false, err
|
||||
end
|
||||
|
||||
local ok, writeErr = AutoUpdate.writeFile(startupPath, body)
|
||||
if not ok then
|
||||
State.updateInProgress = false
|
||||
return false, writeErr
|
||||
end
|
||||
|
||||
Utils.log("SUCCESS", "Updated startup to " .. targetVersion)
|
||||
Utils.log("WARN", "Rebooting to finish update")
|
||||
os.sleep(1)
|
||||
os.reboot()
|
||||
return true
|
||||
end
|
||||
|
||||
function AutoUpdate.checkOnBoot()
|
||||
local info, err = AutoUpdate.fetchInfo()
|
||||
if not info then
|
||||
Utils.log("WARN", "Update check skipped: " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
|
||||
if not AutoUpdate.needsUpdate(info) then
|
||||
return false
|
||||
end
|
||||
|
||||
Utils.log("INFO", "Found published update " .. AutoUpdate.getTargetVersion(info) .. " on boot")
|
||||
local ok, applyErr = AutoUpdate.apply(info, "boot")
|
||||
if not ok then
|
||||
Utils.log("ERROR", "Boot update failed: " .. tostring(applyErr))
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- 6. Network System
|
||||
-- ============================================================================
|
||||
@@ -1990,6 +2148,25 @@ function Network.messageLoop()
|
||||
local flushBelow = data.flushBelow
|
||||
Audio.stop(mode, flushBelow)
|
||||
end
|
||||
elseif data.type == "station_update" then
|
||||
local targetVersion = AutoUpdate.getTargetVersion(data)
|
||||
if targetVersion == "" then
|
||||
Utils.log("WARN", "Ignored station update without version")
|
||||
elseif targetVersion == APP_VERSION_TEXT then
|
||||
Utils.log("INFO", "Received update notice for current version " .. targetVersion)
|
||||
else
|
||||
Utils.log("WARN", "Received published update " .. targetVersion)
|
||||
local okUpdate, updateErr = AutoUpdate.apply({
|
||||
targetVersion = targetVersion,
|
||||
version = targetVersion,
|
||||
releaseId = data.releaseId,
|
||||
downloadPath = data.downloadPath,
|
||||
downloadUrl = data.downloadUrl
|
||||
}, "publish")
|
||||
if not okUpdate then
|
||||
Utils.log("ERROR", "Station update failed: " .. tostring(updateErr))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if State.audioRx then
|
||||
@@ -2842,6 +3019,7 @@ end
|
||||
-- ============================================================================
|
||||
-- 9. Bootstrap
|
||||
-- ============================================================================
|
||||
AutoUpdate.checkOnBoot()
|
||||
Peripherals.init()
|
||||
Utils.ensureSlaveIdentityConfigured()
|
||||
Audio.loadStationList()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
local STATION_BROADCAST_URL = "https://gitea.fse-media.group/Henry_Du/CC-StationBroadcast/raw/branch/main/station_broadcast.lua"
|
||||
local STATION_BROADCAST_URL = "http://stabadmin.fse-media.group/downloads/station_broadcast.lua"
|
||||
|
||||
local function fetch(url)
|
||||
if not http then
|
||||
|
||||
Reference in New Issue
Block a user