Add launcher catalog workflow and smoke tests
This commit is contained in:
168
app/assets/js/profileassetmanager.js
Normal file
168
app/assets/js/profileassetmanager.js
Normal file
@@ -0,0 +1,168 @@
|
||||
const AdmZip = require('adm-zip')
|
||||
const fs = require('fs-extra')
|
||||
const got = require('got')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const { pipeline } = require('stream/promises')
|
||||
|
||||
const ConfigManager = require('./configmanager')
|
||||
|
||||
function getProfileBaseDirectory(profileId){
|
||||
return path.join(ConfigManager.getDataDirectory(), 'profiles', profileId)
|
||||
}
|
||||
|
||||
function getProfileDownloadDirectory(profileId){
|
||||
return path.join(getProfileBaseDirectory(profileId), 'downloads')
|
||||
}
|
||||
|
||||
function getProfileCacheFile(profileId, fileName){
|
||||
return path.join(getProfileDownloadDirectory(profileId), fileName)
|
||||
}
|
||||
|
||||
function getServerBundleDirectory(profile){
|
||||
return path.join(getProfileBaseDirectory(profile.id), profile.serverDirectoryName || 'server')
|
||||
}
|
||||
|
||||
function isRemoteSource(source){
|
||||
return /^https?:\/\//i.test(source)
|
||||
}
|
||||
|
||||
function resolveLocalSource(source){
|
||||
if(source == null){
|
||||
return null
|
||||
}
|
||||
|
||||
if(source.startsWith('file://')){
|
||||
return decodeURIComponent(source.substring('file://'.length))
|
||||
}
|
||||
|
||||
return path.resolve(source)
|
||||
}
|
||||
|
||||
async function downloadSourceToFile(source, destination){
|
||||
await fs.ensureDir(path.dirname(destination))
|
||||
|
||||
if(isRemoteSource(source)){
|
||||
await pipeline(
|
||||
got.stream(source),
|
||||
fs.createWriteStream(destination)
|
||||
)
|
||||
return destination
|
||||
}
|
||||
|
||||
const localSource = resolveLocalSource(source)
|
||||
const localStat = await fs.stat(localSource)
|
||||
if(localStat.isDirectory()){
|
||||
throw new Error(`Directory source is not supported for archive cache: ${localSource}`)
|
||||
}
|
||||
await fs.copy(localSource, destination, { overwrite: true })
|
||||
return destination
|
||||
}
|
||||
|
||||
async function extractZipToDirectory(zipPath, destination){
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'launcher-assets-'))
|
||||
try {
|
||||
await fs.ensureDir(tempDir)
|
||||
new AdmZip(zipPath).extractAllTo(tempDir, true)
|
||||
|
||||
const rootEntries = (await fs.readdir(tempDir)).filter((entry) => entry !== '__MACOSX')
|
||||
await fs.remove(destination)
|
||||
|
||||
if(rootEntries.length === 1){
|
||||
const onlyEntry = path.join(tempDir, rootEntries[0])
|
||||
const entryStat = await fs.stat(onlyEntry)
|
||||
if(entryStat.isDirectory()){
|
||||
await fs.copy(onlyEntry, destination, { overwrite: true })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
await fs.copy(tempDir, destination, { overwrite: true })
|
||||
} finally {
|
||||
await fs.remove(tempDir)
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureCachedArchive(profileId, source, fileName){
|
||||
const cachePath = getProfileCacheFile(profileId, fileName)
|
||||
await downloadSourceToFile(source, cachePath)
|
||||
return cachePath
|
||||
}
|
||||
|
||||
exports.getProfileBaseDirectory = getProfileBaseDirectory
|
||||
exports.getServerBundleDirectory = getServerBundleDirectory
|
||||
|
||||
exports.prefetchProfileAssets = async function(profile){
|
||||
if(profile.worldArchiveUrl){
|
||||
await ensureCachedArchive(profile.id, profile.worldArchiveUrl, 'world.zip')
|
||||
}
|
||||
if(profile.serverBundleUrl){
|
||||
await ensureCachedArchive(profile.id, profile.serverBundleUrl, 'server.zip')
|
||||
}
|
||||
|
||||
const currentState = ConfigManager.getLibraryProfileAssetState(profile.id)
|
||||
ConfigManager.setLibraryProfileAssetState(profile.id, {
|
||||
...currentState,
|
||||
prefetchedAt: new Date().toISOString()
|
||||
})
|
||||
ConfigManager.save()
|
||||
}
|
||||
|
||||
exports.ensureWorldInstalled = async function(profile, serverId){
|
||||
if(!profile.worldArchiveUrl || !profile.worldDirectoryName){
|
||||
return null
|
||||
}
|
||||
|
||||
const savesDirectory = path.join(ConfigManager.getInstanceDirectory(), serverId, 'saves')
|
||||
const targetWorldDirectory = path.join(savesDirectory, profile.worldDirectoryName)
|
||||
|
||||
await fs.ensureDir(savesDirectory)
|
||||
if(isRemoteSource(profile.worldArchiveUrl) || profile.worldArchiveUrl.endsWith('.zip') || profile.worldArchiveUrl.startsWith('file://')){
|
||||
const cachePath = await ensureCachedArchive(profile.id, profile.worldArchiveUrl, 'world.zip')
|
||||
await extractZipToDirectory(cachePath, targetWorldDirectory)
|
||||
} else {
|
||||
await fs.remove(targetWorldDirectory)
|
||||
await fs.copy(resolveLocalSource(profile.worldArchiveUrl), targetWorldDirectory, { overwrite: true })
|
||||
}
|
||||
|
||||
ConfigManager.setLibraryQuickPlayWorld(profile.id, profile.worldDirectoryName)
|
||||
ConfigManager.setLibraryProfileAssetState(profile.id, {
|
||||
worldInstalledAt: new Date().toISOString(),
|
||||
worldInstalledServerId: serverId,
|
||||
worldDirectoryName: profile.worldDirectoryName
|
||||
})
|
||||
ConfigManager.save()
|
||||
|
||||
return targetWorldDirectory
|
||||
}
|
||||
|
||||
exports.ensureServerBundleInstalled = async function(profile){
|
||||
if(!profile.serverBundleUrl){
|
||||
return null
|
||||
}
|
||||
|
||||
const targetDirectory = getServerBundleDirectory(profile)
|
||||
if(isRemoteSource(profile.serverBundleUrl) || profile.serverBundleUrl.endsWith('.zip') || profile.serverBundleUrl.startsWith('file://')){
|
||||
const cachePath = await ensureCachedArchive(profile.id, profile.serverBundleUrl, 'server.zip')
|
||||
await extractZipToDirectory(cachePath, targetDirectory)
|
||||
} else {
|
||||
await fs.remove(targetDirectory)
|
||||
await fs.copy(resolveLocalSource(profile.serverBundleUrl), targetDirectory, { overwrite: true })
|
||||
}
|
||||
|
||||
ConfigManager.setLibraryProfileAssetState(profile.id, {
|
||||
serverBundleInstalledAt: new Date().toISOString(),
|
||||
serverBundleDirectory: targetDirectory
|
||||
})
|
||||
ConfigManager.save()
|
||||
|
||||
return targetDirectory
|
||||
}
|
||||
|
||||
exports.prepareProfileForLaunch = async function(profile, serverId){
|
||||
if(profile.kind === 'map'){
|
||||
return exports.ensureWorldInstalled(profile, serverId)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user