Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 124 additions & 19 deletions apps/app-frontend/src/store/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import dayjs from 'dayjs'

import { get_project, get_version_many } from '@/helpers/cache.js'
import { get_project, get_version, get_version_many } from '@/helpers/cache.js'
import { add_project_from_version, check_installed } from '@/helpers/profile.js'
import {
add_server_to_profile,
Expand Down Expand Up @@ -49,33 +49,138 @@ export const isVersionCompatible = (version, project, instance) => {
}

export const installVersionDependencies = async (profile, version, onDepInstalling) => {
for (const dep of version.dependencies) {
if (dep.dependency_type !== 'required') continue
// disallow fabric api install on quilt
if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
const projectNames = new Map()
const storeProjectName = (p) => {
if (p?.id && p.title) projectNames.set(p.id, p.title)
}

const visitedVersions = new Set()
const announcedProjects = new Set()
const queuedVersionIds = new Set()
const queuedProjectVersions = new Map()
const queuedInstalls = []
const installedProjectCache = new Map()

const isProjectInstalled = async (projectId) => {
if (!projectId) return false
if (installedProjectCache.has(projectId)) {
return installedProjectCache.get(projectId)
}
const installed = await check_installed(profile.path, projectId)
installedProjectCache.set(projectId, installed)
return installed
}

const queueInstall = async (projectId, resolvedVersion) => {
if (!resolvedVersion?.id) return false

const versionId = resolvedVersion.id
const resolvedProjectId = projectId ?? resolvedVersion.project_id ?? null

if (resolvedProjectId) {
if (await isProjectInstalled(resolvedProjectId)) return false

const existingVersionId = queuedProjectVersions.get(resolvedProjectId)
if (existingVersionId && existingVersionId !== versionId) return false
if (existingVersionId === versionId) return false
}

if (queuedVersionIds.has(versionId)) return false

queuedVersionIds.add(versionId)
if (resolvedProjectId) {
queuedProjectVersions.set(resolvedProjectId, versionId)
}
queuedInstalls.push({ versionId, projectId: resolvedProjectId })
return true
}

const announceDependency = async (projectId, resolvedVersion) => {
if (!onDepInstalling || !projectId) return
if (announcedProjects.has(projectId)) return

const depProject = await get_project(projectId, 'bypass').catch(() => null)
if (!depProject) return

storeProjectName(depProject)
onDepInstalling(depProject, resolvedVersion ?? undefined)
announcedProjects.add(projectId)
}

const resolveDependency = async (dep) => {
let depVersion = null
let depProjectId = dep.project_id ?? null

if (dep.version_id) {
if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
if (dep.project_id && onDepInstalling) {
const depProject = await get_project(dep.project_id, 'bypass').catch(() => null)
if (depProject) onDepInstalling(depProject)
depVersion = await get_version(dep.version_id, 'bypass').catch(() => null)
if (!depVersion) return null

depProjectId = depProjectId ?? depVersion.project_id ?? null
if (depProjectId && !projectNames.has(depProjectId)) {
const p = await get_project(depProjectId, 'bypass').catch(() => null)
storeProjectName(p)
}
await add_project_from_version(profile.path, dep.version_id)
} else {
if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
} else if (dep.project_id) {
const depProject = await get_project(dep.project_id, 'bypass').catch(() => null)
if (!depProject) return null

const depProject = await get_project(dep.project_id, 'bypass')
if (onDepInstalling) onDepInstalling(depProject)
storeProjectName(depProject)

const depVersions = (await get_version_many(depProject.versions, 'bypass')).sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
const depVersions = await get_version_many(depProject.versions, 'bypass').catch(() => [])
depVersion = findPreferredVersion(
depVersions.sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published)),
dep,
profile,
)
if (!depVersion) return null

depProjectId = dep.project_id
} else {
return null
}

return { depVersion, depProjectId }
}

const collectDependenciesForVersion = async (inputVersion) => {
if (!inputVersion?.id || visitedVersions.has(inputVersion.id)) return
visitedVersions.add(inputVersion.id)

const latest = findPreferredVersion(depVersions, dep, profile)
if (latest) {
await add_project_from_version(profile.path, latest.id)
if (inputVersion.project_id && !projectNames.has(inputVersion.project_id)) {
const p = await get_project(inputVersion.project_id, 'bypass').catch(() => null)
storeProjectName(p)
}

for (const dep of inputVersion.dependencies ?? []) {
if (dep.dependency_type !== 'required') continue
if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue

const resolved = await resolveDependency(dep, inputVersion)
if (!resolved) continue

const { depVersion, depProjectId } = resolved
const queued = await queueInstall(depProjectId, depVersion)
if (queued && depProjectId) {
await announceDependency(depProjectId, depVersion)
}

await collectDependenciesForVersion(depVersion)
}
}

await collectDependenciesForVersion(version)

if (queuedInstalls.length === 0) return

const batchSize = 8
for (let i = 0; i < queuedInstalls.length; i += batchSize) {
const batch = queuedInstalls.slice(i, i + batchSize)
await Promise.all(
batch.map(async ({ versionId }) => {
await add_project_from_version(profile.path, versionId)
}),
)
}
}

export const getServerAddress = (javaServer) => {
Expand Down
Loading