-
Notifications
You must be signed in to change notification settings - Fork 54
Conversation
Having reviewed this, and the corresponding context, my first thought is that naming is key here to avoid confusion, and made complicated by the fact that we already have a bunch of concepts in IPFS with the term "file system" in them. So that deserves some thought. If I understand correctly, in the FUSE case the hierarchy looks something like something like this, going from Blocks to FUSE:
Am I getting it right? Here are some thoughts:
My first thought is the Index should be an explicit data member of FileSystem, not an embedded interface (i.e. composition over inheritance, sorta?). I can see lots of reasons to customize the index, not a whole number of reasons to write a custom implementation of On a broader level, my meta comment is about how generic we need to get. I can see that you have to implement FsNode for UnixFs / MFS / IPFS / UnixFSv2 to support FUSE (I can't completely tell if you have to implement FileSystem/Index purely to support FUSE). And then it makes sense that they are useful generic interfaces, and people certainly might want to make more FsNode types specifically for the FUSE case. So it certainly makes sense to expose them, and provide a mechanism for implementing more FSNode types in the case of FUSE (maybe this is where the need for Index/FileSystem comes in?). The question is whether the abstraction should keep going, and escalate to providing these as generics in go-core-interface, for implementing generic indexes and generic filesystems in support of generic needs. I'm not really sure what the right answers are here without more context, just want to surface these questions. |
Sorry one more clarification -- all these are just questions, not objections to this concept, and some may be based on misconceptions I have about interface-go-ipfs-core or other broader concepts in the IPFS system. It feels a bit above my pay grade to try to figure this stuff out -- so I'm mainly trying to offer feedback as best I can. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Some random comments)
I'd limit context use to cancelling requests. For anything that requires more complex closing logic (like flushing buffers, which needs to keep blockstore open), I'd use https://github.com/jbenet/goprocess
filesystem/index.go
Outdated
// we depend on data from the coreapi to initalize our API nodes | ||
// so fetch it or something and store it on the FS | ||
daemon := fallbackApi() | ||
ctx := deriveCtx(daemon.ctx) // if the daemon cancels, so should we |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you'll be constructing fallback api, you'll control the context. https://github.com/ipfs/interface-go-ipfs-core/issues/32
filesystem/index.go
Outdated
{"/ipns/", nameAPIParser}, | ||
{filesRootPrefix, filesAPIParser}, | ||
} { | ||
closer, err := pkgRoot.Register(pair.string, pair.ParseFn) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to adopt some unix terminology - pkgRoot.Mount
?
filesystem/index.go
Outdated
func NewDefaultFileSystem(parentCtx context.Context) (FileSystem, error) { | ||
// something like this | ||
fsCtx := deriveFrom(parentCtx) | ||
// go onCancel(fsCtx) { callClosers() } () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use goprocess instead of contexts since it allows for much nicer closing logic (and we already use it for quite a few things)
filesystem/interface.go
Outdated
corepath.Path | ||
InitMetadata(context.Context) (FileMetadata, error) | ||
Metadata() FileMetadata | ||
//RWLocker Maybe? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that eventually this may need to work over http api
Premature post because hotkeys, ignore that^ @hannahhoward
Agreed. Team members can attest to my VERY GOOD pre-draft explanations.
It seems so! Generally trying to construct a file system interface from the technologies we have, unified under a common overarching interface.
In general, we share the appeal of the CoreAPI for Go developers. Exposing multiple IPFS operations, through this unified interface. Wrapping the CoreAPI and others internally, to expose IPFS as a filesystem, usable by the Go runtime. Making snippets similar to this, legal. Without developers needing to know specific/new conventions for specific/new APIs. func client() {
f, err := fs.OpenFile("/ipfs/Qm.../file.ext", flags, perm)
if err != nil {
//...
}
defer f.Close()
x, y := f.Read(someByteSlice)
}
A self contained package was certainly the original intent, however it's seemingly useful to expose this interface (only) at the language level if we can.
Agreed. I'm not sure what yet but these names need work. It's unfortunately really easy to fall into stuttering patterns. -- |
Offline feedback note (unrelated to above): This is not entirley thought out yet. There may be better ways of handling it, but I'll demonstrate how they're used in concept, and why we need a way of passing arbitrary data from the request to the node. If you wanted to implement a filesystem that supported "symlink creation" as a concept, your package would look something like this: package myFs
import fs "core/filesystem"
var root = overlay(fs.BaseRoot) // derive from an Fs that already has standard conventions built-in
CreateLink(name, target ) {
/* Here, the FS package is (only) responsible for defining its (own) operation specifications
We have decided that for `myFs`, this operation succeeds if:
- `name` does not exist
- `target` exist
- The IO operation itself actually succeeds
otherwise we return a,b,c for scenarios x, y, z
The implementation follows:
*/
// To start, we want to get an abstract type "FsNode"
// which points to a filesystem location that may or may not exist
fsNode, err := root.Lookup(name)
switch (err) {
default:
log(err); return err
nil:
log(...); return fs.ErrExist
case fs.ErrNoExist:
break
}
// check the target
targetNode, err := root.Lookup(target)
if err != nil {
log(err); return wrap(err)
}
/* We have now determined that this is a valid request,
and would like to relay it to the API the name belongs to
If the name resides in a namespace where symlinks are stored as a unixfs type
we'd normally have to make unixfs requests here
However, `Lookup` returned a node to us
that already implements an interface between us and the node
All that's needed from us, is the type+request specific values required by the node
(Which ultimately make it to the unixfs (api-level), which then get turned into go-merkledag ProtoNodes (format-level)
*/
callCtx : = context.WithValue(ctx, fs.TargetKey, target)
err := fsNode.Create(callCtx, name, unixfs.TSymlink)
if err != nil {
return x
}
return y
} On the consumer side: import myFs
myFs.CreateLink("/namespace1/link", "/somewhere/target") Ultimately, we just need to be able to define and enforce filesystem behaviour, by way of processing client requests, and orchestrating abstracts nodes with request values we have access to, but the node may not (but still requires). Something like this: This requires us to interface with the node in a way that crosses api and format boundaries. Implementing new nodes and adding them in your own filesystem would look like this: package myNode
const P2Ppipe FsType = 123456
type PeerKey struct{}
type Pipe struct {...}
func (p *Pipe) Pipe Create(ctx context, name string, type FsType) {
//...
if p.handle != nil { return error }
friend, := ctx.Value(PeerKey{})
p.handle, err = libp2p.connectTo(friend)
//...
}
func (p *Pipe) Stat(){...} package myFs
import myNode
CreatePipe(name, friend libp2p.ID) {
//...
callCtx : = context.WithValue(ctx, myNode.PeerKey, friend)
myFs.Create(callCtx, name, myNode.P2Ppipe)
//...
} This may be a bad way of handling it though. |
🚗 💨 apologies for a drive-by comment
Not sure how useful it is, but here is a potential data point: We've been thinking about adding WebDAV support (ipfs/in-web-browsers#146) at some point. At high level it is similar to FUSE or any other FS mapper (verbs include COPY, MOVE, MKCOL, LOCK, UNLOCK). I suspect its future implementation could use filesystem interface proposed here.
Yup, in JS land this type of shim/polyfill is quite popular (e.g. iso-stream-http acts as Node's Just like you hinted, this type of abstraction feels language-specific. I am not sure how well it plays with an idea of extending Core API and exposing golang-first APIs over HTTP API. |
e335def
to
649eb3c
Compare
*Draft Notes -I'll update this as things become more clear, but please critique heavily in the meantime.
I propose that we expose a filesystem interface, that allows developers to treat some object, as an index comprised of generic nodes. Ultimately allowing access to content living in IPFS APIs, in the same unified way that the
os
pkg exposes access to nodes on disks and other media.In essence, it shouldn't matter what API we need to interact with, just that some operation be performed on it.
In the same way you can
os.OpenFile
on any valid OS path, regardless of if it resides on a disk, nfs, etc.you should be able to call
ipfs.OpenFile
with any valid IPFS path,, regardless of if it resides in IPFS, IPNS, MFS, UnixFS, et al.i.e.
The utility of providing this interface, affords us the ability to implement a standard filesystem pkg that people may wrap at a Go-API level, to supply their own specific logic around file system operations, without having to implement operation bindings for every API we have, and while conforming to a single standard.
Our own use cases include; Using this to implement the FUSE interface, and in a meta fashion, exposing IPFS as a "file system" to Go, in a way similar to MFS.
Included in the patchset are
interface.go
Which defines the interface for a filesystem and standard IO types.
index.go
Which is a partial implementation of this, showing off a slash(
/
) delimited, namespace oriented, filing system.client.go
Which shows of how 3rd party developers would use these interfaces, and construct their own.
Not included are the parser and node implementations. The interfaces around these may change
But they are very simple bindings to our APIs. You can imagine an IPFS namespace parser to require a handle to the coreapi, which is used during FS+IO operations. Others are similar.
TODO: ...
Additional context:
You can see some of the lineage of this here: ipfs/kubo#5003 (comment)