fix: report attachment cache failures and retries
This commit is contained in:
+8
-7
@@ -16,11 +16,11 @@ function remoteAttachmentUrl(attachment: any, baseUrl: string) {
|
||||
}
|
||||
|
||||
async function cacheAttachments(source: Source, attachments: any[]) {
|
||||
if (source.attachment_storage_mode === "remote") return attachments;
|
||||
if (source.attachment_storage_mode === "remote") return { items: attachments, errors: [] as string[] };
|
||||
const directory = join(process.cwd(), "public", "uploads", "cache", `source-${source.id}`); await mkdir(directory, { recursive: true });
|
||||
let used = 0;
|
||||
for (const row of db.prepare("SELECT attachments_json FROM posts WHERE source_id=?").all(source.id) as { attachments_json: string }[]) { try { used += (JSON.parse(row.attachments_json) as any[]).filter((item) => String(item.url || "").startsWith(`/uploads/cache/source-${source.id}/`)).reduce((sum, item) => sum + Number(item.size || 0), 0); } catch {} }
|
||||
const result: any[] = [];
|
||||
const result: any[] = [], errors: string[] = [];
|
||||
for (const attachment of attachments) {
|
||||
const url = remoteAttachmentUrl(attachment, source.base_url); const type = attachment.type || "";
|
||||
if (!url || (source.attachment_storage_mode === "images" && !type.startsWith("image/"))) { result.push(attachment); continue; }
|
||||
@@ -31,9 +31,9 @@ async function cacheAttachments(source: Source, attachments: any[]) {
|
||||
const filename = attachment.filename || attachment.name || "attachment"; const key = createHash("sha256").update(url).digest("hex").slice(0, 24) + extname(filename);
|
||||
await writeFile(join(directory, key), body); used += body.length;
|
||||
result.push({ ...attachment, originalUrl: url, url: `/uploads/cache/source-${source.id}/${key}`, type: type || response.headers.get("content-type") || "application/octet-stream", size: body.length });
|
||||
} catch (error) { recordError("attachment-cache", error, { sourceId: source.id, url }); result.push(attachment); }
|
||||
} catch (error) { const message = error instanceof Error ? error.message : "快取附件失敗"; errors.push(message); recordError("attachment-cache", error, { sourceId: source.id, url }); result.push(attachment); }
|
||||
}
|
||||
return result;
|
||||
return { items: result, errors };
|
||||
}
|
||||
|
||||
async function cleanupCache(source: Source) {
|
||||
@@ -43,22 +43,23 @@ async function cleanupCache(source: Source) {
|
||||
}
|
||||
|
||||
async function upsertRemote(source: Source, memo: any) {
|
||||
const tags = JSON.stringify(memo.tags || []), attachments = JSON.stringify(await cacheAttachments(source, memo.attachments || memo.resources || []));
|
||||
const cached = await cacheAttachments(source, memo.attachments || memo.resources || []); const tags = JSON.stringify(memo.tags || []), attachments = JSON.stringify(cached.items);
|
||||
db.prepare(`INSERT INTO posts(source_id,author_id,remote_memo_name,content,visibility,tags_json,attachments_json,origin,remote_created_at,remote_updated_at,sync_status,hidden,remote_url) VALUES(?,?,?,?,?,?,?,?,?,?, 'synced',0,?) ON CONFLICT(source_id,remote_memo_name) DO UPDATE SET content=excluded.content,visibility=excluded.visibility,tags_json=excluded.tags_json,attachments_json=excluded.attachments_json,remote_updated_at=excluded.remote_updated_at,remote_url=excluded.remote_url,hidden=0,updated_at=CURRENT_TIMESTAMP`).run(source.id, source.user_id, memo.name, memo.content, memo.visibility, tags, attachments, "memos", memo.createTime || null, memo.updateTime || null, memoUrl(source.base_url, memo.name));
|
||||
return cached.errors;
|
||||
}
|
||||
|
||||
async function pull(source: Source) {
|
||||
const token = decrypt(source.token_encrypted); const identity = await getMemosIdentity(source.base_url, token); const creator = source.remote_user || identity.name;
|
||||
const rules = { creator, tags: JSON.parse(source.sync_tags_json || "[]") as string[], from: source.sync_from, to: source.sync_to, attachmentMode: source.sync_attachment_mode };
|
||||
const memos = await listMemos(source.base_url, token, rules);
|
||||
for (const memo of memos) await upsertRemote(source, memo);
|
||||
const cacheErrors: string[] = []; for (const memo of memos) cacheErrors.push(...await upsertRemote(source, memo));
|
||||
const names = memos.map((memo) => memo.name);
|
||||
if (names.length) { const placeholders = names.map(() => "?").join(","); db.prepare(`UPDATE posts SET hidden=1,updated_at=CURRENT_TIMESTAMP WHERE source_id=? AND remote_memo_name IS NOT NULL AND remote_memo_name NOT IN (${placeholders})`).run(source.id, ...names); }
|
||||
else db.prepare("UPDATE posts SET hidden=1,updated_at=CURRENT_TIMESTAMP WHERE source_id=? AND remote_memo_name IS NOT NULL").run(source.id);
|
||||
await cleanupCache(source);
|
||||
const avatar = identity.avatarUrl || identity.avatar || null; const avatarUrl = avatar?.startsWith("/") ? `${source.base_url.replace(/\/$/, "")}${avatar}` : avatar;
|
||||
const name = identity.nickname || identity.username || identity.name;
|
||||
db.prepare("UPDATE sources SET name=?,remote_user=?,sync_status='synced',last_synced_at=CURRENT_TIMESTAMP,last_error=NULL,last_connection_at=CURRENT_TIMESTAMP,last_connection_error=NULL,remote_display_name=?,remote_avatar_url=? WHERE id=?").run(name, creator, name, avatarUrl, source.id);
|
||||
db.prepare("UPDATE sources SET name=?,remote_user=?,sync_status='synced',last_synced_at=CURRENT_TIMESTAMP,last_error=NULL,last_connection_at=CURRENT_TIMESTAMP,last_connection_error=NULL,attachment_cache_error=?,remote_display_name=?,remote_avatar_url=? WHERE id=?").run(name, creator, cacheErrors.length ? `${cacheErrors.length} 個附件快取失敗;可按「立即同步」重試。` : null, name, avatarUrl, source.id);
|
||||
}
|
||||
|
||||
async function push(source: Source, payload: any) {
|
||||
|
||||
Reference in New Issue
Block a user