fix: Bug - Sending files and attachment issues

Normalize attachment MIME types from filenames, hydrate playable media and
gallery tiles from disk without redundant peer requests, reset stalled partial
downloads, and improve gallery retry/hydration UX across chat and DMs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 13:30:13 +02:00
co-authored by Cursor
parent b13f71d2d3
commit 0078c320a5
14 changed files with 388 additions and 15 deletions
@@ -13,10 +13,13 @@ import { isSharingFromThisDevice, canHostAttachment } from '../../domain/logic/a
import { selectFileRequestPeer } from '../../domain/logic/attachment-request.rules';
import {
canReceiveAttachment,
needsAttachmentDisplayHydration,
shouldCopyLargeUploaderFileToAppData,
shouldPersistDownloadedAttachment,
shouldStreamAttachmentReceiveToDisk
} from '../../domain/logic/attachment.logic';
import { normalizeAttachmentMeta } from '../../domain/logic/attachment-normalize.rules';
import { resolveAttachmentMime } from '../../domain/logic/attachment-mime.rules';
import { shouldServeAttachmentFromDiskPath } from '../../domain/logic/attachment-serve.rules';
import type { Attachment, AttachmentMeta } from '../../domain/models/attachment.model';
import {
@@ -141,9 +144,11 @@ export class AttachmentTransferService {
const alreadyKnown = existing.find((entry) => entry.id === meta.id);
if (!alreadyKnown) {
const attachment: Attachment = { ...meta,
const attachment: Attachment = {
...normalizeAttachmentMeta(meta),
available: false,
receivedBytes: 0 };
receivedBytes: 0
};
existing.push(attachment);
newAttachments.push(attachment);
@@ -171,6 +176,16 @@ export class AttachmentTransferService {
// request makes the sender stream the file twice and corrupts byte accounting.
this.runtimeStore.setPendingRequestPeers(requestKey, new Set<string>());
if (needsAttachmentDisplayHydration(attachment)) {
const hydratedLocally = await this.persistence.tryRestoreAttachmentFromLocal(attachment);
if (hydratedLocally) {
this.runtimeStore.deletePendingRequest(requestKey);
this.runtimeStore.touch();
return;
}
}
if (!attachment.available) {
const restoredLocally = await this.persistence.tryRestoreAttachmentFromLocal(attachment);
@@ -230,6 +245,10 @@ export class AttachmentTransferService {
}
requestImageFromAnyPeer(messageId: string, attachment: Attachment): Promise<void> {
if (needsAttachmentDisplayHydration(attachment)) {
return this.requestFromAnyPeer(messageId, attachment);
}
if ((attachment.receivedBytes ?? 0) > 0 || this.hasPendingRequest(messageId, attachment.id)) {
this.cancelRequest(messageId, attachment);
}
@@ -259,7 +278,7 @@ export class AttachmentTransferService {
messageId,
filename: file.name,
size: file.size,
mime: file.type || DEFAULT_ATTACHMENT_MIME_TYPE,
mime: resolveAttachmentMime(file.name, file.type || DEFAULT_ATTACHMENT_MIME_TYPE),
isImage: resolvePublishAttachmentIsImage(file),
uploaderPeerId,
filePath: (file as LocalFileWithPath).path,
@@ -327,29 +346,40 @@ export class AttachmentTransferService {
const alreadyKnown = list.find((entry) => entry.id === file.id);
if (alreadyKnown) {
alreadyKnown.filename = file.filename;
alreadyKnown.size = file.size;
alreadyKnown.mime = resolveAttachmentMime(file.filename, file.mime);
alreadyKnown.isImage = isImageAttachment({
filename: file.filename,
isImage: !!file.isImage,
mime: alreadyKnown.mime
});
alreadyKnown.uploaderPeerId = file.uploaderPeerId ?? alreadyKnown.uploaderPeerId;
this.runtimeStore.touch();
void this.persistence.persistAttachmentMeta(alreadyKnown);
return false;
}
const attachment: Attachment = {
const normalizedMeta = normalizeAttachmentMeta({
id: file.id,
messageId,
filename: file.filename,
size: file.size,
mime: file.mime,
isImage: isImageAttachment({
filename: file.filename,
isImage: !!file.isImage,
mime: file.mime
}),
uploaderPeerId: file.uploaderPeerId,
isImage: !!file.isImage,
uploaderPeerId: file.uploaderPeerId
});
const runtimeAttachment: Attachment = {
...normalizedMeta,
available: false,
receivedBytes: 0
};
list.push(attachment);
list.push(runtimeAttachment);
this.runtimeStore.setAttachmentsForMessage(messageId, list);
this.runtimeStore.touch();
void this.persistence.persistAttachmentMeta(attachment);
void this.persistence.persistAttachmentMeta(runtimeAttachment);
return true;
}
@@ -793,6 +823,10 @@ export class AttachmentTransferService {
this.runtimeStore.touch();
void this.persistence.persistAttachmentMeta(attachment);
void this.announceLocalHost(attachment);
if (this.isPlayableMedia(attachment)) {
await this.hydratePlayableMediaAfterDiskReceive(attachment);
}
}
/**