169 lines
5.6 KiB
JavaScript
169 lines
5.6 KiB
JavaScript
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
|
|
}
|