🌳
pt0/ipfsF/ipfsBackupAI.mts
1import fs from 'fs/promises'
2import path from 'path'
9type IpfsBackupMeta = {
10 mfsRoot: string | null
11 kvKey: string
12 rootCid: string
15const META_FILE = '_meta.json'
17export const backupIpfsContent = async ({rootCids, mfsParamsMap}: {
18 rootCids: {label: string, cid: string}[],
19 mfsParamsMap?: Record<string, {mfsRoot: string, kvKey: string}>,
20}) => {
21 const client = getIpfsClient()
22 const summary = {total: 0, new: 0, skipped: 0, failed: 0}
24 for (const {label, cid} of rootCids) {
25 const dirPath = getIpfsBackupDir({label})
26 await mkDirUnlessExist(dirPath)
28 const mfsParam = mfsParamsMap?.[label]
29 const meta: IpfsBackupMeta = {mfsRoot: mfsParam?.mfsRoot ?? null, kvKey: label, rootCid: cid}
30 await fs.writeFile(path.join(dirPath, META_FILE), JSON.stringify(meta, null, 2))
32 let entries: {cid: any, name: string, type: string}[] = []
33 try {
34 if (mfsParam?.mfsRoot) {
35 for await (const entry of client.files.ls(mfsParam.mfsRoot)) {
36 entries.push(entry as any)
37 }
38 } else {
39 for await (const entry of client.ls(cid)) {
40 entries.push(entry as any)
41 }
42 }
43 } catch (err) {
44 betLog('ipfsBackupLsFail', {label, cid, errMsg: (err as Error).message})
45 summary.failed++
46 continue
47 }
49 const fileEntries = entries.filter(e => e.type === 'file')
50 summary.total += fileEntries.length
52 for (const entry of fileEntries) {
53 const entryCid = entry.cid.toString()
54 const lastDot = entry.name.lastIndexOf('.')
55 const ext = lastDot >= 0 ? entry.name.slice(lastDot + 1) : 'bin'
56 const fileName = `${entryCid}.${ext}`
57 const filePath = path.join(dirPath, fileName)
59 try { await fs.access(filePath); summary.skipped++; continue } catch {}
61 try {
62 const resp = await fetch(ipfsGatewayFetchUrl(entryCid))
63 if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
64 let finalExt = ext
65 if (ext === 'bin') {
66 const contentType = resp.headers.get('content-type')
67 if (contentType) {
68 const derived = getExtFromMime(contentType)
69 if (derived) finalExt = derived
70 }
71 }
72 const finalFileName = `${entryCid}.${finalExt}`
73 const finalFilePath = path.join(dirPath, finalFileName)
74 const buffer = Buffer.from(await resp.arrayBuffer())
75 await fs.writeFile(finalFilePath, buffer)
76 summary.new++
77 } catch (err) {
78 betLog('ipfsBackupDownloadFail', {label, cid: entryCid, errMsg: (err as Error).message})
79 summary.failed++
80 }
81 }
82 }
84 betLog('ipfsBackupResult', summary)
85 return summary