|
| 1 | +package fs |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "io" |
| 6 | + "os" |
| 7 | + "strings" |
| 8 | + "sync" |
| 9 | + |
| 10 | + "github.com/ipfs/go-unixfs" |
| 11 | +) |
| 12 | + |
| 13 | +// This file represents a partial and theoretical |
| 14 | +// pkg-level implementation of a filesystem{} |
| 15 | + |
| 16 | +var ( |
| 17 | + pkgRoot FileSystem |
| 18 | + closers []io.Closer |
| 19 | +) |
| 20 | + |
| 21 | +func init() { |
| 22 | + |
| 23 | + // we depend on data from the coreapi to initalize our API nodes |
| 24 | + // so fetch it or something and store it on the FS |
| 25 | + daemon := fallbackApi() |
| 26 | + ctx := deriveCtx(daemon.ctx) // if the daemon cancels, so should we |
| 27 | + |
| 28 | + pkgRoot = NewFileSystem(ctx) |
| 29 | + for _, pair := range [...]struct { |
| 30 | + string |
| 31 | + ParseFn |
| 32 | + }{ |
| 33 | + {"/", rootParser}, |
| 34 | + {"/ipfs", pinRootParser}, // requests for "/ipfs" are directed at pinRootParser(ctx, requestString) |
| 35 | + {"/ipfs/", coreAPIParser}, // all requests beneath "/ipfs/" are directed at coreAPIParser(ctx, requestString) |
| 36 | + {"/ipns", keyRootParser}, |
| 37 | + {"/ipns/", nameAPIParser}, |
| 38 | + {filesRootPrefix, filesAPIParser}, |
| 39 | + } { |
| 40 | + closer, err := pkgRoot.Register(pair.string, pair.ParseFn) |
| 41 | + if err != nil { |
| 42 | + if err == errRegistered { |
| 43 | + panic(fmt.Sprtinf("%q is already registered", pair.string)) |
| 44 | + } |
| 45 | + panic(fmt.Sprtinf("cannot register %q: %s", pair.string, err)) |
| 46 | + } |
| 47 | + |
| 48 | + // store these somewhere important and call them before you exit |
| 49 | + // this will release the namespace at the FS level |
| 50 | + closers = append(closers, closer) // in our example we do nothing with them :^) |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +func NewDefaultFileSystem(parentCtx context.Context) (FileSystem, error) { |
| 55 | + // something like this |
| 56 | + fsCtx := deriveFrom(parentCtx) |
| 57 | + // go onCancel(fsCtx) { callClosers() } () |
| 58 | + return &PathParserRegistry{fsCtx}, nil |
| 59 | +} |
| 60 | + |
| 61 | +type PathParserRegistry struct { |
| 62 | + sync.Mutex |
| 63 | + ctx context.Context |
| 64 | + nodeParsers map[string]ParseFn |
| 65 | +} |
| 66 | + |
| 67 | +func (rr *PathParserRegistry) Register(subrootPath string, nodeParser ParseFn) (io.Closer, error) { |
| 68 | + rr.Lock() |
| 69 | + val, ok := rr.nodeParsers[subrootPath] |
| 70 | + if ok || val != nil { |
| 71 | + return nil, errRegistered |
| 72 | + } |
| 73 | + |
| 74 | + rr.nodeParsers[subrootPath] = nodeParsers |
| 75 | + return func() { |
| 76 | + ri.Lock() |
| 77 | + delete(ri.subSystem, subrootPath) |
| 78 | + ri.Unlock() |
| 79 | + }, nil |
| 80 | +} |
| 81 | + |
| 82 | +func (PathParserRegistry) Lookup(ctx context.Context, name string) (FsPath, error) { |
| 83 | + //NOTE: we can use a pkg level cache here, and fallback to the parser only when necessary |
| 84 | + |
| 85 | + /* very simple map lookup |
| 86 | + path is compared against registered subsystems |
| 87 | + the function associated with the most specific matching prefix wins |
| 88 | + (see registration in init() for additional context) |
| 89 | + */ |
| 90 | + var ( |
| 91 | + subLookup ParseFn |
| 92 | + highestPrecision int |
| 93 | + ) |
| 94 | + for subSystem, parser := range ri.nodeParsers { |
| 95 | + if strings.HasPrefix(name, subSystem) { |
| 96 | + if precision := len(subSystem); precision > highestPrecision { |
| 97 | + highestPrecision = precision |
| 98 | + subLookup = parser |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + if subLookup == nil { |
| 103 | + return nil, errNoHandler |
| 104 | + } |
| 105 | + |
| 106 | + //TODO: [important] should we pass in relative (to namespace) paths here instead of absolute? |
| 107 | + return subLookup(ctx, name) |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +// `pkg/log`-like wrappers for pkg level FS |
| 112 | +// simply so we can say `pkg.lock` instead of `pkg.default.lock` |
| 113 | +// this is more justified in client.go rather than here |
| 114 | +func Lock() { |
| 115 | + pkgRoot.Lock() |
| 116 | +} |
| 117 | + |
| 118 | +func Unlock() { |
| 119 | + pkgRoot.Unlock() |
| 120 | +} |
| 121 | + |
| 122 | +func Lookup(ctx context.Context, name string) (FsPath, error) { |
| 123 | + return pkgRoot.Lookup(ctx, name) |
| 124 | +} |
| 125 | + |
| 126 | +// ... |
| 127 | + |
| 128 | +func OpenFile(name string, flags flags, perm os.FileMode) (FsFile, error) { |
| 129 | + fs.Lock() |
| 130 | + defer fs.Unlock() |
| 131 | + |
| 132 | + fsNode, err := fs.Lookup(ctx, name) |
| 133 | + if err != nil { |
| 134 | + return nil, err |
| 135 | + } |
| 136 | + |
| 137 | + /* |
| 138 | + insert logic pertinent to your own file system implementation here |
| 139 | + i.e. flag parsing, permission checking, link-resolution, etc. |
| 140 | + e.g. If you are implementing a POSIX compliant FS, |
| 141 | + you may want to return an error if write permissions where requested, for a name which resides in a read-only API |
| 142 | +
|
| 143 | + Descriptor management (if any) is also client defined |
| 144 | + For our Go example, we simple use the interface directly, and Close() it when we're done |
| 145 | + If we were implementing POSIX, we could wrap that interface inside of PosixOpen(), |
| 146 | + assign it an integer value, hold on to the interface, and Close it later when PosixClose(ourInt) is called |
| 147 | + Something like this |
| 148 | + `wrappedIo, err := posixRoot.YieldOrGetHandle(fsNode, perm, unixfs.TFile) |
| 149 | + if err != nil { |
| 150 | + //... |
| 151 | + } |
| 152 | + return wrappedIo, nil` |
| 153 | + */ |
| 154 | + |
| 155 | + // YieldIo is expected to handle type/capability checking and return conforming errors |
| 156 | + // XXX: see PR discussion; YieldIo should not require type |
| 157 | + return fsNode.YieldIo(ctx, unixfs.TFile) |
| 158 | +} |
0 commit comments