Skip to content

Instantly share code, notes, and snippets.

@Gozala
Last active January 9, 2018 07:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gozala/e52439ba42fcad635667cfacee5d8271 to your computer and use it in GitHub Desktop.
Save Gozala/e52439ba42fcad635667cfacee5d8271 to your computer and use it in GitHub Desktop.
const path = require("path")
const url = require("url")
const IPFS = require("ipfs")
const mime = require("mime-types")
const fileType = require("file-type")
const unixfs = require("ipfs-unixfs-engine")
const pull = require("pull-stream")
const toStream = require("pull-stream-to-stream")
const peek = require("pull-peek")
class FileSystem {
constructor(node) {
this.node = node
this.ipldResolver = node._ipldResolver
}
static normalizePath(path) {
const path2 = path.startsWith("/ipfs/") ? path.substr(6) : path
const path3 = path2.endsWith("/")
? path2.substr(0, path2.length - 1)
: path2
return path3
}
entry(ipfsPath) {
return new Promise((resolve, reject) => {
const entryPath = FileSystem.normalizePath(ipfsPath)
const maxDepth = entryPath.split("/").length
return pull(
unixfs.exporter(entryPath, this.ipldResolver, { maxDepth }),
pull.collect((error, entries) => {
if (error) {
if (error.message.startsWith("multihash length inconsistent")) {
reject(new InvalidCID(error))
} else if (error.message.startsWith("Non-base58 character")) {
reject(new InvalidCID(error))
} else {
reject(new IOError(error))
}
} else {
switch (entries.length) {
case 0:
return reject(new EntryNotFound(entryPath))
case 1: {
const [node] = entries
return resolve(new FileEntry(path.dirname(entryPath), node))
}
default: {
const [node, ...nodes] = entries
return resolve(
new DirectoryEntry(this, path.dirname(entryPath), node, nodes)
)
}
}
}
})
)
})
}
}
class Entry {
static from(fs, parentPath, node) {
if (node.type === "file") {
return new FileEntry(parentPath, node)
} else {
return new DirectoryEntry(fs, parentPath, node, null)
}
}
}
class FileEntry {
constructor(parentPath, node) {
this.node = node
this.name = node.name
this.path = `/ipfs/${parentPath}/${this.name}`
this.hash = node.hash
this.type = "file"
}
peek() {
if (this._nodeContent == null) {
this._fileType = new Promise((resolve, reject) => {
this._nodeContent = pull(
this.node.content,
peek((end, data) => {
resolve(fileType(data))
})
)
})
}
return this
}
fileType() {
return this.peek()._fileType
}
contentStream() {
return toStream.source(this.peek()._nodeContent)
}
contentBuffer() {
return new Promise((resolve, reject) => {
pull(
this.peek()._nodeContent,
pull.collect((error, chunks) => {
if (error) reject(error)
else resolve(Buffer.concat(chunks))
})
)
})
}
}
class DirectoryEntry {
constructor(fs, parentPath, node, nodes) {
this.fs = fs
this.nodes = nodes
this.node = node
this.name = node.name
this.path = path.normalize(`/ipfs/${parentPath}/${this.name}/`)
this.hash = node.hash
this.type = "directory"
}
async entries() {
if (this._entries) {
return this._entries
} else if (this.nodes) {
this._entries = this.nodes.map(node =>
Entry.from(this.fs, this.path, node)
)
return this._entries
} else {
const directory = await this.fs.entry(this.path)
this._entries = await directory.entries()
return this._entries
}
}
}
class IOError {
constructor(error) {
this.code = 500
this.reason = error
this.stack = error.stack
this.message = error.message
}
}
class InvalidCID {
constructor(error) {
this.code = 404
this.reason = error
this.stack = error.stack
this.message = error.message
}
}
class EntryNotFound {
constructor(path) {
this.code = 404
this.path = path
this.stack = new Error().stack
this.message = this.toString()
}
toString() {
return `Entry \`/ipfs/${this.path}\` not found`
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment