Skip to content

Commit d0afdbc

Browse files
authored
fix: dont file download in mem files tab (#7891)
1 parent 9bb261c commit d0afdbc

15 files changed

Lines changed: 615 additions & 26 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎apps/app-frontend/src/components/ui/download-manager/download-manager-job.vue‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const cancelLabel = computed(() =>
6565
formatMessage(
6666
needsAttention.value
6767
? messages.dismiss
68-
: props.job.kind === 'debug-export'
68+
: props.job.kind === 'external-file' || props.job.kind === 'debug-export'
6969
? commonMessages.cancelButton
7070
: messages.cancel,
7171
),
@@ -105,7 +105,9 @@ const instanceLink = computed(() =>
105105
class="flex size-9 shrink-0 items-center justify-center rounded-xl border border-solid border-surface-5 bg-purple/10"
106106
>
107107
<component
108-
:is="job.kind === 'debug-export' ? DownloadIcon : BoxIcon"
108+
:is="
109+
job.kind === 'external-file' || job.kind === 'debug-export' ? DownloadIcon : BoxIcon
110+
"
109111
class="size-6 text-primary"
110112
aria-hidden="true"
111113
/>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { reactive, ref } from 'vue'
2+
3+
export interface ExternalFileDownloadProgress {
4+
stage: 'waiting' | 'downloading' | 'saving'
5+
downloadedBytes: number
6+
totalBytes: number | null
7+
}
8+
9+
export interface ExternalFileDownloadTask {
10+
id: string
11+
filename: string
12+
serverId?: string
13+
serverName?: string
14+
status: 'running' | 'succeeded' | 'failed' | 'canceled'
15+
stage: 'preparing' | ExternalFileDownloadProgress['stage']
16+
downloadedBytes: number
17+
totalBytes: number | null
18+
rate: number
19+
lastRead: number
20+
createdAt: string
21+
finishedAt?: string
22+
error?: string
23+
canceling: boolean
24+
cancel: () => Promise<void>
25+
}
26+
27+
export const externalFileDownloads = ref(new Map<string, ExternalFileDownloadTask>())
28+
29+
export function trackExternalFileDownload(
30+
id: string,
31+
filename: string,
32+
onCancel: () => Promise<void>,
33+
server: { serverId?: string; serverName?: string } = {},
34+
) {
35+
const task = reactive<ExternalFileDownloadTask>({
36+
id,
37+
filename,
38+
...server,
39+
status: 'running',
40+
stage: 'preparing',
41+
downloadedBytes: 0,
42+
totalBytes: null,
43+
rate: 0,
44+
lastRead: performance.now(),
45+
createdAt: new Date().toISOString(),
46+
canceling: false,
47+
async cancel() {
48+
if (task.status !== 'running' || task.canceling) return
49+
task.canceling = true
50+
try {
51+
await onCancel()
52+
if (task.status === 'running') finish('canceled')
53+
} finally {
54+
task.canceling = false
55+
}
56+
},
57+
})
58+
externalFileDownloads.value.set(id, task)
59+
let previousBytes = 0
60+
let previousTime = performance.now()
61+
62+
function finish(status: ExternalFileDownloadTask['status']) {
63+
task.status = status
64+
task.finishedAt = new Date().toISOString()
65+
task.rate = 0
66+
}
67+
68+
return {
69+
update(progress: ExternalFileDownloadProgress) {
70+
if (task.status !== 'running' || task.canceling) return
71+
const now = performance.now()
72+
if (progress.stage !== 'downloading' || progress.downloadedBytes < previousBytes) {
73+
task.rate = 0
74+
} else if (progress.downloadedBytes > previousBytes) {
75+
const rate =
76+
((progress.downloadedBytes - previousBytes) * 1000) / Math.max(1, now - previousTime)
77+
task.rate = task.rate ? task.rate * 0.6 + rate * 0.4 : rate
78+
task.lastRead = now
79+
}
80+
task.stage = progress.stage
81+
task.downloadedBytes = progress.downloadedBytes
82+
task.totalBytes = progress.totalBytes
83+
previousBytes = progress.downloadedBytes
84+
previousTime = now
85+
},
86+
succeed(filename: string) {
87+
task.filename = filename
88+
finish('succeeded')
89+
},
90+
fail(error: string) {
91+
task.error = error
92+
finish('failed')
93+
},
94+
canceled() {
95+
finish('canceled')
96+
},
97+
}
98+
}
99+
100+
export function getExternalFileDownloadRate(task: ExternalFileDownloadTask, now: number) {
101+
return task.status === 'running' &&
102+
task.stage === 'downloading' &&
103+
!task.canceling &&
104+
now - task.lastRead < 5000
105+
? task.rate
106+
: 0
107+
}

‎apps/app-frontend/src/components/ui/download-manager/use-download-manager.ts‎

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,19 @@ import { injectAppEvents } from '@/providers/app-events'
2626

2727
import { debugInfoExports, type DebugInfoExportTask } from './debug-info-export'
2828
import { createDownloadTransferTracker } from './download-transfer'
29+
import {
30+
externalFileDownloads,
31+
type ExternalFileDownloadTask,
32+
getExternalFileDownloadRate,
33+
} from './external-file-downloads'
2934
import { createInstallJobProgressTracker } from './install-job-progress'
3035
import { storeVerificationTask } from './store-verification'
3136
import { useInstallJobDisplay } from './use-install-job-display'
3237

3338
export interface DownloadManagerJob {
3439
id: string
35-
kind?: 'debug-export'
40+
kind?: 'external-file' | 'debug-export'
41+
serverId?: string
3642
createdAt?: string
3743
instanceId: string | null
3844
status: InstallJobSnapshot['status']
@@ -69,6 +75,13 @@ const verificationMessages = defineMessages({
6975
},
7076
})
7177

78+
const fileMessages = defineMessages({
79+
preparing: { id: 'app.file-download.preparing', defaultMessage: 'Preparing download…' },
80+
waiting: { id: 'app.file-download.waiting', defaultMessage: 'Waiting for the server…' },
81+
canceling: { id: 'app.file-download.canceling', defaultMessage: 'Canceling download…' },
82+
canceled: { id: 'app.action-bar.install.summary.canceled', defaultMessage: 'Canceled' },
83+
})
84+
7285
const debugExportMessages = defineMessages({
7386
canceled: { id: 'app.action-bar.install.summary.canceled', defaultMessage: 'Canceled' },
7487
title: {
@@ -167,12 +180,14 @@ export function useDownloadManager() {
167180
const task = storeVerificationTask.value
168181
return task ? [buildVerificationRow(task)] : []
169182
})
183+
const fileRows = computed(() => [...externalFileDownloads.value.values()].map(buildFileRow))
170184
const debugExportRows = computed(() =>
171185
[...debugInfoExports.value.values()].map(buildDebugExportRow),
172186
)
173187
const allRows = computed(() => [
174188
...rows.value,
175189
...verificationRows.value,
190+
...fileRows.value,
176191
...debugExportRows.value,
177192
])
178193

@@ -197,10 +212,11 @@ export function useDownloadManager() {
197212
)
198213
const rate = computed(() =>
199214
display.formatRate(
200-
activeJobs.value.reduce(
201-
(total, job) => total + (transfer.get(job.id, now.value).rate ?? 0),
202-
0,
203-
),
215+
activeJobs.value.reduce((total, job) => {
216+
const file = externalFileDownloads.value.get(job.id)
217+
const fileRate = file ? getExternalFileDownloadRate(file, now.value) : null
218+
return total + (fileRate ?? transfer.get(job.id, now.value).rate ?? 0)
219+
}, 0),
204220
),
205221
)
206222

@@ -246,6 +262,68 @@ export function useDownloadManager() {
246262
}
247263
}
248264

265+
function buildFileRow(task: ExternalFileDownloadTask): DownloadManagerJob {
266+
const rate = getExternalFileDownloadRate(task, now.value)
267+
const progress = task.totalBytes ? Math.min(0.99, task.downloadedBytes / task.totalBytes) : 0
268+
const text =
269+
task.error ??
270+
formatMessage(
271+
task.canceling
272+
? fileMessages.canceling
273+
: task.status === 'canceled'
274+
? fileMessages.canceled
275+
: task.status === 'succeeded'
276+
? commonMessages.savedLabel
277+
: task.stage === 'preparing'
278+
? fileMessages.preparing
279+
: task.stage === 'waiting'
280+
? fileMessages.waiting
281+
: task.stage === 'saving'
282+
? commonMessages.savingButton
283+
: commonMessages.downloadingButton,
284+
)
285+
286+
return {
287+
id: task.id,
288+
kind: 'external-file',
289+
serverId: task.serverId,
290+
createdAt: task.createdAt,
291+
instanceId: null,
292+
status: task.status,
293+
paused: false,
294+
canceling: task.canceling,
295+
canPause: false,
296+
canCancel: task.status === 'running' && task.stage !== 'saving',
297+
title: task.serverName ?? task.filename,
298+
iconUrl: null,
299+
text,
300+
taskType: task.filename,
301+
finishedAt: task.finishedAt,
302+
progress,
303+
overallProgress: task.status === 'succeeded' ? 1 : progress,
304+
progressLabel:
305+
task.status === 'running' && (task.stage === 'downloading' || task.stage === 'saving')
306+
? [
307+
task.totalBytes
308+
? `${formatBytes(task.downloadedBytes)} / ${formatBytes(task.totalBytes)}`
309+
: formatBytes(task.downloadedBytes),
310+
display.formatRate(rate),
311+
]
312+
.filter(Boolean)
313+
.join(' · ')
314+
: '',
315+
waiting: task.stage !== 'downloading' || !task.totalBytes,
316+
eta:
317+
rate && task.totalBytes
318+
? display.formatEta((task.totalBytes - task.downloadedBytes) / rate)
319+
: '',
320+
canRetry: false,
321+
canCopyDetails: !!task.error,
322+
copied: copiedJobs.value.has(task.id),
323+
busy: busyJobs.value.has(task.id),
324+
}
325+
}
326+
249327
function buildVerificationRow(
250328
task: NonNullable<typeof storeVerificationTask.value>,
251329
): DownloadManagerJob {
@@ -387,6 +465,11 @@ export function useDownloadManager() {
387465
await runAction(id, debugExport.cancel)
388466
return
389467
}
468+
const file = externalFileDownloads.value.get(id)
469+
if (file) {
470+
await runAction(id, file.cancel)
471+
return
472+
}
390473
if (!jobs.value.get(id)?.can_cancel) return
391474
await runJobAction(id, () => install_job_cancel(id))
392475
}
@@ -403,6 +486,11 @@ export function useDownloadManager() {
403486
if (debugExport.status !== 'running') debugInfoExports.value.delete(id)
404487
return
405488
}
489+
const file = externalFileDownloads.value.get(id)
490+
if (file) {
491+
if (file.status !== 'running') externalFileDownloads.value.delete(id)
492+
return
493+
}
406494
if (storeVerificationTask.value?.id === id) {
407495
if (storeVerificationTask.value.status !== 'running') storeVerificationTask.value = null
408496
return
@@ -424,10 +512,8 @@ export function useDownloadManager() {
424512

425513
async function copyDetails(id: string) {
426514
await runAction(id, async () => {
427-
const debugExport = debugInfoExports.value.get(id)
428-
const details = debugExport
429-
? (debugExport.error ?? '')
430-
: await install_job_support_details(id)
515+
const file = externalFileDownloads.value.get(id) ?? debugInfoExports.value.get(id)
516+
const details = file ? (file.error ?? '') : await install_job_support_details(id)
431517
if (disposed) return
432518
await navigator.clipboard.writeText(details)
433519
if (disposed) return

‎apps/app-frontend/src/locales/en-US/index.json‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,15 @@
479479
"app.features-settings.sync-across-devices.title": {
480480
"message": "Sync features across devices"
481481
},
482+
"app.file-download.canceling": {
483+
"message": "Canceling download…"
484+
},
485+
"app.file-download.preparing": {
486+
"message": "Preparing download…"
487+
},
488+
"app.file-download.waiting": {
489+
"message": "Waiting for the server…"
490+
},
482491
"app.home.jump-back-in.actions.label": {
483492
"message": "Instance actions"
484493
},

‎apps/app-frontend/src/providers/setup.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { InstanceIconConfig } from '@/helpers/types'
66
import type { AppEvents } from './app-events'
77
import { setupOnboardingChecklistProvider } from './onboarding-checklist'
88
import { setupCreationModal } from './setup/creation-modal'
9+
import { setupFileDownloadProvider } from './setup/file-download'
910
import { setupFileDropProvider } from './setup/file-drop'
1011
import { setupFilePickerProvider } from './setup/file-picker'
1112
import { setupImageViewerEditorProvider } from './setup/image-viewer-editor'
@@ -24,6 +25,7 @@ export function setupProviders(
2425
const tags = setupTagsProvider(notificationManager)
2526
setupFileDropProvider()
2627
setupFilePickerProvider()
28+
setupFileDownloadProvider(client)
2729
setupImageViewerEditorProvider()
2830
setupInstanceImportProvider(notificationManager)
2931
const onboardingChecklist = setupOnboardingChecklistProvider(appEvents)

0 commit comments

Comments
 (0)