+// Size implements os.FileInfo.
+func (fi fileinfo) Size() int64 {
+ return fi.size
+}
+
+// Sys implements os.FileInfo.
+func (fi fileinfo) Sys() interface{} {
+ return nil
+}
+
+// A CollectionFileSystem is an http.Filesystem plus Stat() and
+// support for opening writable files. All methods are safe to call
+// from multiple goroutines.
+type CollectionFileSystem interface {
+ http.FileSystem
+
+ // analogous to os.Stat()
+ Stat(name string) (os.FileInfo, error)
+
+ // analogous to os.Create(): create/truncate a file and open it O_RDWR.
+ Create(name string) (File, error)
+
+ // Like os.OpenFile(): create or open a file or directory.
+ //
+ // If flag&os.O_EXCL==0, it opens an existing file or
+ // directory if one exists. If flag&os.O_CREATE!=0, it creates
+ // a new empty file or directory if one does not already
+ // exist.
+ //
+ // When creating a new item, perm&os.ModeDir determines
+ // whether it is a file or a directory.
+ //
+ // A file can be opened multiple times and used concurrently
+ // from multiple goroutines. However, each File object should
+ // be used by only one goroutine at a time.
+ OpenFile(name string, flag int, perm os.FileMode) (File, error)
+
+ Mkdir(name string, perm os.FileMode) error
+ Remove(name string) error
+ RemoveAll(name string) error
+ Rename(oldname, newname string) error
+ MarshalManifest(prefix string) (string, error)
+}
+
+type fileSystem struct {
+ dirnode
+}
+
+func (fs *fileSystem) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
+ return fs.dirnode.OpenFile(path.Clean(name), flag, perm)
+}
+
+func (fs *fileSystem) Open(name string) (http.File, error) {
+ return fs.dirnode.OpenFile(path.Clean(name), os.O_RDONLY, 0)
+}
+
+func (fs *fileSystem) Create(name string) (File, error) {
+ return fs.dirnode.OpenFile(path.Clean(name), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0)
+}
+
+func (fs *fileSystem) Stat(name string) (os.FileInfo, error) {
+ f, err := fs.OpenFile(name, os.O_RDONLY, 0)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return f.Stat()
+}
+
+type inode interface {
+ Parent() inode
+ Read([]byte, filenodePtr) (int, filenodePtr, error)
+ Write([]byte, filenodePtr) (int, filenodePtr, error)
+ Truncate(int64) error
+ Readdir() []os.FileInfo
+ Size() int64
+ Stat() os.FileInfo
+ sync.Locker
+ RLock()
+ RUnlock()
+}
+
+// filenode implements inode.
+type filenode struct {
+ fileinfo fileinfo
+ parent *dirnode
+ extents []extent
+ repacked int64 // number of times anything in []extents has changed len
+ memsize int64 // bytes in memExtents
+ sync.RWMutex
+}
+
+// filenodePtr is an offset into a file that is (usually) efficient to
+// seek to. Specifically, if filenode.repacked==filenodePtr.repacked
+// then filenode.extents[filenodePtr.extentIdx][filenodePtr.extentOff]
+// corresponds to file offset filenodePtr.off. Otherwise, it is
+// necessary to reexamine len(filenode.extents[0]) etc. to find the
+// correct extent and offset.
+type filenodePtr struct {
+ off int64
+ extentIdx int
+ extentOff int
+ repacked int64
+}
+
+// seek returns a ptr that is consistent with both startPtr.off and
+// the current state of fn. The caller must already hold fn.RLock() or
+// fn.Lock().
+//
+// If startPtr points beyond the end of the file, ptr will point to
+// exactly the end of the file.
+//
+// After seeking:
+//
+// ptr.extentIdx == len(filenode.extents) // i.e., at EOF
+// ||
+// filenode.extents[ptr.extentIdx].Len() >= ptr.extentOff
+func (fn *filenode) seek(startPtr filenodePtr) (ptr filenodePtr) {
+ ptr = startPtr
+ if ptr.off < 0 {
+ // meaningless anyway
+ return
+ } else if ptr.off >= fn.fileinfo.size {
+ ptr.extentIdx = len(fn.extents)
+ ptr.extentOff = 0
+ ptr.repacked = fn.repacked
+ return
+ } else if ptr.repacked == fn.repacked {
+ // extentIdx and extentOff accurately reflect ptr.off,
+ // but might have fallen off the end of an extent
+ if ptr.extentOff >= fn.extents[ptr.extentIdx].Len() {
+ ptr.extentIdx++
+ ptr.extentOff = 0
+ }
+ return
+ }
+ defer func() {
+ ptr.repacked = fn.repacked
+ }()
+ if ptr.off >= fn.fileinfo.size {
+ ptr.extentIdx, ptr.extentOff = len(fn.extents), 0
+ return
+ }
+ // Recompute extentIdx and extentOff. We have already
+ // established fn.fileinfo.size > ptr.off >= 0, so we don't
+ // have to deal with edge cases here.
+ var off int64
+ for ptr.extentIdx, ptr.extentOff = 0, 0; off < ptr.off; ptr.extentIdx++ {
+ // This would panic (index out of range) if
+ // fn.fileinfo.size were larger than
+ // sum(fn.extents[i].Len()) -- but that can't happen
+ // because we have ensured fn.fileinfo.size is always
+ // accurate.
+ extLen := int64(fn.extents[ptr.extentIdx].Len())
+ if off+extLen > ptr.off {
+ ptr.extentOff = int(ptr.off - off)
+ break
+ }
+ off += extLen
+ }
+ return
+}
+
+func (fn *filenode) appendExtent(e extent) {
+ fn.Lock()
+ defer fn.Unlock()
+ fn.extents = append(fn.extents, e)
+ fn.fileinfo.size += int64(e.Len())
+}
+
+func (fn *filenode) Parent() inode {
+ return fn.parent
+}
+
+func (fn *filenode) Readdir() []os.FileInfo {
+ return nil
+}
+
+func (fn *filenode) Read(p []byte, startPtr filenodePtr) (n int, ptr filenodePtr, err error) {
+ ptr = fn.seek(startPtr)
+ if ptr.off < 0 {
+ err = ErrNegativeOffset
+ return