Enable remote launcher catalog source
This commit is contained in:
@@ -6,6 +6,8 @@ const ConfigManager = require('./configmanager')
|
||||
const { DEFAULT_REMOTE_DISTRO_URL, setRemoteDistributionUrl } = require('./distromanager')
|
||||
|
||||
const LOCAL_CATALOG_PATH = path.join(__dirname, '..', 'launcher', 'catalog.json')
|
||||
const ROOT_ASSET_PATH = path.join(__dirname, '..', '..', '..')
|
||||
const DEFAULT_REMOTE_CATALOG_URL = normalizeNullableText(process.env.LAUNCHER_CATALOG_URL) || 'http://127.0.0.1:8787/catalog.json'
|
||||
|
||||
function normalizeText(value){
|
||||
return typeof value === 'string' ? value.trim() : ''
|
||||
@@ -28,6 +30,14 @@ function normalizePositiveInteger(value, fallback, minimum = 1){
|
||||
return fallback
|
||||
}
|
||||
|
||||
function isRemoteSource(source){
|
||||
return /^https?:\/\//i.test(source)
|
||||
}
|
||||
|
||||
function isAbsoluteFileSystemPath(source){
|
||||
return path.isAbsolute(source) || /^[a-zA-Z]:[\\/]/.test(source)
|
||||
}
|
||||
|
||||
function deriveFeatureFlags(rawProfile){
|
||||
const legacyKind = normalizeText(rawProfile?.kind)
|
||||
|
||||
@@ -77,7 +87,24 @@ function resolveLegacyServerJar(rawProfile){
|
||||
return null
|
||||
}
|
||||
|
||||
function toStoredProfile(rawProfile){
|
||||
function resolveProfileSourceValue(value, catalogSource){
|
||||
const nextValue = normalizeNullableText(value)
|
||||
if(nextValue == null){
|
||||
return null
|
||||
}
|
||||
|
||||
if(isRemoteSource(nextValue) || nextValue.startsWith('file://') || isAbsoluteFileSystemPath(nextValue)){
|
||||
return nextValue
|
||||
}
|
||||
|
||||
if(isRemoteSource(catalogSource)){
|
||||
return new URL(nextValue, catalogSource).toString()
|
||||
}
|
||||
|
||||
return path.resolve(ROOT_ASSET_PATH, nextValue)
|
||||
}
|
||||
|
||||
function toStoredProfile(rawProfile, catalogSource){
|
||||
const flags = deriveFeatureFlags(rawProfile)
|
||||
const storedProfile = {
|
||||
id: normalizeText(rawProfile.id),
|
||||
@@ -85,19 +112,19 @@ function toStoredProfile(rawProfile){
|
||||
kind: deriveLegacyKind(flags),
|
||||
description: normalizeText(rawProfile.description),
|
||||
details: normalizeText(rawProfile.details),
|
||||
distributionUrl: normalizeNullableText(rawProfile.distributionUrl),
|
||||
distributionUrl: resolveProfileSourceValue(rawProfile.distributionUrl, catalogSource),
|
||||
modsEnabled: flags.modsEnabled,
|
||||
pluginsEnabled: flags.pluginsEnabled,
|
||||
serverEnabled: flags.serverEnabled,
|
||||
worldArchiveUrl: normalizeNullableText(rawProfile.worldArchiveUrl),
|
||||
worldArchiveUrl: resolveProfileSourceValue(rawProfile.worldArchiveUrl, catalogSource),
|
||||
worldDirectoryName: normalizeNullableText(rawProfile.worldDirectoryName),
|
||||
serverJarUrl: resolveLegacyServerJar(rawProfile),
|
||||
serverJarUrl: resolveProfileSourceValue(resolveLegacyServerJar(rawProfile), catalogSource),
|
||||
serverDirectoryName: normalizeText(rawProfile.serverDirectoryName) || 'server',
|
||||
serverPort: Number.isFinite(Number(rawProfile.serverPort)) ? Number(rawProfile.serverPort) : 25565,
|
||||
serverMemoryMb: normalizePositiveInteger(rawProfile.serverMemoryMb, 4096, 512),
|
||||
serverMaxPlayers: normalizePositiveInteger(rawProfile.serverMaxPlayers, 20, 1),
|
||||
serverWhitelistEnabled: normalizeBoolean(rawProfile.serverWhitelistEnabled),
|
||||
artwork: normalizeText(rawProfile.artwork)
|
||||
artwork: resolveProfileSourceValue(rawProfile.artwork, catalogSource) ?? ''
|
||||
}
|
||||
|
||||
if(!storedProfile.serverEnabled){
|
||||
@@ -112,8 +139,8 @@ function toStoredProfile(rawProfile){
|
||||
return storedProfile
|
||||
}
|
||||
|
||||
function normalizeProfile(rawProfile, sourceType = 'catalog'){
|
||||
const storedProfile = toStoredProfile(rawProfile)
|
||||
function normalizeProfile(rawProfile, sourceType = 'catalog', catalogSource = LOCAL_CATALOG_PATH){
|
||||
const storedProfile = toStoredProfile(rawProfile, catalogSource)
|
||||
const launchIssues = []
|
||||
const hostIssues = []
|
||||
|
||||
@@ -168,19 +195,39 @@ exports.getLocalCatalogPath = function(){
|
||||
return LOCAL_CATALOG_PATH
|
||||
}
|
||||
|
||||
exports.getDefaultRemoteCatalogUrl = function(){
|
||||
return DEFAULT_REMOTE_CATALOG_URL
|
||||
}
|
||||
|
||||
function buildCatalogSourceCandidates(configuredSource){
|
||||
const trimmedSource = normalizeNullableText(configuredSource)
|
||||
if(trimmedSource != null){
|
||||
return [trimmedSource]
|
||||
}
|
||||
|
||||
return [DEFAULT_REMOTE_CATALOG_URL, LOCAL_CATALOG_PATH]
|
||||
}
|
||||
|
||||
exports.loadCatalog = async function(){
|
||||
const configuredSource = ConfigManager.getLibraryCatalogSource()
|
||||
const source = configuredSource != null && configuredSource.trim().length > 0 ? configuredSource.trim() : LOCAL_CATALOG_PATH
|
||||
const sourceCandidates = buildCatalogSourceCandidates(configuredSource)
|
||||
let rawCatalog = {
|
||||
version: 1,
|
||||
profiles: []
|
||||
}
|
||||
let sourceError = null
|
||||
let source = sourceCandidates[sourceCandidates.length - 1]
|
||||
|
||||
try {
|
||||
rawCatalog = await readCatalogSource(source)
|
||||
} catch (error) {
|
||||
sourceError = error
|
||||
for(const candidate of sourceCandidates){
|
||||
try {
|
||||
rawCatalog = await readCatalogSource(candidate)
|
||||
source = candidate
|
||||
sourceError = null
|
||||
break
|
||||
} catch (error) {
|
||||
source = candidate
|
||||
sourceError = error
|
||||
}
|
||||
}
|
||||
|
||||
const rawProfiles = Array.isArray(rawCatalog.profiles) ? rawCatalog.profiles : []
|
||||
@@ -191,7 +238,7 @@ exports.loadCatalog = async function(){
|
||||
sourceError,
|
||||
profiles: rawProfiles
|
||||
.filter((profile) => profile != null && typeof profile.id === 'string' && typeof profile.name === 'string')
|
||||
.map((profile) => normalizeProfile(profile, 'catalog'))
|
||||
.map((profile) => normalizeProfile(profile, 'catalog', source))
|
||||
.sort((left, right) => left.name.localeCompare(right.name, 'ko'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +476,11 @@ exports.getLibraryCatalogSource = function(){
|
||||
* @param {string|null} source A remote URL or local path.
|
||||
*/
|
||||
exports.setLibraryCatalogSource = function(source){
|
||||
config.library.catalogSource = source
|
||||
if(typeof source !== 'string' || source.trim().length === 0){
|
||||
config.library.catalogSource = null
|
||||
} else {
|
||||
config.library.catalogSource = source.trim()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -998,3 +1002,22 @@ exports.getAllowPrerelease = function(def = false){
|
||||
exports.setAllowPrerelease = function(allowPrerelease){
|
||||
config.settings.launcher.allowPrerelease = allowPrerelease
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a library catalog source.
|
||||
*
|
||||
* @param {string} source A remote URL or local path.
|
||||
* @returns {boolean} Whether the value is allowed.
|
||||
*/
|
||||
exports.validateLibraryCatalogSource = function(source){
|
||||
const trimmedSource = typeof source === 'string' ? source.trim() : ''
|
||||
if(trimmedSource.length === 0){
|
||||
return true
|
||||
}
|
||||
|
||||
if(trimmedSource.includes('://')){
|
||||
return /^https?:\/\/\S+$/i.test(trimmedSource) || trimmedSource.startsWith('file://')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ logger.info('Loading..')
|
||||
|
||||
// Load ConfigManager
|
||||
ConfigManager.load()
|
||||
CatalogManager.applyConfiguredProfile()
|
||||
|
||||
// Yuck!
|
||||
// TODO Fix this
|
||||
@@ -44,20 +43,32 @@ function onDistroLoad(data){
|
||||
ipcRenderer.send('distributionIndexDone', data != null)
|
||||
}
|
||||
|
||||
// Ensure Distribution is downloaded and cached.
|
||||
DistroAPI.getDistribution()
|
||||
.then(heliosDistro => {
|
||||
logger.info('Loaded distribution index.')
|
||||
async function bootstrapDistribution(){
|
||||
try {
|
||||
await CatalogManager.getInstalledProfiles()
|
||||
} catch (err) {
|
||||
logger.warn('Unable to refresh installed profiles from catalog at startup.', err)
|
||||
}
|
||||
|
||||
onDistroLoad(heliosDistro)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.info('Failed to load an older version of the distribution index.')
|
||||
logger.info('Application cannot run.')
|
||||
logger.error(err)
|
||||
CatalogManager.applyConfiguredProfile()
|
||||
|
||||
onDistroLoad(null)
|
||||
})
|
||||
// Ensure Distribution is downloaded and cached.
|
||||
DistroAPI.getDistribution()
|
||||
.then(heliosDistro => {
|
||||
logger.info('Loaded distribution index.')
|
||||
|
||||
onDistroLoad(heliosDistro)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.info('Failed to load an older version of the distribution index.')
|
||||
logger.info('Application cannot run.')
|
||||
logger.error(err)
|
||||
|
||||
onDistroLoad(null)
|
||||
})
|
||||
}
|
||||
|
||||
bootstrapDistribution()
|
||||
|
||||
// Clean up temp dir incase previous launches ended unexpectedly.
|
||||
fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
ensureJavaDirIsRoot
|
||||
} = require('helios-core/java')
|
||||
|
||||
const CatalogManager = require('./assets/js/catalogmanager')
|
||||
const DropinModUtil = require('./assets/js/dropinmodutil')
|
||||
const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants')
|
||||
const settingsBackButton = document.getElementById('settingsBackButton')
|
||||
@@ -326,22 +327,45 @@ function settingsSaveDisabled(v){
|
||||
settingsNavDone.disabled = v
|
||||
}
|
||||
|
||||
function fullSettingsSave() {
|
||||
async function refreshCatalogSourceViews(){
|
||||
await CatalogManager.getInstalledProfiles()
|
||||
CatalogManager.applyConfiguredProfile()
|
||||
|
||||
if(typeof refreshInstallView === 'function'){
|
||||
await refreshInstallView()
|
||||
}
|
||||
if(typeof refreshLibraryView === 'function'){
|
||||
await refreshLibraryView()
|
||||
}
|
||||
if(typeof refreshSelectedProfileButton === 'function'){
|
||||
refreshSelectedProfileButton()
|
||||
}
|
||||
if(typeof refreshServerStatus === 'function'){
|
||||
await refreshServerStatus(true)
|
||||
}
|
||||
}
|
||||
|
||||
async function fullSettingsSave() {
|
||||
const previousCatalogSource = ConfigManager.getLibraryCatalogSource()
|
||||
saveSettingsValues()
|
||||
saveModConfiguration()
|
||||
ConfigManager.save()
|
||||
saveDropinModConfiguration()
|
||||
saveShaderpackSettings()
|
||||
|
||||
if(previousCatalogSource !== ConfigManager.getLibraryCatalogSource()){
|
||||
await refreshCatalogSourceViews()
|
||||
}
|
||||
}
|
||||
|
||||
/* Closes the settings view and saves all data. */
|
||||
settingsNavDone.onclick = () => {
|
||||
fullSettingsSave()
|
||||
settingsNavDone.onclick = async () => {
|
||||
await fullSettingsSave()
|
||||
switchView(getCurrentView(), VIEWS.landing)
|
||||
}
|
||||
|
||||
settingsBackButton.onclick = () => {
|
||||
fullSettingsSave()
|
||||
settingsBackButton.onclick = async () => {
|
||||
await fullSettingsSave()
|
||||
switchView(getCurrentView(), VIEWS.landing)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user