feat:移除了弹窗,服务器添加sls

This commit is contained in:
AxTps committed 2026-09-08 22:39:45 +08:00
commit 6a295f9a7a
4082 files changed
+1322534

No files matched your search

@@ -0,0 +1,196 @@
import type { Labrinth } from '@modrinth/api-client'
import { reactive } from 'vue'
import { resolveServerLauncher } from '@/components/multiplayer/servers/server-flow-utils'
import { get_version } from '@/helpers/cache.js'
import {
type InstallModpackOptions,
serverEventListener,
type ServerInfoData,
servers,
} from '@/helpers/servers'
import { createServerDownloadBridge } from './server-download-bridge'
import { refresh as refreshServerList } from './useServers'
const LOG_CAPACITY = 500
export interface ActiveServerInstall {
progress: { downloaded: number; total: number | null } | null
log: string[]
}
/**
* Why a server's start controls are replaced by download actions:
* - `installing`: files are transferring right now (this app session)
* - `interrupted`: the manifest was flagged mid-install and never finished
* (app closed while downloading); resumable
* - `failed`: the last install attempt errored; retryable
*/
export type ServerSetupStatus = 'installing' | 'interrupted' | 'failed'
/** Module-level singleton: install activity is global to the app. */
const activeInstalls = reactive<Record<string, ActiveServerInstall | undefined>>({})
export function activeInstallFor(serverId: string): ActiveServerInstall | null {
return activeInstalls[serverId] ?? null
}
export function serverSetupStatus(server: ServerInfoData): ServerSetupStatus | null {
if (activeInstalls[server.id]) return 'installing'
if (server.installState === 'incomplete') return 'interrupted'
if (server.installState === 'failed') return 'failed'
return null
}
/**
* [SERVER-DOWNLOAD-BRIDGE] Runs a modpack server install in the background.
*
* During the install, progress and log events are tracked in the shared
* `activeInstalls` registry so any surface (wizard, servers list, detail
* page) can render live state.
*
* Additionally, when a `DownloadManager` reference is provided, a synthetic
* install job is injected so the server download also appears in the global
* Downloads page (/downloads) and the sidebar download badge.
*
* NOTE: The download manager MUST be captured during Vue setup context and
* passed in — `injectDownloadManager()` only works inside synchronous setup
* scope. After any `await` the injection context is lost and the call
* would throw.
*/
export async function startModpackServerInstall(
serverId: string,
options: InstallModpackOptions,
downloadManager?: import('@/providers/download-manager').DownloadManager | null,
): Promise<void> {
if (activeInstalls[serverId]) {
throw new Error('This server already has an install running')
}
const entry: ActiveServerInstall = { progress: null, log: [] }
activeInstalls[serverId] = entry
// [SERVER-DOWNLOAD-BRIDGE] Create a synthetic job that mirrors this server
// install in the global Downloads page. The job_id uses a `server-` prefix
// to avoid collisions with backend-tracked install jobs. The shared bridge
// centralises the snapshot shape used by every server download (modpack and
// vanilla) so they stay consistent in the sidebar.
const syntheticJobId = `server-${serverId}`
const bridge = downloadManager
? createServerDownloadBridge(downloadManager, syntheticJobId, {
title: options.modpackTitle ?? 'Server',
icon: options.modpackIconUrl ?? null,
provider: options.modpackProjectId ? 'modrinth' : 'local',
})
: null
if (!bridge) {
console.warn(
`[SERVER-DOWNLOAD-BRIDGE] No download manager available — job ${syntheticJobId} will NOT appear in sidebar`,
)
}
// [SERVER-DOWNLOAD-BRIDGE] Listen for backend progress events and forward
// them to both the local `entry` (used by wizard UI) and the synthetic
// job (used by the global Downloads page).
let prevBytes = 0
let prevTime = performance.now()
let smoothedSpeed = 0
const unlistenProgress = await serverEventListener((id, payload) => {
if (id !== serverId || payload.event !== 'download_progress') return
entry.progress = { downloaded: payload.downloaded, total: payload.total ?? null }
const now2 = performance.now()
const dt = (now2 - prevTime) / 1000
if (dt > 0.3 && prevBytes > 0 && payload.downloaded >= prevBytes) {
const rawSpeed = (payload.downloaded - prevBytes) / dt
smoothedSpeed = smoothedSpeed === 0 ? rawSpeed : smoothedSpeed * 0.7 + rawSpeed * 0.3
}
prevBytes = payload.downloaded
prevTime = now2
const speed = smoothedSpeed > 100 ? smoothedSpeed : null
const total = payload.total ?? null
const eta =
speed && total != null && total > payload.downloaded
? (total - payload.downloaded) / speed
: null
bridge?.update({ downloaded: payload.downloaded, total }, speed, eta)
})
const unlistenLogs = await serverEventListener((id, payload) => {
if (id !== serverId || payload.event !== 'log') return
entry.log.push(payload.line)
if (entry.log.length > LOG_CAPACITY) entry.log.splice(0, entry.log.length - LOG_CAPACITY)
})
// [SERVER-DOWNLOAD-BRIDGE] Register a cancel handler so the Downloads page
// can abort the running server install when the user clicks Cancel.
bridge?.cancel(async () => {
console.log(`[SERVER-DOWNLOAD-BRIDGE] Cancelling server install ${serverId}`)
await servers.stop(serverId).catch(() => {})
})
let installSucceeded = false
try {
await servers.installModpack(serverId, options)
installSucceeded = true
} finally {
unlistenProgress()
unlistenLogs()
// [SERVER-DOWNLOAD-BRIDGE] Mark the synthetic job as succeeded or failed
// so it transitions out of the active tab and into history.
bridge?.complete(installSucceeded, entry.progress ?? undefined)
activeInstalls[serverId] = undefined
void refreshServerList()
}
}
/**
* [SERVER-DOWNLOAD-BRIDGE] Resumes an interrupted or failed modpack install
* for a server created from a modpack: re-resolves the pack file and launcher
* jar from the recorded source project, then reruns the same background install.
*
* The optional `downloadManager` parameter is forwarded to
* `startModpackServerInstall` so the synthetic job appears in sidebar.
* See the note in `startModpackServerInstall` for why this must be passed
* explicitly.
*/
export async function resumeModpackInstall(
server: ServerInfoData,
downloadManager?: import('@/providers/download-manager').DownloadManager | null,
): Promise<void> {
const modpack = server.modpack
if (!modpack?.versionId) {
throw new Error('This server has no modpack source to resume from')
}
const version = (await get_version(modpack.versionId)) as Labrinth.Versions.v2.Version
const primaryFile = version.files.find((file) => file.primary) ?? version.files[0]
if (!primaryFile?.url) {
throw new Error('Modpack has no downloadable file')
}
const jar = await resolveServerLauncher(
server.serverType,
server.gameVersion,
server.loaderVersion,
)
if (!jar) {
throw new Error(
`No server launcher available for ${server.serverType} on ${server.gameVersion}`,
)
}
await startModpackServerInstall(
server.id,
{
mrpackUrl: primaryFile.url,
mrpackSha1: primaryFile.hashes?.sha1,
jarUrl: jar.url,
jarFilename: jar.filename,
jarSha1: jar.sha1,
modpackProjectId: modpack.projectId,
modpackVersionId: modpack.versionId,
modpackTitle: modpack.title,
modpackIconUrl: modpack.iconUrl,
},
downloadManager,
)
}