1
0
mirror of https://github.com/kremalicious/ipfs.git synced 2024-12-29 08:07:47 +01:00

refactor file upload

* addReadableStream instead of add
* handle multiple files
* ipfs functions splitup
This commit is contained in:
Matthias Kretschmann 2019-10-26 17:36:43 +02:00
parent 8f1a166ab1
commit 6a27079032
Signed by: m
GPG Key ID: 606EEEF3C479A91F
6 changed files with 85 additions and 63 deletions

View File

@ -13,7 +13,7 @@ server {
proxy_cache_bypass $http_upgrade; proxy_cache_bypass $http_upgrade;
} }
location ~ "^/api/v0/(add|version|id)" { location ~ "^/api/v0/(add|version|id|ls)" {
proxy_pass http://localhost:5001; proxy_pass http://localhost:5001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade; proxy_cache_bypass $http_upgrade;

View File

@ -14,23 +14,23 @@
"dependencies": { "dependencies": {
"@zeit/next-css": "^1.0.1", "@zeit/next-css": "^1.0.1",
"axios": "^0.19.0", "axios": "^0.19.0",
"ipfs-http-client": "^39.0.0", "ipfs-http-client": "^39.0.2",
"next": "9.1.1", "next": "9.1.1",
"next-seo": "^2.1.2", "next-seo": "^2.2.1",
"next-svgr": "0.0.2", "next-svgr": "0.0.2",
"react": "^16.10.2", "react": "^16.11.0",
"react-dom": "^16.10.2", "react-dom": "^16.11.0",
"react-dropzone": "^10.1.10", "react-dropzone": "^10.1.10",
"use-dark-mode": "^2.3.1" "use-dark-mode": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/next-seo": "^1.10.0", "@types/next-seo": "^1.10.0",
"@types/node": "^12.11.1", "@types/node": "^12.11.7",
"@types/react": "^16.9.9", "@types/react": "^16.9.11",
"@typescript-eslint/eslint-plugin": "^2.4.0", "@typescript-eslint/eslint-plugin": "^2.5.0",
"@typescript-eslint/parser": "^2.4.0", "@typescript-eslint/parser": "^2.5.0",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"eslint": "^6.5.1", "eslint": "^6.6.0",
"eslint-config-prettier": "^6.4.0", "eslint-config-prettier": "^6.4.0",
"eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.1", "eslint-plugin-prettier": "^3.1.1",

View File

@ -1,11 +1,11 @@
import React, { useState, useEffect } from 'react' import React, { useState } from 'react'
import { ipfsNodeUri, ipfsGateway } from '../../site.config' import { ipfsNodeUri, ipfsGateway } from '../../site.config'
import Dropzone from './Dropzone' import Dropzone, { FileDropzone } from './Dropzone'
import styles from './Add.module.css' import styles from './Add.module.css'
import Loader from './Loader' import Loader from './Loader'
import useIpfsApi from '../hooks/use-ipfs-api' import useIpfsApi from '../hooks/use-ipfs-api'
import { IpfsConfig } from '../@types/ipfs' import { IpfsConfig } from '../@types/ipfs'
import { formatBytes, addToIpfs } from '../utils' import { addToIpfs, FileIpfs } from '../ipfs'
const { hostname, port, protocol } = new URL(ipfsNodeUri) const { hostname, port, protocol } = new URL(ipfsNodeUri)
@ -17,33 +17,23 @@ const ipfsConfig: IpfsConfig = {
export default function Add() { export default function Add() {
const { ipfs, isIpfsReady, ipfsError } = useIpfsApi(ipfsConfig) const { ipfs, isIpfsReady, ipfsError } = useIpfsApi(ipfsConfig)
const [fileHash, setFileHash] = useState() const [files, setFiles] = useState()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [message, setMessage] = useState() const [message] = useState()
const [error, setError] = useState() const [error, setError] = useState()
const [fileSize, setFileSize] = useState()
const [fileSizeReceived, setFileSizeReceived] = useState('')
useEffect(() => { async function handleOnDrop(acceptedFiles: FileDropzone[]) {
setMessage( if (!acceptedFiles) return
`Adding to IPFS<br />
<small>${fileSizeReceived || 0}/${fileSize}</small><br />`
)
}, [fileSize, fileSizeReceived])
async function handleOnDrop(acceptedFiles: File[]) {
if (!acceptedFiles[0]) return
setLoading(true) setLoading(true)
setError(null) setError(null)
const totalSize = formatBytes(acceptedFiles[0].size, 0)
setFileSize(totalSize)
try { try {
const cid = await addToIpfs(acceptedFiles, setFileSizeReceived, ipfs) const directoryCid = await addToIpfs(ipfs, acceptedFiles)
if (!cid) return if (!directoryCid) return
setFileHash(cid)
const fileList = await ipfs.ls(directoryCid)
setFiles(fileList)
setLoading(false) setLoading(false)
} catch (error) { } catch (error) {
setError(`Adding to IPFS failed: ${error.message}`) setError(`Adding to IPFS failed: ${error.message}`)
@ -55,17 +45,22 @@ export default function Add() {
<div className={styles.add}> <div className={styles.add}>
{loading ? ( {loading ? (
<Loader message={message} /> <Loader message={message} />
) : fileHash ? ( ) : files ? (
<ul style={{ textAlign: 'left' }}>
{files.map((file: FileIpfs) => (
<li key={file.path}>
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
href={`${ipfsGateway}/ipfs/${fileHash}`} href={`${ipfsGateway}/ipfs/${file.path}`}
> >
ipfs://{fileHash} ipfs://{file.path}
</a> </a>
</li>
))}
</ul>
) : ( ) : (
<Dropzone <Dropzone
multiple={false}
handleOnDrop={handleOnDrop} handleOnDrop={handleOnDrop}
disabled={!isIpfsReady} disabled={!isIpfsReady}
error={error || ipfsError} error={error || ipfsError}

View File

@ -2,13 +2,17 @@ import React, { useCallback } from 'react'
import { useDropzone } from 'react-dropzone' import { useDropzone } from 'react-dropzone'
import styles from './Dropzone.module.css' import styles from './Dropzone.module.css'
export interface FileDropzone extends File {
path: string
}
export default function Dropzone({ export default function Dropzone({
handleOnDrop, handleOnDrop,
disabled, disabled,
multiple, multiple,
error error
}: { }: {
handleOnDrop(files: File[]): void handleOnDrop(files: FileDropzone[]): void
disabled?: boolean disabled?: boolean
multiple?: boolean multiple?: boolean
error?: string error?: string
@ -37,12 +41,12 @@ export default function Dropzone({
<input {...getInputProps({ multiple })} /> <input {...getInputProps({ multiple })} />
{isDragActive && !isDragReject ? ( {isDragActive && !isDragReject ? (
`Drop it like it's hot!` `Drop it like it's hot!`
) : multiple ? ( ) : multiple === false ? (
`Drag 'n' drop some files here, or click to select files.` `Drag 'n' drop a file here, or click to select a file.`
) : error ? ( ) : error ? (
<div className={styles.error}>{error}</div> <div className={styles.error}>{error}</div>
) : ( ) : (
`Drag 'n' drop a file here, or click to select a file.` `Drag 'n' drop some files here, or click to select files.`
)} )}
</div> </div>
) )

41
src/ipfs.ts Normal file
View File

@ -0,0 +1,41 @@
import { FileDropzone } from './components/Dropzone'
export interface FileIpfsAdd {
path: string
content: File | ReadableStream | Buffer | string
}
export interface FileIpfs {
path: string
hash: string
size: number
}
export function streamFiles(ipfs: any, ipfsFiles: FileIpfsAdd[]) {
return new Promise((resolve, reject) => {
const stream = ipfs.addReadableStream({
wrapWithDirectory: true
// progress: (length: number) => setFileSizeReceived(formatBytes(length, 0))
})
stream.on('data', (data: FileIpfs) => {
console.log(`Added ${data.path} hash: ${data.hash}`)
// The last data event will contain the directory hash
if (data.path === '') resolve(data.hash)
})
stream.on('error', reject)
ipfsFiles.forEach((file: FileIpfsAdd) => stream.write(file))
stream.end()
})
}
export async function addToIpfs(ipfs: any, files: FileDropzone[]) {
const ipfsFiles = [
...files.map((file: FileDropzone) => {
return { path: file.path, content: file }
})
]
const directoryCid = await streamFiles(ipfs, ipfsFiles)
return directoryCid
}

View File

@ -9,24 +9,6 @@ export function formatBytes(a: number, b: number) {
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f] return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f]
} }
export async function addToIpfs(
files: File[],
setFileSizeReceived: (size: string) => void,
ipfs: any
) {
const file = [...files][0]
const fileDetails = { path: file.name, content: file }
const response = await ipfs.add(fileDetails, {
wrapWithDirectory: true,
progress: (length: number) => setFileSizeReceived(formatBytes(length, 0))
})
// CID of wrapping directory is returned last
const cid = `${response[response.length - 1].hash}/${file.name}`
return cid
}
export async function pingUrl(url: string) { export async function pingUrl(url: string) {
try { try {
const response = await axios(url, { timeout: 5000 }) const response = await axios(url, { timeout: 5000 })