fix: 修复整合包失败后的完整重试流程
Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
Sync LobeHub models / sync (push) Has been cancelled
Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
Sync LobeHub models / sync (push) Has been cancelled
This commit is contained in:
@ -83,20 +83,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
|
import { ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { computed, inject, ref, watch } from 'vue'
|
import { computed, inject, ref, watch } from 'vue'
|
||||||
import { useHostedSync } from '@/composables/useHostedSync'
|
|
||||||
import { injectDownloadManager } from '@/providers/download-manager'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import InstanceModeSettings from '@/components/instance/InstanceModeSettings.vue'
|
|
||||||
import HostedPackProgress from '@/components/instance/HostedPackProgress.vue'
|
import HostedPackProgress from '@/components/instance/HostedPackProgress.vue'
|
||||||
|
import InstanceModeSettings from '@/components/instance/InstanceModeSettings.vue'
|
||||||
|
import { markHostedCreationCompleted } from '@/composables/useHostedCreation'
|
||||||
|
import { useHostedSync } from '@/composables/useHostedSync'
|
||||||
import { useInstanceMode } from '@/composables/useInstanceMode'
|
import { useInstanceMode } from '@/composables/useInstanceMode'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type HostedBinding,
|
type HostedBinding,
|
||||||
hostedBinding,
|
hostedBinding,
|
||||||
hostedDefault,
|
hostedDefault,
|
||||||
type HostedPublication,
|
type HostedPublication,
|
||||||
} from '@/helpers/hosted-packs'
|
} from '@/helpers/hosted-packs'
|
||||||
|
import { injectDownloadManager } from '@/providers/download-manager'
|
||||||
const props = defineProps<{ instanceId: string }>()
|
const props = defineProps<{ instanceId: string }>()
|
||||||
const modeQuery = useInstanceMode(() => props.instanceId)
|
const modeQuery = useInstanceMode(() => props.instanceId)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -186,7 +186,8 @@ async function load() {
|
|||||||
async function sync() {
|
async function sync() {
|
||||||
if (syncing.value || !ready.value || modeQuery.data.value !== 'starlight') return
|
if (syncing.value || !ready.value || modeQuery.data.value !== 'starlight') return
|
||||||
loadError.value = ''
|
loadError.value = ''
|
||||||
await task.sync()
|
const result = await task.sync()
|
||||||
|
if (result) markHostedCreationCompleted(props.instanceId)
|
||||||
}
|
}
|
||||||
watch(syncing, (busy, wasBusy) => {
|
watch(syncing, (busy, wasBusy) => {
|
||||||
if (!busy && wasBusy) void load()
|
if (!busy && wasBusy) void load()
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import type { Router } from 'vue-router'
|
|||||||
import {
|
import {
|
||||||
install_job_dismiss,
|
install_job_dismiss,
|
||||||
install_job_repair_cache_and_retry,
|
install_job_repair_cache_and_retry,
|
||||||
install_job_retry,
|
|
||||||
install_job_support_details,
|
install_job_support_details,
|
||||||
installJobInstanceId,
|
installJobInstanceId,
|
||||||
type InstallJobSnapshot,
|
type InstallJobSnapshot,
|
||||||
@ -674,7 +673,7 @@ export async function useInstallJobNotifications(opts: {
|
|||||||
action: async () => {
|
action: async () => {
|
||||||
if (repairingJobIds.value.has(job.job_id)) return
|
if (repairingJobIds.value.has(job.job_id)) return
|
||||||
if (!requiresCacheRepair) {
|
if (!requiresCacheRepair) {
|
||||||
await install_job_retry(job.job_id).catch(opts.handleError)
|
await opts.manager.retry(job.job_id).catch(opts.handleError)
|
||||||
await refresh()
|
await refresh()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { hostedCreate, hostedSync } from '../helpers/hosted-packs.ts'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { hostedCreate } from '../helpers/hosted-packs.ts'
|
||||||
|
import { runHostedSync } from './useHostedSync.ts'
|
||||||
|
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
const installError = ref('')
|
const installError = ref('')
|
||||||
@ -16,6 +18,18 @@ export function forgetHostedCreation(instanceId: string) {
|
|||||||
installError.value = ''
|
installError.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function markHostedCreationCompleted(instanceId: string) {
|
||||||
|
if (createdInstance.value !== instanceId) return
|
||||||
|
installError.value = ''
|
||||||
|
completed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markHostedCreationFailed(instanceId: string, cause: unknown) {
|
||||||
|
if (createdInstance.value !== instanceId) return
|
||||||
|
completed.value = false
|
||||||
|
installError.value = String(cause)
|
||||||
|
}
|
||||||
|
|
||||||
export function useHostedCreation() {
|
export function useHostedCreation() {
|
||||||
function acknowledge(instanceId: string) {
|
function acknowledge(instanceId: string) {
|
||||||
if (completed.value && createdInstance.value === instanceId) {
|
if (completed.value && createdInstance.value === instanceId) {
|
||||||
@ -42,12 +56,15 @@ export function useHostedCreation() {
|
|||||||
if (completed.value) return createdInstance.value
|
if (completed.value) return createdInstance.value
|
||||||
createdInstance.value ??= await hostedCreate(gameDirRoot)
|
createdInstance.value ??= await hostedCreate(gameDirRoot)
|
||||||
const instanceId = createdInstance.value
|
const instanceId = createdInstance.value
|
||||||
await hostedSync(instanceId)
|
await runHostedSync(instanceId)
|
||||||
if (attempt !== generation) return
|
if (attempt !== generation) return
|
||||||
completed.value = true
|
markHostedCreationCompleted(instanceId)
|
||||||
return instanceId
|
return instanceId
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
if (attempt === generation) installError.value = String(cause)
|
if (attempt === generation) {
|
||||||
|
if (createdInstance.value) markHostedCreationFailed(createdInstance.value, cause)
|
||||||
|
else installError.value = String(cause)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
installing.value = false
|
installing.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,59 @@
|
|||||||
import { computed, reactive } from 'vue'
|
import { computed, reactive } from 'vue'
|
||||||
|
|
||||||
import { hostedSync, type HostedSyncResult } from '../helpers/hosted-packs.ts'
|
import { hostedSync, type HostedSyncResult } from '../helpers/hosted-packs.ts'
|
||||||
|
|
||||||
const tasks = reactive(
|
const tasks = reactive(
|
||||||
new Map<string, { busy: boolean; result: HostedSyncResult | null; error: string }>(),
|
new Map<string, { busy: boolean; result: HostedSyncResult | null; error: string }>(),
|
||||||
)
|
)
|
||||||
|
const inFlight = new Map<string, Promise<HostedSyncResult>>()
|
||||||
|
|
||||||
|
function getTask(instanceId: string) {
|
||||||
|
if (!tasks.has(instanceId)) tasks.set(instanceId, { busy: false, result: null, error: '' })
|
||||||
|
return tasks.get(instanceId)!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run one authoritative StarLight synchronization per instance. Every retry
|
||||||
|
* surface uses this function so the full pack transaction, its progress and
|
||||||
|
* its final result cannot diverge between pages.
|
||||||
|
*/
|
||||||
|
export function runHostedSync(instanceId: string): Promise<HostedSyncResult> {
|
||||||
|
const existing = inFlight.get(instanceId)
|
||||||
|
if (existing) return existing
|
||||||
|
|
||||||
|
const current = getTask(instanceId)
|
||||||
|
current.busy = true
|
||||||
|
current.error = ''
|
||||||
|
current.result = null
|
||||||
|
const request = hostedSync(instanceId)
|
||||||
|
.then((result) => {
|
||||||
|
current.result = result
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
current.error = String(error)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
current.busy = false
|
||||||
|
inFlight.delete(instanceId)
|
||||||
|
})
|
||||||
|
inFlight.set(instanceId, request)
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
export function useHostedSync(instanceId: () => string) {
|
export function useHostedSync(instanceId: () => string) {
|
||||||
const task = computed(() => {
|
const task = computed(() => {
|
||||||
const id = instanceId()
|
return getTask(instanceId())
|
||||||
if (!tasks.has(id)) tasks.set(id, { busy: false, result: null, error: '' })
|
|
||||||
return tasks.get(id)!
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function sync() {
|
async function sync() {
|
||||||
const id = instanceId()
|
const id = instanceId()
|
||||||
const current = task.value
|
if (task.value.busy) return undefined
|
||||||
if (current.busy) return
|
|
||||||
current.busy = true
|
|
||||||
current.error = ''
|
|
||||||
current.result = null
|
|
||||||
try {
|
try {
|
||||||
current.result = await hostedSync(id)
|
return await runHostedSync(id)
|
||||||
} catch (error) {
|
} catch {
|
||||||
current.error = String(error)
|
return undefined
|
||||||
} finally {
|
|
||||||
current.busy = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
124
apps/app-frontend/src/helpers/hosted-install-retry.test.ts
Normal file
124
apps/app-frontend/src/helpers/hosted-install-retry.test.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
import {
|
||||||
|
hostedRetryRoute,
|
||||||
|
hostedRuntimeRetryInstanceId,
|
||||||
|
retryInstallJob,
|
||||||
|
} from './hosted-install-retry.ts'
|
||||||
|
import type { InstallJobSnapshot } from './install.ts'
|
||||||
|
|
||||||
|
function job(overrides: Partial<InstallJobSnapshot> = {}): InstallJobSnapshot {
|
||||||
|
return {
|
||||||
|
job_id: 'job',
|
||||||
|
instance_id: 'local:instance',
|
||||||
|
source_instance_id: null,
|
||||||
|
instance_deleted: false,
|
||||||
|
kind: 'install_existing_instance',
|
||||||
|
status: 'failed',
|
||||||
|
execution_mode: 'normal',
|
||||||
|
provider: 'minecraft',
|
||||||
|
target: { type: 'existing_instance', instance_id: 'local:instance' },
|
||||||
|
phase: 'downloading_minecraft',
|
||||||
|
progress: null,
|
||||||
|
details: { type: 'empty' },
|
||||||
|
parallel: null,
|
||||||
|
display: null,
|
||||||
|
error: null,
|
||||||
|
rollback_error: null,
|
||||||
|
pause_reason: null,
|
||||||
|
upgrade_result: null,
|
||||||
|
created: '2026-09-18T00:00:00Z',
|
||||||
|
modified: '2026-09-18T00:00:00Z',
|
||||||
|
finished: '2026-09-18T00:00:01Z',
|
||||||
|
summary: {
|
||||||
|
files_completed: 0,
|
||||||
|
files_total: null,
|
||||||
|
bytes_downloaded: 0,
|
||||||
|
bytes_total: null,
|
||||||
|
speed_bytes_per_second: null,
|
||||||
|
eta_seconds: null,
|
||||||
|
source: null,
|
||||||
|
fallback_count: 0,
|
||||||
|
},
|
||||||
|
items: [],
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('StarLight runtime retries keep the existing instance identity', () => {
|
||||||
|
assert.equal(hostedRuntimeRetryInstanceId(job()), 'local:instance')
|
||||||
|
assert.equal(
|
||||||
|
hostedRuntimeRetryInstanceId(
|
||||||
|
job({ instance_id: null, target: { type: 'existing_instance', instance_id: 'target' } }),
|
||||||
|
),
|
||||||
|
'target',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('deleted and unrelated install jobs do not enter the StarLight runtime retry path', () => {
|
||||||
|
assert.equal(hostedRuntimeRetryInstanceId(job({ instance_deleted: true })), null)
|
||||||
|
assert.equal(hostedRuntimeRetryInstanceId(job({ kind: 'create_instance' })), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the hosted retry link targets the current instance content route', () => {
|
||||||
|
assert.equal(
|
||||||
|
hostedRetryRoute('local:instance/with space'),
|
||||||
|
'/instance/local%3Ainstance%2Fwith%20space',
|
||||||
|
)
|
||||||
|
assert.doesNotMatch(hostedRetryRoute('local:instance'), /\/mods$/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('a StarLight runtime failure retries the complete hosted transaction only', async () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
const result = await retryInstallJob(job(), {
|
||||||
|
getInstanceMode: async (instanceId) => {
|
||||||
|
calls.push(`mode:${instanceId}`)
|
||||||
|
return 'starlight'
|
||||||
|
},
|
||||||
|
retryHosted: async (instanceId, sourceJobId) => {
|
||||||
|
calls.push(`hosted:${instanceId}:${sourceJobId}`)
|
||||||
|
},
|
||||||
|
retryGeneric: async (jobId) => {
|
||||||
|
calls.push(`generic:${jobId}`)
|
||||||
|
return job()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.equal(result, null)
|
||||||
|
assert.deepEqual(calls, ['mode:local:instance', 'hosted:local:instance:job'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('a Local runtime failure keeps the generic retry path', async () => {
|
||||||
|
const replacement = job({ job_id: 'replacement', status: 'queued' })
|
||||||
|
const calls: string[] = []
|
||||||
|
const result = await retryInstallJob(job(), {
|
||||||
|
getInstanceMode: async () => 'local',
|
||||||
|
retryHosted: async () => {
|
||||||
|
calls.push('hosted')
|
||||||
|
},
|
||||||
|
retryGeneric: async (jobId) => {
|
||||||
|
calls.push(`generic:${jobId}`)
|
||||||
|
return replacement
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.equal(result, replacement)
|
||||||
|
assert.deepEqual(calls, ['generic:job'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mode lookup failures never fall back to an incomplete generic retry', async () => {
|
||||||
|
let genericCalls = 0
|
||||||
|
await assert.rejects(
|
||||||
|
retryInstallJob(job(), {
|
||||||
|
getInstanceMode: async () => {
|
||||||
|
throw new Error('instance unavailable')
|
||||||
|
},
|
||||||
|
retryHosted: async () => {},
|
||||||
|
retryGeneric: async () => {
|
||||||
|
genericCalls++
|
||||||
|
return job()
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
/instance unavailable/,
|
||||||
|
)
|
||||||
|
assert.equal(genericCalls, 0)
|
||||||
|
})
|
||||||
34
apps/app-frontend/src/helpers/hosted-install-retry.ts
Normal file
34
apps/app-frontend/src/helpers/hosted-install-retry.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { InstallJobSnapshot } from './install.ts'
|
||||||
|
|
||||||
|
interface InstallRetryDependencies {
|
||||||
|
getInstanceMode: (instanceId: string) => Promise<'starlight' | 'local'>
|
||||||
|
retryHosted: (instanceId: string, sourceJobId: string) => Promise<void>
|
||||||
|
retryGeneric: (jobId: string) => Promise<InstallJobSnapshot>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minecraft installation jobs started by StarLight synchronization are only
|
||||||
|
* one stage of the full operation. Retrying that stage alone leaves the
|
||||||
|
* pending pack journal unapplied, so these jobs must resume through
|
||||||
|
* `hostedSync` instead of the generic install-job retry command.
|
||||||
|
*/
|
||||||
|
export function hostedRuntimeRetryInstanceId(job: InstallJobSnapshot): string | null {
|
||||||
|
if (job.kind !== 'install_existing_instance' || job.instance_deleted) return null
|
||||||
|
return job.instance_id ?? job.target.instance_id ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hostedRetryRoute(instanceId: string): string {
|
||||||
|
return `/instance/${encodeURIComponent(instanceId)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function retryInstallJob(
|
||||||
|
job: InstallJobSnapshot,
|
||||||
|
dependencies: InstallRetryDependencies,
|
||||||
|
): Promise<InstallJobSnapshot | null> {
|
||||||
|
const instanceId = hostedRuntimeRetryInstanceId(job)
|
||||||
|
if (instanceId && (await dependencies.getInstanceMode(instanceId)) === 'starlight') {
|
||||||
|
await dependencies.retryHosted(instanceId, job.job_id)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return dependencies.retryGeneric(job.job_id)
|
||||||
|
}
|
||||||
@ -99,7 +99,7 @@
|
|||||||
/>
|
/>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-else
|
v-else
|
||||||
:to="`/instance/${encodeURIComponent(bar.bar_type?.instance_id ?? '')}/mods`"
|
:to="hostedRetryRoute(bar.bar_type?.instance_id ?? '')"
|
||||||
class="mt-3 inline-block text-brand hover:underline"
|
class="mt-3 inline-block text-brand hover:underline"
|
||||||
>
|
>
|
||||||
{{ formatMessage(messages.hostedRetry) }}
|
{{ formatMessage(messages.hostedRetry) }}
|
||||||
@ -471,6 +471,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import MissingModpackContentModal from '@/components/ui/modal/MissingModpackContentModal.vue'
|
import MissingModpackContentModal from '@/components/ui/modal/MissingModpackContentModal.vue'
|
||||||
import { listPendingCurseForgeManualDownloads } from '@/helpers/curseforge'
|
import { listPendingCurseForgeManualDownloads } from '@/helpers/curseforge'
|
||||||
import type { CurseForgeManualDownloadItem } from '@/helpers/curseforge-manual'
|
import type { CurseForgeManualDownloadItem } from '@/helpers/curseforge-manual'
|
||||||
|
import { hostedRetryRoute } from '@/helpers/hosted-install-retry'
|
||||||
import {
|
import {
|
||||||
download_job_support_details,
|
download_job_support_details,
|
||||||
type InstallJobSnapshot,
|
type InstallJobSnapshot,
|
||||||
|
|||||||
@ -1,16 +1,22 @@
|
|||||||
import { createContext } from '@modrinth/ui'
|
import { createContext } from '@modrinth/ui'
|
||||||
import { computed, type ComputedRef, type Ref, ref } from 'vue'
|
import { computed, type ComputedRef, type Ref, ref } from 'vue'
|
||||||
|
|
||||||
|
import {
|
||||||
|
forgetHostedCreation,
|
||||||
|
markHostedCreationCompleted,
|
||||||
|
markHostedCreationFailed,
|
||||||
|
} from '@/composables/useHostedCreation'
|
||||||
|
import { runHostedSync } from '@/composables/useHostedSync'
|
||||||
import { setCurseForgeManualDownloads } from '@/helpers/curseforge-manual'
|
import { setCurseForgeManualDownloads } from '@/helpers/curseforge-manual'
|
||||||
import { onHostedPackAttemptStarted } from '@/helpers/hosted-packs'
|
|
||||||
import { createHostedDownloadFailures } from '@/helpers/hosted-download-failures'
|
|
||||||
import { forgetHostedCreation } from '@/composables/useHostedCreation'
|
|
||||||
import {
|
import {
|
||||||
download_request_listener,
|
download_request_listener,
|
||||||
install_job_listener,
|
install_job_listener,
|
||||||
loading_listener,
|
|
||||||
instance_listener,
|
instance_listener,
|
||||||
|
loading_listener,
|
||||||
} from '@/helpers/events'
|
} from '@/helpers/events'
|
||||||
|
import { createHostedDownloadFailures } from '@/helpers/hosted-download-failures'
|
||||||
|
import { retryInstallJob } from '@/helpers/hosted-install-retry'
|
||||||
|
import { getInstanceMode, onHostedPackAttemptStarted } from '@/helpers/hosted-packs'
|
||||||
import {
|
import {
|
||||||
download_history_clear,
|
download_history_clear,
|
||||||
download_job_cancel,
|
download_job_cancel,
|
||||||
@ -50,6 +56,7 @@ export interface DownloadManager {
|
|||||||
refresh: () => Promise<void>
|
refresh: () => Promise<void>
|
||||||
cancel: (jobId: string) => Promise<void>
|
cancel: (jobId: string) => Promise<void>
|
||||||
retry: (jobId: string) => Promise<void>
|
retry: (jobId: string) => Promise<void>
|
||||||
|
retryHosted: (instanceId: string, sourceJobId?: string) => Promise<void>
|
||||||
resume: (jobId: string) => Promise<void>
|
resume: (jobId: string) => Promise<void>
|
||||||
skipMissingContent: (jobId: string) => Promise<void>
|
skipMissingContent: (jobId: string) => Promise<void>
|
||||||
remove: (jobId: string) => Promise<void>
|
remove: (jobId: string) => Promise<void>
|
||||||
@ -376,8 +383,29 @@ export function createDownloadManager(handleError: (error: unknown) => void): Do
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function retry(jobId: string) {
|
async function retry(jobId: string) {
|
||||||
const job = await download_job_retry(jobId)
|
const original =
|
||||||
await reconcileJob(job)
|
jobs.value.find((candidate) => candidate.job_id === jobId) ?? (await download_job_get(jobId))
|
||||||
|
const job = await retryInstallJob(original, {
|
||||||
|
getInstanceMode,
|
||||||
|
retryHosted,
|
||||||
|
retryGeneric: download_job_retry,
|
||||||
|
})
|
||||||
|
if (job) await reconcileJob(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retryHosted(instanceId: string, sourceJobId?: string) {
|
||||||
|
try {
|
||||||
|
await runHostedSync(instanceId)
|
||||||
|
} catch (error) {
|
||||||
|
markHostedCreationFailed(instanceId, error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
markHostedCreationCompleted(instanceId)
|
||||||
|
if (sourceJobId) {
|
||||||
|
await download_job_delete(sourceJobId).catch(handleError)
|
||||||
|
jobs.value = jobs.value.filter((job) => job.job_id !== sourceJobId)
|
||||||
|
}
|
||||||
|
await refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resume(jobId: string) {
|
async function resume(jobId: string) {
|
||||||
@ -459,6 +487,7 @@ export function createDownloadManager(handleError: (error: unknown) => void): Do
|
|||||||
refresh,
|
refresh,
|
||||||
cancel,
|
cancel,
|
||||||
retry,
|
retry,
|
||||||
|
retryHosted,
|
||||||
resume,
|
resume,
|
||||||
skipMissingContent,
|
skipMissingContent,
|
||||||
remove,
|
remove,
|
||||||
|
|||||||
Reference in New Issue
Block a user