fix: stabilize mobile uploads and show transfer speed
This commit is contained in:
1 parent
09d66092f5
commit
c0d155c608
12 files changed
+279
-69
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { removeChannelItem } from './channelItems';
|
||||
import { errorMessage } from '../errors';
|
||||
|
||||
import { saveResource } from '../database';
|
||||
|
||||
@@ -10,7 +11,7 @@ import { deviceIdentity } from './presentation';
|
||||
|
||||
|
||||
|
||||
import { flushOutbox, persistPlan } from './transfer';
|
||||
import { flushOutbox, MAX_FRAME_DATA, MOBILE_UPLOAD_FRAME_DATA_BYTES, persistPlan } from './transfer';
|
||||
import { publishWorkspace } from './presentation';
|
||||
|
||||
export async function sendText(resourceId: string, text: string): Promise<{ itemId: string; uploaded: boolean }> {
|
||||
@@ -35,24 +36,66 @@ export async function sendFiles(resourceId: string, files: File[]): Promise<void
|
||||
if (resource.kind !== 'channel' || (!session.can('owner') && (!session.can('write') || !resource.membersCanUpload))) throw new Error('此链接没有上传权限');
|
||||
const device = deviceIdentity();
|
||||
session.deviceName = device.name;
|
||||
const maxFrameData = device.kind === 'phone' ? MOBILE_UPLOAD_FRAME_DATA_BYTES : MAX_FRAME_DATA;
|
||||
for (const file of files) {
|
||||
const ts = Date.now();
|
||||
const plan = session.encryptFile(file, {}, { maxDownloads: resource.maxDownloads ?? 10, dev: device.name, ts });
|
||||
let item: FileItem = {
|
||||
id: plan.itemId, kind: 'file', ts, deviceId: device.name, state: { kind: 'receiving', progress: 0 },
|
||||
name: file.name, mime: file.type || 'application/octet-stream', size: file.size,
|
||||
remaining: resource.maxDownloads || null, preview: previewKindOf(file.type, file.name), gated: true,
|
||||
};
|
||||
resource.localItems = [...(resource.localItems ?? []).filter((entry) => entry.id !== item.id), item];
|
||||
await saveResource(resource);
|
||||
blobs.set(`${resourceId}:${item.id}`, file);
|
||||
publishWorkspace(resourceId, { t: 'item', item });
|
||||
await persistPlan(resource, plan);
|
||||
const uploaded = await flushOutbox(resource, session);
|
||||
item = { ...item, state: uploaded ? { kind: 'complete' } : { kind: 'incomplete' } };
|
||||
resource.localItems = resource.localItems.map((entry) => entry.id === item.id ? item : entry);
|
||||
await saveResource(resource);
|
||||
publishWorkspace(resourceId, { t: 'item', item });
|
||||
let item: FileItem | undefined;
|
||||
try {
|
||||
const plan = session.encryptFile(file, {}, { maxDownloads: resource.maxDownloads ?? 10, dev: device.name, ts, maxFrameData });
|
||||
item = {
|
||||
id: plan.itemId, kind: 'file', ts, deviceId: device.name, state: { kind: 'receiving', progress: 0, phase: 'encrypting' },
|
||||
name: file.name, mime: file.type || 'application/octet-stream', size: file.size,
|
||||
remaining: resource.maxDownloads || null, preview: previewKindOf(file.type, file.name), gated: true,
|
||||
};
|
||||
resource.localItems = [...(resource.localItems ?? []).filter((entry) => entry.id !== item?.id), item];
|
||||
await saveResource(resource);
|
||||
blobs.set(`${resourceId}:${item.id}`, file);
|
||||
publishWorkspace(resourceId, { t: 'item', item });
|
||||
const updateProgress = (progress: number, phase: 'encrypting' | 'uploading', speedBytesPerSecond?: number) => {
|
||||
if (!item) return;
|
||||
item = {
|
||||
...item,
|
||||
state: {
|
||||
kind: 'receiving',
|
||||
progress: Math.max(0, Math.min(0.99, progress)),
|
||||
phase,
|
||||
...(speedBytesPerSecond === undefined ? {} : { speedBytesPerSecond }),
|
||||
},
|
||||
};
|
||||
publishWorkspace(resourceId, { t: 'item', item });
|
||||
};
|
||||
const planned = await persistPlan(resource, plan, (_frames) => {
|
||||
const processed = Math.min(file.size, _frames * MAX_FRAME_DATA);
|
||||
updateProgress(file.size === 0 ? 0.5 : Math.min(0.5, processed / file.size * 0.5), 'encrypting');
|
||||
});
|
||||
const uploadedFrames = new Set<number>();
|
||||
let uploadedBytes = 0;
|
||||
const uploadStartedAt = performance.now();
|
||||
const uploaded = await flushOutbox(resource, session, (frame, loadedBytes, totalBytes, complete) => {
|
||||
if (frame.itemId !== item?.id) return;
|
||||
if (complete && !uploadedFrames.has(frame.index)) {
|
||||
uploadedFrames.add(frame.index);
|
||||
uploadedBytes += totalBytes;
|
||||
}
|
||||
const currentBytes = uploadedBytes + (complete ? 0 : loadedBytes);
|
||||
const elapsed = Math.max(1, performance.now() - uploadStartedAt);
|
||||
updateProgress(0.5 + 0.5 * currentBytes / planned.bytes, 'uploading', currentBytes * 1000 / elapsed);
|
||||
});
|
||||
item = { ...item, state: uploaded ? { kind: 'complete' } : { kind: 'incomplete' } };
|
||||
resource.localItems = resource.localItems.map((entry) => entry.id === item?.id ? item! : entry);
|
||||
await saveResource(resource);
|
||||
publishWorkspace(resourceId, { t: 'item', item });
|
||||
if (!uploaded) publishWorkspace(resourceId, { t: 'error', message: '上传暂停了。重新打开频道并连接网络后,可以继续发送已加密内容。' });
|
||||
} catch (error) {
|
||||
if (item) {
|
||||
item = { ...item, state: { kind: 'incomplete' } };
|
||||
resource.localItems = (resource.localItems ?? []).map((entry) => entry.id === item?.id ? item! : entry);
|
||||
await saveResource(resource);
|
||||
publishWorkspace(resourceId, { t: 'item', item });
|
||||
}
|
||||
publishWorkspace(resourceId, { t: 'error', message: `文件上传失败:${errorMessage(error)}` });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user