fix: Bug - Sending files and attachment issues (gallery load and speed)
Route small images through in-memory receive instead of serialized disk chunk-acks, and improve gallery hydration for local copies and pending downloads so thumbnails display without minutes-long progress stalls. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+65
-8
@@ -52,6 +52,7 @@ describe('AttachmentTransferService', () => {
|
||||
getFileUrl: ReturnType<typeof vi.fn>;
|
||||
resolveExistingPath: ReturnType<typeof vi.fn>;
|
||||
resolveLegacyImagePath: ReturnType<typeof vi.fn>;
|
||||
getFileSize: ReturnType<typeof vi.fn>;
|
||||
appendBase64: ReturnType<typeof vi.fn>;
|
||||
appendBytes: ReturnType<typeof vi.fn>;
|
||||
createWritableFile: ReturnType<typeof vi.fn>;
|
||||
@@ -94,6 +95,7 @@ describe('AttachmentTransferService', () => {
|
||||
getFileUrl: vi.fn(async () => null),
|
||||
resolveExistingPath: vi.fn(async () => null),
|
||||
resolveLegacyImagePath: vi.fn(async () => null),
|
||||
getFileSize: vi.fn(async () => null),
|
||||
appendBase64: vi.fn(async () => true),
|
||||
appendBytes: vi.fn(async () => true),
|
||||
createWritableFile: vi.fn(async () => '/appdata/server/room/files/file-1'),
|
||||
@@ -391,11 +393,43 @@ describe('AttachmentTransferService', () => {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
it('streams playable media to disk when the store supports streaming', async () => {
|
||||
it('assembles small images in memory even when the store supports disk streaming', async () => {
|
||||
attachmentStorage.canStreamToDisk.mockReturnValue(true);
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingVideo(3);
|
||||
const attachment = registerIncomingAttachment(9);
|
||||
|
||||
service.handleFileChunk(chunkPayload(0, 3, [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]));
|
||||
|
||||
service.handleFileChunk(chunkPayload(1, 3, [
|
||||
4,
|
||||
5,
|
||||
6
|
||||
]));
|
||||
|
||||
service.handleFileChunk(chunkPayload(2, 3, [
|
||||
7,
|
||||
8,
|
||||
9
|
||||
]));
|
||||
|
||||
await vi.waitFor(() => expect(attachment.available).toBe(true));
|
||||
|
||||
expect(attachmentStorage.createWritableFile).not.toHaveBeenCalled();
|
||||
expect(attachmentStorage.appendBytes).not.toHaveBeenCalled();
|
||||
expect(persistence.saveFileToDisk).toHaveBeenCalledTimes(1);
|
||||
expect(attachment.objectUrl).toMatch(/^blob:/);
|
||||
});
|
||||
|
||||
it('streams oversized playable media to disk when the store supports streaming', async () => {
|
||||
attachmentStorage.canStreamToDisk.mockReturnValue(true);
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingVideo(12 * 1024 * 1024);
|
||||
|
||||
service.handleFileChunk(chunkPayload(0, 1, [
|
||||
1,
|
||||
@@ -517,11 +551,11 @@ describe('AttachmentTransferService', () => {
|
||||
expect(runtimeStore.getChunkBuffer(`${MESSAGE_ID}:${FILE_ID}`)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not hydrate image blobs after a disk-streamed download completes', async () => {
|
||||
it('does not hydrate image blobs after a disk-streamed oversized download completes', async () => {
|
||||
attachmentStorage.canStreamToDisk.mockReturnValue(true);
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingAttachment(3);
|
||||
const attachment = registerIncomingAttachment(12 * 1024 * 1024);
|
||||
|
||||
service.handleFileChunk(chunkPayload(0, 1, [
|
||||
1,
|
||||
@@ -537,12 +571,12 @@ describe('AttachmentTransferService', () => {
|
||||
expect(persistence.ensureInlineDisplayObjectUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('hydrates playable media with a native file url after disk-streamed download completes', async () => {
|
||||
it('hydrates playable media with a native file url after disk-streamed oversized download completes', async () => {
|
||||
attachmentStorage.canStreamToDisk.mockReturnValue(true);
|
||||
attachmentStorage.getFileUrl.mockResolvedValue('file:///appdata/server/room/files/clip.mp4');
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingVideo(3);
|
||||
const attachment = registerIncomingVideo(12 * 1024 * 1024);
|
||||
|
||||
service.handleFileChunk(chunkPayload(0, 1, [
|
||||
1,
|
||||
@@ -558,7 +592,7 @@ describe('AttachmentTransferService', () => {
|
||||
expect(persistence.ensureInlineDisplayObjectUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('falls back to inline blob hydration for playable media when no native file url exists', async () => {
|
||||
it('falls back to inline blob hydration for oversized playable media when no native file url exists', async () => {
|
||||
attachmentStorage.canStreamToDisk.mockReturnValue(true);
|
||||
attachmentStorage.getFileUrl.mockResolvedValue(null);
|
||||
persistence.ensureInlineDisplayObjectUrl.mockImplementation(async (entry) => {
|
||||
@@ -568,7 +602,7 @@ describe('AttachmentTransferService', () => {
|
||||
});
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingVideo(3);
|
||||
const attachment = registerIncomingVideo(12 * 1024 * 1024);
|
||||
|
||||
service.handleFileChunk(chunkPayload(0, 1, [
|
||||
1,
|
||||
@@ -632,8 +666,30 @@ describe('AttachmentTransferService', () => {
|
||||
expect(persistence.persistUploadCopyFromSourcePath).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('falls back to the in-memory upload when the resolved disk path is empty', async () => {
|
||||
attachmentStorage.resolveExistingPath.mockResolvedValue('/appdata/server/room/files/photo.png');
|
||||
attachmentStorage.getFileSize.mockResolvedValue(0);
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingAttachment(9);
|
||||
|
||||
attachment.available = true;
|
||||
attachment.savedPath = '/appdata/server/room/files/photo.png';
|
||||
runtimeStore.setOriginalFile(`${MESSAGE_ID}:${FILE_ID}`, new File([new Uint8Array(9)], 'photo.png', { type: 'image/png' }));
|
||||
|
||||
await service.handleFileRequest({
|
||||
messageId: MESSAGE_ID,
|
||||
fileId: FILE_ID,
|
||||
fromPeerId: 'peer-2'
|
||||
});
|
||||
|
||||
expect(transport.streamFileFromDiskToPeer).not.toHaveBeenCalled();
|
||||
expect(transport.streamFileToPeer).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('streams a restored oversized generic file from app data when the in-memory upload is gone', async () => {
|
||||
attachmentStorage.resolveExistingPath.mockResolvedValue('/appdata/server/room/files/setup.exe');
|
||||
attachmentStorage.getFileSize.mockResolvedValue(12 * 1024 * 1024);
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingGenericFile(12 * 1024 * 1024);
|
||||
@@ -737,6 +793,7 @@ describe('AttachmentTransferService', () => {
|
||||
|
||||
it('prefers streaming from disk over an in-memory original file when both exist', async () => {
|
||||
attachmentStorage.resolveExistingPath.mockResolvedValue('/appdata/server/room/files/setup.exe');
|
||||
attachmentStorage.getFileSize.mockResolvedValue(12 * 1024 * 1024);
|
||||
|
||||
const service = createService();
|
||||
const attachment = registerIncomingGenericFile(12 * 1024 * 1024);
|
||||
|
||||
+3
-2
@@ -17,6 +17,7 @@ import {
|
||||
shouldPersistDownloadedAttachment,
|
||||
shouldStreamAttachmentReceiveToDisk
|
||||
} from '../../domain/logic/attachment.logic';
|
||||
import { shouldServeAttachmentFromDiskPath } from '../../domain/logic/attachment-serve.rules';
|
||||
import type { Attachment, AttachmentMeta } from '../../domain/models/attachment.model';
|
||||
import {
|
||||
ATTACHMENT_TRANSFER_EWMA_CURRENT_WEIGHT,
|
||||
@@ -564,7 +565,7 @@ export class AttachmentTransferService {
|
||||
? await this.attachmentStorage.resolveExistingPath(attachment)
|
||||
: null;
|
||||
|
||||
if (diskPath) {
|
||||
if (diskPath && shouldServeAttachmentFromDiskPath(await this.attachmentStorage.getFileSize(diskPath))) {
|
||||
await this.transport.streamFileFromDiskToPeer(
|
||||
fromPeerId,
|
||||
messageId,
|
||||
@@ -598,7 +599,7 @@ export class AttachmentTransferService {
|
||||
roomName
|
||||
);
|
||||
|
||||
if (legacyDiskPath) {
|
||||
if (legacyDiskPath && shouldServeAttachmentFromDiskPath(await this.attachmentStorage.getFileSize(legacyDiskPath))) {
|
||||
await this.transport.streamFileFromDiskToPeer(
|
||||
fromPeerId,
|
||||
messageId,
|
||||
|
||||
Reference in New Issue
Block a user