feat(attachments): expose fetchSource on preview and mesh retry

Add readAttachmentBytesWithSource so callers know whether bytes came from
local staging, drive-local, protomux attach-mesh, or hyperdrive replication.

readAttachmentPreview now returns fetchSource for UI and smokes.
_fetchAttachMeshWithRetry mirrors banner prefetch (14s timeout + 600ms retry
when attach sessions exist). Banner prefetch uses the same retry helper.

Phase 60 / v0.8.25 — Pear attach preview mesh path.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-22 00:36:15 -04:00
co-authored by Cursor
parent 018291aaa5
commit 5f7d8b460c
+33 -13
View File
@@ -621,7 +621,7 @@ class PearcordPlatform extends EventEmitter {
const row = await this.attachments.get(attachmentId) const row = await this.attachments.get(attachmentId)
if (row && this.attachments.hasLocalBytes(row)) return row if (row && this.attachments.hasLocalBytes(row)) return row
if (!this.guild) return null if (!this.guild) return null
const buf = await this.guild.fetchAttachmentFromPeers(attachmentId).catch(() => null) const buf = await this._fetchAttachMeshWithRetry(attachmentId)
if (!buf) return null if (!buf) return null
const meta = row || (await this.attachments.get(attachmentId)) const meta = row || (await this.attachments.get(attachmentId))
if (!meta) return null if (!meta) return null
@@ -2124,21 +2124,33 @@ class PearcordPlatform extends EventEmitter {
return row return row
} }
async readAttachmentBytes (attachmentId) { async _fetchAttachMeshWithRetry (attachmentId, timeoutMs = 14000) {
if (!this.guild) return null
let buf = await this.guild.fetchAttachmentFromPeers(attachmentId, timeoutMs).catch(() => null)
if (buf?.length) return buf
if (!(this.guild._attachChannels?.size > 0)) return null
await new Promise((r) => setTimeout(r, 600))
return this.guild.fetchAttachmentFromPeers(attachmentId, timeoutMs).catch(() => null)
}
async readAttachmentBytesWithSource (attachmentId) {
const row = await this.attachments?.get(attachmentId) const row = await this.attachments?.get(attachmentId)
if (!row) throw new Error('attachment not found') if (!row) throw new Error('attachment not found')
if (this.attachments.hasLocalBytes(row)) { if (this.attachments.hasLocalBytes(row)) {
return this.attachments.readBytes(row) return { buf: await this.attachments.readBytes(row), source: 'local' }
} }
const drive = getGuildDrive(this.storagePath, row.guildId) const drive = getGuildDrive(this.storagePath, row.guildId)
const localDrive = await drive.get(row.id, row.filename, row.drivePath) const localDrive = await drive.get(row.id, row.filename, row.drivePath)
if (localDrive?.length) return localDrive if (localDrive?.length) {
return { buf: localDrive, source: 'drive-local' }
let buf = null
if (this.guild) {
buf = await this.guild.fetchAttachmentFromPeers(attachmentId).catch(() => null)
} }
if (!buf && isHexDriveKey(row.driveKey) && (this.guild?.peers?.size || 0) > 0) {
let buf = await this._fetchAttachMeshWithRetry(attachmentId)
if (buf?.length) {
await this.attachments.persistFetched(row, buf)
return { buf, source: 'attach-mesh' }
}
if (isHexDriveKey(row.driveKey) && (this.guild?.peers?.size || 0) > 0) {
buf = await drive.fetchRemote({ buf = await drive.fetchRemote({
driveKey: row.driveKey, driveKey: row.driveKey,
drivePath: row.drivePath, drivePath: row.drivePath,
@@ -2147,9 +2159,16 @@ class PearcordPlatform extends EventEmitter {
swarm: this.guild?.swarm, swarm: this.guild?.swarm,
timeoutMs: 8000 timeoutMs: 8000
}) })
if (buf?.length) {
await this.attachments.persistFetched(row, buf)
return { buf, source: 'hyperdrive' }
}
} }
if (!buf) throw new Error('attachment file missing') throw new Error('attachment file missing')
await this.attachments.persistFetched(row, buf) }
async readAttachmentBytes (attachmentId) {
const { buf } = await this.readAttachmentBytesWithSource(attachmentId)
return buf return buf
} }
@@ -2159,12 +2178,13 @@ class PearcordPlatform extends EventEmitter {
if (!String(row.mimeType || '').startsWith('image/')) { if (!String(row.mimeType || '').startsWith('image/')) {
throw new Error('preview only for images') throw new Error('preview only for images')
} }
const buf = await this.readAttachmentBytes(attachmentId) const { buf, source } = await this.readAttachmentBytesWithSource(attachmentId)
return { return {
attachmentId: row.id, attachmentId: row.id,
mimeType: row.mimeType, mimeType: row.mimeType,
filename: row.filename, filename: row.filename,
dataBase64: b4a.toString(buf, 'base64') dataBase64: b4a.toString(buf, 'base64'),
fetchSource: source
} }
} }