github.com/influx6/npkg@v0.8.8/nfs/filesystem.go (about) 1 package nfs 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "path/filepath" 8 "strings" 9 "sync" 10 "time" 11 ) 12 13 // errors ... 14 var ( 15 ErrExist = errors.New("Path exists") 16 ErrNotExist = errors.New("Path does not exists") 17 ErrToManyParts = errors.New("Prefix must contain only two parts: /bob/log") 18 ) 19 20 // FileMode defines the mode value of a file. 21 type FileMode uint32 22 23 // FileInfo defines an interface representing a file into. 24 type FileInfo interface { 25 Name() string 26 Size() int64 27 ModTime() time.Time 28 IsDir() bool 29 } 30 31 // File defines a interface for representing a file. 32 type File interface { 33 io.Closer 34 io.Reader 35 io.Seeker 36 Readdir(count int) ([]FileInfo, error) 37 Stat() (FileInfo, error) 38 } 39 40 // FileSystem defines a interface for a virutal nfs 41 // representing a file. 42 type FileSystem interface { 43 Open(string) (File, error) 44 } 45 46 // New returns a new instance of a VirtualFileSystem as a FileSystem type. 47 func New(fn GetFile) FileSystem { 48 return VirtualFileSystem{ 49 GetFileFunc: fn, 50 } 51 } 52 53 // VirtualFile exposes a slice of []byte and associated name as 54 // a http.File. It implements http.File interface. 55 type VirtualFile struct { 56 *bytes.Reader 57 FileName string 58 FileSize int64 59 FileMod time.Time 60 } 61 62 // NewVirtualFile returns a new instance of VirtualFile. 63 func NewVirtualFile(r *bytes.Reader, filename string, size int64, mod time.Time) *VirtualFile { 64 return &VirtualFile{ 65 Reader: r, 66 FileSize: size, 67 FileName: filename, 68 FileMod: mod, 69 } 70 } 71 72 // ModTime returns associated mode time for file. 73 func (vf *VirtualFile) ModTime() time.Time { 74 if vf.FileMod.IsZero() { 75 return time.Now() 76 } 77 78 return vf.FileMod 79 } 80 81 // IsDir returns false because this is a virtual file. 82 func (vf *VirtualFile) IsDir() bool { 83 return false 84 } 85 86 // Mode returns associated file mode of file. 87 func (vf *VirtualFile) Mode() FileMode { 88 return 0700 89 } 90 91 // Size returns data file size. 92 func (vf *VirtualFile) Size() int64 { 93 return vf.FileSize 94 } 95 96 // Name returns filename of giving file, either as a absolute or 97 // relative path. 98 func (vf *VirtualFile) Name() string { 99 return vf.FileName 100 } 101 102 // Stat returns VirtualFile which implements os.FileInfo for virtual 103 // file to meet http.File interface. 104 func (vf *VirtualFile) Stat() (FileInfo, error) { 105 return vf, nil 106 } 107 108 // Readdir returns nil slices as this is a file not a directory. 109 func (vf *VirtualFile) Readdir(n int) ([]FileInfo, error) { 110 return nil, nil 111 } 112 113 // Close returns nothing. 114 func (vf *VirtualFile) Close() error { 115 return nil 116 } 117 118 // GetFile define a function type that returns a VirtualFile type 119 // or an error. 120 type GetFile func(string) (File, error) 121 122 // StripPrefix returns a new GetFile which wraps the previous provided 123 // GetFile and always strips provided prefix from incoming path. 124 func StripPrefix(prefix string, from GetFile) GetFile { 125 return func(path string) (File, error) { 126 return from(strings.TrimPrefix(path, prefix)) 127 } 128 } 129 130 // VirtualFileSystem connects a series of functions which are provided 131 // to retrieve bundled files and serve to a http server. It implements 132 // http.FileSystem interface. 133 type VirtualFileSystem struct { 134 GetFileFunc GetFile 135 } 136 137 // Open returns associated file with given name if found else 138 // returning an error. It implements http.FileSystem.Open method. 139 func (v VirtualFileSystem) Open(name string) (File, error) { 140 if v.GetFileFunc == nil { 141 return nil, ErrNotExist 142 } 143 144 vfile, err := v.GetFileFunc(name) 145 if err != nil { 146 return nil, err 147 } 148 149 return vfile, nil 150 } 151 152 type systemNode struct { 153 prefix string 154 root FileSystem 155 nodes map[string]FileSystem 156 } 157 158 // SystemGroup allows the combination of multiple nfs to 159 // respond to incoming request based on initial path prefix. 160 type SystemGroup struct { 161 ml sync.Mutex 162 systems map[string]systemNode 163 } 164 165 // NewSystemGroup returns a new instance of SystemGroup. 166 func NewSystemGroup() *SystemGroup { 167 return &SystemGroup{ 168 systems: make(map[string]systemNode), 169 } 170 } 171 172 // MustRegister will panic if the prefix and FileSystem failed to register 173 // It returns itself if successfully to allow chaining. 174 func (fs *SystemGroup) MustRegister(prefix string, m FileSystem) *SystemGroup { 175 if err := fs.Register(prefix, m); err != nil { 176 panic(err) 177 } 178 179 return fs 180 } 181 182 // Register adds giving file system to handling paths with given prefix. 183 func (fs *SystemGroup) Register(prefix string, m FileSystem) error { 184 defer fs.ml.Unlock() 185 fs.ml.Lock() 186 187 if strings.Contains(prefix, "\\") { 188 prefix = filepath.ToSlash(prefix) 189 } 190 191 prefix = strings.TrimPrefix(prefix, "/") 192 prefix = strings.TrimSuffix(prefix, "/") 193 194 parts := strings.Split(prefix, "/") 195 if len(parts) > 2 { 196 return ErrToManyParts 197 } 198 199 root := parts[0] 200 if len(parts) == 1 { 201 if node, ok := fs.systems[root]; ok { 202 node.root = m 203 fs.systems[root] = node 204 return nil 205 } 206 207 var node systemNode 208 node.prefix = root 209 node.root = m 210 node.nodes = make(map[string]FileSystem) 211 fs.systems[root] = node 212 return nil 213 } 214 215 tail := parts[1] 216 if node, ok := fs.systems[root]; ok { 217 node.nodes[tail] = m 218 fs.systems[root] = node 219 return nil 220 } 221 222 var node systemNode 223 node.prefix = root 224 node.nodes = make(map[string]FileSystem) 225 node.nodes[tail] = m 226 fs.systems[root] = node 227 228 return nil 229 } 230 231 // Open attempts to locate giving path from different file systems, else returning error. 232 func (fs *SystemGroup) Open(path string) (File, error) { 233 if len(path) == 0 { 234 return nil, ErrNotExist 235 } 236 237 defer fs.ml.Unlock() 238 fs.ml.Lock() 239 240 if strings.Contains(path, "\\") { 241 path = filepath.ToSlash(path) 242 } 243 244 path = strings.TrimPrefix(path, "/") 245 path = strings.TrimSuffix(path, "/") 246 247 parts := strings.Split(path, "/") 248 if node, ok := fs.systems[parts[0]]; ok { 249 if len(parts) > 1 { 250 if subnode, ok := node.nodes[parts[1]]; ok { 251 return subnode.Open(filepath.Join(parts[2:]...)) 252 } 253 } 254 255 if node.root != nil { 256 return node.root.Open(filepath.Join(parts[1:]...)) 257 } 258 } 259 260 return nil, ErrNotExist 261 }