import { invoke } from '@tauri-apps/api/core' import { install_job_listener } from './events' import type { InstanceUpgradeResult } from './instance-upgrade' import type { InstanceLink, InstanceLoader, LoaderComponent } from './types' export interface PackLocationVersionId { type: 'fromVersionId' project_id: string version_id: string title: string icon_url?: string | null } export interface PackLocationFile { type: 'fromFile' path: string } export type CreatePackLocation = PackLocationVersionId | PackLocationFile export interface InstallModpackPreview { name: string gameVersion: string modloader: InstanceLoader loaderVersion: string | null adjuncts?: LoaderComponent[] icon?: string | null iconUrl?: string | null link?: InstanceLink | null unknownFile: boolean } export interface InstallCreateInstanceRequest { instanceMode?: 'starlight' | 'local' name: string gameVersion: string loader: InstanceLoader loaderVersion: string | null adjuncts?: LoaderComponent[] iconPath: string | null link?: InstanceLink | null gameDirOverride?: string | null } export interface InstallPostInstallEdit { name?: string | null iconPath?: string | null link?: InstanceLink | null } export type InstallJobStatus = | 'queued' | 'running' | 'canceling' | 'waiting_for_user' | 'succeeded' | 'failed' | 'interrupted' | 'canceled' export type InstallPhaseId = | 'preparing_instance' | 'resolving_pack' | 'downloading_pack_file' | 'reading_pack_manifest' | 'downloading_content' | 'extracting_overrides' | 'resolving_minecraft' | 'resolving_loader' | 'preparing_java' | 'downloading_minecraft' | 'running_loader_processors' | 'creating_backup' | 'staging_content' | 'applying_content' | 'updating_loader' | 'verifying' | 'finalizing' | 'completed' | 'rolling_back' export interface InstallProgress { current: number total: number secondary?: InstallProgressSecondary | null } export interface InstallProgressSecondary { current: number total: number } export interface InstallParallelProgress { phase: InstallPhaseId current: number total: number details: InstallJobSnapshot['details'] } export type InstallJavaStep = | 'resolving' | 'fetching_metadata' | 'downloading' | 'extracting' | 'validating' export interface InstallErrorView { code: string phase?: InstallPhaseId | null message: string api?: { error: string status?: number | null method?: string | null url?: string | null route?: string | null } | null context?: { operation: string source_path?: string | null target_path?: string | null file_path?: string | null entry_path?: string | null urls?: string[] cache_types?: string[] sqlite_code?: string | null expected_hash?: string | null expected_size?: number | null project_id?: string | null version_id?: string | null minecraft_version?: string | null loader?: string | null java_version?: number | null os?: string | null arch?: string | null } | null } export type InstallPauseReason = { type: 'missing_required_content' failed_files: number paths: string[] } export interface InstallJobSnapshot { job_id: string instance_id?: string | null source_instance_id?: string | null instance_deleted: boolean kind: | 'create_instance' | 'create_modpack_instance' | 'import_instance' | 'duplicate_instance' | 'install_existing_instance' | 'install_pack_to_existing_instance' | 'install_content' | 'upgrade_unmanaged_instance' | 'download_java' status: InstallJobStatus execution_mode: 'normal' | 'recovery_validation' provider: 'modrinth' | 'curse_forge' | 'minecraft' | 'java' | 'application' | 'local' target: | { type: 'new_instance'; instance_id?: string | null } | { type: 'existing_instance'; instance_id: string } phase: InstallPhaseId progress?: InstallProgress | null details: | { type: 'empty' } | { type: 'instance'; name: string } | { type: 'minecraft'; game_version: string; loader: InstanceLoader } | { type: 'java'; major_version: number; step: InstallJavaStep } | { type: 'modpack' project_id?: string | null version_id?: string | null title?: string | null } | { type: 'import'; launcher_type: string; instance_folder: string } parallel?: InstallParallelProgress | null display?: { title: string; icon?: string | null } | null error?: InstallErrorView | null rollback_error?: InstallErrorView | null pause_reason?: InstallPauseReason | null upgrade_result?: InstanceUpgradeResult | null created: string modified: string finished?: string | null summary: { files_completed: number files_total?: number | null bytes_downloaded: number bytes_total?: number | null speed_bytes_per_second?: number | null eta_seconds?: number | null source?: string | null fallback_count: number } items: Array<{ id: string name: string project_id?: string | null version_id?: string | null status: | 'queued' | 'worker_started' | 'waiting_for_resource' | 'connecting' | 'downloading' | 'verifying' | 'writing' | 'metadata' | 'waiting_for_database' | 'finalizing' | 'waiting_for_user' | 'completed' | 'skipped' | 'failed' | 'canceled' bytes_downloaded: number bytes_total?: number | null attempt?: number | null max_attempts?: number | null error?: string | null manual_url?: string | null request_url?: string | null source?: string | null }> } export interface DownloadJobListRequest { status?: InstallJobStatus provider?: InstallJobSnapshot['provider'] query?: string cursor?: string limit?: number } export interface DownloadJobPage { jobs: InstallJobSnapshot[] nextCursor?: string | null } export interface MissingModpackContentView { remaining: number files: Array<{ itemId: string path: string expectedSize: number status: InstallJobSnapshot['items'][number]['status'] lastError?: string | null browserUrls: string[] attempt?: number | null maxAttempts?: number | null }> } export interface MissingModpackScanResult { downloadDirectory?: string | null content: MissingModpackContentView importedItemIds: string[] mismatchedItemIds: string[] rejectedItemIds: string[] checkedCandidates: number pendingCandidates: number errors: Array<{ itemId: string; message: string }> job: InstallJobSnapshot } export type DownloadRequestUpdate = | { type: 'started' job_id: string id: string name: string url: string source: string bytes_total?: number | null attempt: number max_attempts: number } | { type: 'progress' job_id: string id: string bytes: number status: | 'worker_started' | 'waiting_for_resource' | 'connecting' | 'downloading' | 'writing' | 'verifying' | 'metadata' | 'waiting_for_database' | 'finalizing' speed_bytes_per_second?: number | null eta_seconds?: number | null } | { type: 'finished'; job_id: string; id: string; bytes: number } | { type: 'failed'; job_id: string; id: string } export type ImportPlanStage = 'resolving' | 'scanning' | 'done' | 'error' export interface ImportPlanCounts { files: number bytes: number } export interface ImportPlanSnapshot { requestId: string stage: ImportPlanStage gameVersion: string | null loader: string | null loaderVersion: string | null importPath: string minecraftRoot: string modCount: number cache: ImportPlanCounts local: ImportPlanCounts network: ImportPlanCounts migrate: ImportPlanCounts error?: string | null } export interface ImportPlanRequest { requestId: string launcherType: string basePath: string instanceFolder: string instancePath?: string | null gameVersion?: string | null loader?: string | null loaderVersion?: string | null } export async function install_get_modpack_preview(location: CreatePackLocation) { return await invoke('plugin:install|install_get_modpack_preview', { location, }) } export async function install_create_instance(request: InstallCreateInstanceRequest) { return await invoke('plugin:install|install_create_instance', { request }) } export async function install_create_modpack_instance( location: CreatePackLocation, postInstallEdit?: InstallPostInstallEdit | null, ) { return await invoke('plugin:install|install_create_modpack_instance', { location, postInstallEdit, }) } export async function install_import_instance( launcherType: string, basePath: string, instanceFolder: string, symlink?: boolean, instancePath?: string, gameVersion?: string | null, loader?: string | null, loaderVersion?: string | null, gameDirOverride?: string | null, ) { return await invoke('plugin:install|install_import_instance', { launcherType, basePath, instanceFolder, instancePath, symlink, gameVersion, loader, loaderVersion, gameDirOverride, }) } export async function install_start_import_plan(request: ImportPlanRequest) { return await invoke('plugin:install|install_start_import_plan', { request }) } export async function install_cancel_import_plan(requestId: string) { return await invoke('plugin:install|install_cancel_import_plan', { requestId }) } export async function install_duplicate_instance(sourceInstanceId: string) { return await invoke('plugin:install|install_duplicate_instance', { sourceInstanceId, }) } export async function install_existing_instance(instanceId: string, force: boolean) { return await invoke('plugin:install|install_existing_instance', { instanceId, force, }) } export async function install_pack_to_existing_instance( instanceId: string, location: CreatePackLocation, postInstallEdit?: InstallPostInstallEdit | null, ) { return await invoke('plugin:install|install_pack_to_existing_instance', { instanceId, location, postInstallEdit, }) } export async function install_job_list(includeFinished: boolean) { return await invoke('plugin:install|install_job_list', { includeFinished }) } export async function install_job_get(jobId: string) { return await invoke('plugin:install|install_job_get', { jobId }) } export async function install_job_retry(jobId: string) { return await invoke('plugin:install|install_job_retry', { jobId }) } export async function install_job_repair_cache_and_retry(jobId: string) { return await invoke('plugin:install|install_job_repair_cache_and_retry', { jobId, }) } export async function install_job_resume(jobId: string) { return await invoke('plugin:install|install_job_resume', { jobId }) } export async function install_job_skip_missing_content(jobId: string) { return await invoke('plugin:install|install_job_skip_missing_content', { jobId, }) } export async function install_job_missing_files(jobId: string) { return await invoke('plugin:install|install_job_missing_files', { jobId, }) } export async function install_job_scan_missing_files(jobId: string, scanDirectory?: string | null) { return await invoke('plugin:install|install_job_scan_missing_files', { jobId, scanDirectory: scanDirectory ?? null, }) } export async function install_job_retry_missing_file(jobId: string, itemId: string) { return await invoke('plugin:install|install_job_retry_missing_file', { jobId, itemId, }) } export async function install_job_import_missing_file( jobId: string, itemId: string, selectedFilePath: string, ) { return await invoke('plugin:install|install_job_import_missing_file', { jobId, itemId, selectedFilePath, }) } export async function install_job_cancel(jobId: string) { return await invoke('plugin:install|install_job_cancel', { jobId }) } export async function install_job_dismiss(jobId: string) { return await invoke('plugin:install|install_job_dismiss', { jobId }) } export async function install_job_support_details(jobId: string) { return await invoke('plugin:install|install_job_support_details', { jobId }) } export async function download_job_list(request: DownloadJobListRequest = {}) { return await invoke('plugin:install|download_job_list', { request }) } export async function download_job_get(jobId: string) { return await invoke('plugin:install|download_job_get', { jobId }) } export async function download_job_retry(jobId: string) { return await invoke('plugin:install|download_job_retry', { jobId }) } export async function download_job_resume(jobId: string) { return await invoke('plugin:install|download_job_resume', { jobId }) } export async function download_job_cancel(jobId: string) { return await invoke('plugin:install|download_job_cancel', { jobId }) } export async function download_job_delete(jobId: string) { return await invoke('plugin:install|download_job_delete', { jobId }) } export async function download_history_clear() { return await invoke('plugin:install|download_history_clear') } export async function download_job_support_details(jobId: string) { return await invoke('plugin:install|download_job_support_details', { jobId }) } export function installJobInstanceId(job: InstallJobSnapshot): string | null { return job.instance_id ?? job.target.instance_id ?? null } export function isInstallJobFinished(status: InstallJobStatus) { return ( status === 'succeeded' || status === 'failed' || status === 'interrupted' || status === 'canceled' ) } function settleInstallJob(job: InstallJobSnapshot) { if (job.status === 'succeeded') return job throw new Error(job.error?.message ?? `Install job ${job.job_id} ${job.status}`) } export async function wait_for_install_job(jobId: string) { const current = await install_job_get(jobId) if (isInstallJobFinished(current.status)) return settleInstallJob(current) return await new Promise((resolve, reject) => { let finished = false let unlisten: (() => void) | null = null const cleanup = () => { if (unlisten) { unlisten() unlisten = null } } const resolveJob = (job: InstallJobSnapshot) => { if (finished || job.job_id !== jobId || !isInstallJobFinished(job.status)) return finished = true cleanup() try { resolve(settleInstallJob(job)) } catch (err) { reject(err) } } const rejectWait = (err: unknown) => { if (finished) return finished = true cleanup() reject(err) } install_job_listener(resolveJob) .then((listener) => { if (finished) { listener() return } unlisten = listener install_job_get(jobId).then(resolveJob).catch(rejectWait) }) .catch(rejectWait) }) }