Improve attachment memory safety, downloads, and high-memory alert UX.
Queue Release Build / prepare (push) Successful in 20s
Deploy Web Apps / deploy (push) Successful in 9m2s
Queue Release Build / build-windows (push) Successful in 28m8s
Queue Release Build / build-linux (push) Successful in 47m26s
Queue Release Build / build-android (push) Successful in 19m52s
Queue Release Build / finalize (push) Successful in 4m42s
Queue Release Build / prepare (push) Successful in 20s
Deploy Web Apps / deploy (push) Successful in 9m2s
Queue Release Build / build-windows (push) Successful in 28m8s
Queue Release Build / build-linux (push) Successful in 47m26s
Queue Release Build / build-android (push) Successful in 19m52s
Queue Release Build / finalize (push) Successful in 4m42s
Stream large receives to disk with chunk acks to cap renderer RAM, evict off-screen display blobs, and route exports through a disk-aware download service. Fix the high-memory dialog (backdrop dismiss, copy, log actions), allow diagnostics paths in the path jail, and restore persisted image hydration after reload. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+52
-3
@@ -10,6 +10,7 @@ import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||
import { selectCurrentUserId } from '../../../../store/users/users.selectors';
|
||||
import { DatabaseService } from '../../../../infrastructure/persistence';
|
||||
import { yieldToAttachmentHydrationLoop } from '../../domain/logic/attachment-blob.rules';
|
||||
import { buildAttachmentDisplayPinKey, shouldRevokeDisplayBlobForAttachment } from '../../domain/logic/attachment-blob-eviction.rules';
|
||||
import {
|
||||
getWatchedAttachmentRoomIdFromUrl,
|
||||
isDirectMessageAttachmentRoomId,
|
||||
@@ -20,6 +21,7 @@ import type {
|
||||
FileAnnouncePayload,
|
||||
FileCancelPayload,
|
||||
FileChunkPayload,
|
||||
FileChunkAckPayload,
|
||||
FileNotFoundPayload,
|
||||
FileRequestPayload
|
||||
} from '../../domain/models/attachment-transfer.model';
|
||||
@@ -44,6 +46,7 @@ export class AttachmentManagerService {
|
||||
private watchedRoomId: string | null = this.extractWatchedRoomId(this.router.url);
|
||||
private isDatabaseInitialised = false;
|
||||
private autoDownloadRequestsByRoom = new Map<string, Promise<void>>();
|
||||
private pinnedDisplayBlobKeys = new Set<string>();
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
@@ -160,6 +163,48 @@ export class AttachmentManagerService {
|
||||
return restored;
|
||||
}
|
||||
|
||||
pinDisplayBlobs(attachments: readonly Pick<Attachment, 'id' | 'messageId'>[]): void {
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment.messageId || !attachment.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.pinnedDisplayBlobKeys.add(buildAttachmentDisplayPinKey(attachment.messageId, attachment.id));
|
||||
}
|
||||
}
|
||||
|
||||
unpinDisplayBlobs(attachments: readonly Pick<Attachment, 'id' | 'messageId'>[]): void {
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment.messageId || !attachment.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.pinnedDisplayBlobKeys.delete(buildAttachmentDisplayPinKey(attachment.messageId, attachment.id));
|
||||
}
|
||||
}
|
||||
|
||||
revokeOffscreenDisplayBlobsForMessage(messageId: string): void {
|
||||
if (!messageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasChanges = false;
|
||||
|
||||
for (const attachment of this.runtimeStore.getAttachmentsForMessage(messageId)) {
|
||||
if (!shouldRevokeDisplayBlobForAttachment(messageId, attachment, this.pinnedDisplayBlobKeys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.persistence.revokeAttachmentDisplayBlob(attachment)) {
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
this.runtimeStore.touch();
|
||||
}
|
||||
}
|
||||
|
||||
requestFile(messageId: string, attachment: Attachment): Promise<void> {
|
||||
return this.transfer.requestFile(messageId, attachment);
|
||||
}
|
||||
@@ -173,9 +218,9 @@ export class AttachmentManagerService {
|
||||
}
|
||||
|
||||
handleFileAnnounce(payload: FileAnnouncePayload): void {
|
||||
this.transfer.handleFileAnnounce(payload);
|
||||
const isNew = this.transfer.handleFileAnnounce(payload);
|
||||
|
||||
if (payload.messageId && payload.file?.id) {
|
||||
if (isNew && payload.messageId && payload.file?.id) {
|
||||
this.queueAutoDownloadsForMessage(payload.messageId, payload.file.id);
|
||||
}
|
||||
}
|
||||
@@ -184,6 +229,10 @@ export class AttachmentManagerService {
|
||||
this.transfer.handleFileChunk(payload);
|
||||
}
|
||||
|
||||
handleFileChunkAck(payload: FileChunkAckPayload): void {
|
||||
this.transfer.handleFileChunkAck(payload);
|
||||
}
|
||||
|
||||
async handleFileRequest(payload: FileRequestPayload): Promise<void> {
|
||||
await this.transfer.handleFileRequest(payload);
|
||||
}
|
||||
@@ -218,7 +267,7 @@ export class AttachmentManagerService {
|
||||
|
||||
for (const messageId of messageIds) {
|
||||
for (const attachment of this.runtimeStore.getAttachmentsForMessage(messageId)) {
|
||||
if (await this.persistence.tryRestoreAttachmentFromLocal(attachment)) {
|
||||
if (await this.persistence.tryRestoreAttachmentHostOnly(attachment)) {
|
||||
hasChanges = true;
|
||||
await yieldToAttachmentHydrationLoop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user