Prototyping IPFS file download

This commit is contained in:
Chris Troutner
2024-04-09 06:36:23 -07:00
parent a9f7e609e9
commit 14c724bf9c
6 changed files with 188 additions and 2 deletions
+4 -2
View File
@@ -1,12 +1,12 @@
{
"name": "ipfs-bch-wallet-consumer",
"version": "3.0.0",
"version": "3.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ipfs-bch-wallet-consumer",
"version": "3.0.0",
"version": "3.1.1",
"license": "MIT",
"dependencies": {
"@chainsafe/libp2p-gossipsub": "11.0.1",
@@ -27,6 +27,7 @@
"glob": "7.1.6",
"helia": "2.1.0",
"helia-coord": "1.5.6",
"ipfs-unixfs-exporter": "13.5.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -43,6 +44,7 @@
"koa2-ratelimit": "0.9.1",
"libp2p": "1.2.1",
"line-reader": "0.4.0",
"mime-types": "2.1.35",
"minimal-slp-wallet": "5.11.2",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
+2
View File
@@ -42,6 +42,7 @@
"glob": "7.1.6",
"helia": "2.1.0",
"helia-coord": "1.5.6",
"ipfs-unixfs-exporter": "13.5.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -58,6 +59,7 @@
"koa2-ratelimit": "0.9.1",
"libp2p": "1.2.1",
"line-reader": "0.4.0",
"mime-types": "2.1.35",
"minimal-slp-wallet": "5.11.2",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
@@ -3,6 +3,7 @@
*/
// Global npm libraries
import mime from 'mime-types'
// Local libraries
import wlogger from '../../../adapters/wlogger.js'
@@ -34,6 +35,7 @@ class IpfsRESTControllerLib {
this.handleError = this.handleError.bind(this)
this.connect = this.connect.bind(this)
this.getThisNode = this.getThisNode.bind(this)
this.viewFile = this.viewFile.bind(this)
}
/**
@@ -125,6 +127,46 @@ class IpfsRESTControllerLib {
}
}
/**
* @api {get} /ipfs/view/:cid Retrieve and display a file via its IPFS CID
* @apiPermission public
* @apiName GetCidView
* @apiGroup REST BCH
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/view/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*
*/
async viewFile (ctx) {
try {
const { cid } = ctx.params
// const file = await this.adapters.ipfs.ipfs.blockstore.get(cid)
// return file
// const cid = ctx.params.cid
const { filename, readStream } = await this.useCases.ipfs.downloadCid({ cid })
// ctx.body = ctx.req.pipe(readStream)
// Lookup the mime type from the filename.
const contentType = mime.lookup(filename)
ctx.set('Content-Type', contentType)
ctx.set(
'Content-Disposition',
// 'inline; filename="' + filename + '"'
`inline; filename="${filename}"`
)
ctx.body = readStream
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/viewFile(): ', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
+1
View File
@@ -56,6 +56,7 @@ class IpfsRouter {
this.router.post('/relays', this.ipfsRESTController.getRelays)
this.router.post('/connect', this.ipfsRESTController.connect)
this.router.get('/node', this.ipfsRESTController.getThisNode)
this.router.get('/view/:cid', this.ipfsRESTController.viewFile)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
+3
View File
@@ -4,7 +4,9 @@
https://troutsblog.com/blog/clean-architecture
*/
// Local libraries
import UserUseCases from './user.js'
import IpfsUseCases from './ipfs-use-cases.js'
class UseCases {
constructor (localConfig = {}) {
@@ -23,6 +25,7 @@ class UseCases {
// console.log('use-cases/index.js localConfig: ', localConfig)
this.user = new UserUseCases(localConfig)
this.ipfs = new IpfsUseCases(localConfig)
}
// Run any startup Use Cases at the start of the app.
+136
View File
@@ -0,0 +1,136 @@
/*
Use cases for working with IPFS.
*/
// Global npm libraries
// import Wallet from 'minimal-slp-wallet'
// import { CID } from 'multiformats'
// import RetryQueue from '@chris.troutner/retry-queue'
import { exporter } from 'ipfs-unixfs-exporter'
import { Duplex } from 'stream'
// Local libraries
// import PinEntity from '../entities/pin.js'
// import config from '../../config/index.js'
// const PSF_TOKEN_ID = '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
class IpfsUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating IPFS Use Cases library.'
)
}
// Encapsulate dependencies
this.exporter = exporter
// Bind 'this' object to all class subfunctions.
this.downloadCid = this.downloadCid.bind(this)
this.downloadCid2 = this.downloadCid2.bind(this)
}
// Download a pinned file, given its CID.
// Returns a readable stream.
async downloadCid (inObj = {}) {
try {
const { cid } = inObj
if (!cid) throw new Error('CID is undefined')
// const Pins = this.adapters.localdb.Pins
// let existingModel = await Pins.find({ cid })
// existingModel = existingModel[0]
// console.log('existingModel: ', existingModel)
// if (!existingModel) {
// throw new Error(`Database model for CID ${cid} does not exist.`)
// }
//
// if (!existingModel.dataPinned) {
// throw new Error('File has not been pinned. Not available.')
// }
const helia = this.adapters.ipfs.ipfs
// Convert the file to a Buffer.
const fileChunks = []
for await (const chunk of helia.fs.cat(cid)) {
fileChunks.push(chunk)
}
const fileBuf = Buffer.concat(fileChunks)
// Convert the Buffer into a readable stream
const bufferToStream = (myBuffer) => {
const tmp = new Duplex()
tmp.push(myBuffer)
tmp.push(null)
return tmp
}
const readStream = bufferToStream(fileBuf)
const filename = 'test.jpg'
return { filename, readStream }
} catch (err) {
console.error('Error in use-cases/ipfs.js/dowloadCid()')
throw err
}
}
async downloadCid2 (inObj = {}) {
try {
const { cid } = inObj
// console.log(`downloadFile() retrieving this CID: ${cid}, with fileName: ${fileName}, and path: ${path}`)
const blockstore = this.adapters.ipfs.ipfs.blockstore
const entry = await this.exporter(cid, blockstore)
console.info(entry.cid) // Qmqux
console.log('entry: ', entry)
// console.info(entry.unixfs.fileSize()) // 4
// const filePath = `${path}/${fileName}`
// console.log(`filePath: ${filePath}`)
// const writableStream = this.fs.createWriteStream(filePath)
//
// writableStream.on('error', this.writeStreamError)
//
// writableStream.on('finish', this.writeStreamFinished)
//
const fileChunks = []
for await (const buf of entry.content()) {
fileChunks.push(buf)
}
const fileBuf = Buffer.concat(fileChunks)
//
// writableStream.end()
// Convert the Buffer into a readable stream
const bufferToStream = (myBuffer) => {
const tmp = new Duplex()
tmp.push(myBuffer)
tmp.push(null)
return tmp
}
const readStream = bufferToStream(fileBuf)
const filename = 'test.jpg'
return { filename, readStream }
// return { cid }
} catch (err) {
console.error('Error in ipfs-use-cases.js/downloadCid()')
throw err
}
}
}
export default IpfsUseCases