github.com/creachadair/ffs@v0.17.3/file/cursor.go (about) 1 // Copyright 2019 Michael J. Fromberger. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package file 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "io/fs" 22 "time" 23 ) 24 25 // A Cursor bundles a *File with a context so that the file can be used with 26 // the standard interfaces defined by the io package. A Cursor value may be 27 // used only during the lifetime of the request whose context it binds. 28 // 29 // Each Cursor maintains a separate offset position on the underlying file, 30 // affecting Read, Write, and Seek operations on that cursor. The offset value 31 // for a newly-created cursor is always 0. 32 type Cursor struct { 33 ctx context.Context // the captured context 34 offset int64 // the current seek position (≥ 0) 35 kids []string // the directory contents (names only) 36 file *File 37 } 38 39 // Read reads up to len(data) bytes into data from the current offset, and 40 // reports the number of bytes successfully read, as [io.Reader]. 41 func (c *Cursor) Read(data []byte) (int, error) { 42 nr, err := c.file.ReadAt(c.ctx, data, c.offset) 43 c.offset += int64(nr) 44 return nr, err 45 } 46 47 // Write writes len(data) bytes from data to the current offset, and reports 48 // the number of bytes successfully written, as [io.Writer]. 49 func (c *Cursor) Write(data []byte) (int, error) { 50 nw, err := c.file.WriteAt(c.ctx, data, c.offset) 51 c.offset += int64(nw) 52 return nw, err 53 } 54 55 // ReadAt implements the [io.ReaderAt] interface. 56 func (c *Cursor) ReadAt(data []byte, offset int64) (int, error) { 57 return c.file.ReadAt(c.ctx, data, offset) 58 } 59 60 // WriteAt implments the [io.WriterAt] interface. 61 func (c *Cursor) WriteAt(data []byte, offset int64) (int, error) { 62 return c.file.WriteAt(c.ctx, data, offset) 63 } 64 65 // ReadDir implements the [fs.ReadDirFile] interface. 66 func (c *Cursor) ReadDir(n int) ([]fs.DirEntry, error) { 67 if !c.file.Stat().Mode.IsDir() { 68 return nil, fmt.Errorf("%w: not a directory", fs.ErrInvalid) 69 } 70 if c.kids == nil { 71 c.kids = c.file.Child().Names() 72 } 73 out := c.kids 74 if n > 0 && n < len(out) { 75 out = out[:n] 76 c.kids = c.kids[n:] 77 } else { 78 c.kids = nil 79 } 80 if len(out) == 0 { 81 if n > 0 { 82 return nil, io.EOF 83 } 84 return nil, nil 85 } 86 87 de := make([]fs.DirEntry, len(out)) 88 for i, name := range out { 89 de[i] = DirEntry{ctx: c.ctx, parent: c.file, name: name} 90 } 91 return de, nil 92 } 93 94 // Seek sets the starting offset for the next Read or Write, as io.Seeker. 95 func (c *Cursor) Seek(offset int64, whence int) (int64, error) { 96 target := offset 97 switch whence { 98 case io.SeekStart: 99 // use offset as written 100 case io.SeekCurrent: 101 target += c.offset 102 case io.SeekEnd: 103 target += c.file.data.size() 104 default: 105 return 0, fmt.Errorf("seek: invalid offset relation %v", whence) 106 } 107 if target < 0 { 108 return 0, fmt.Errorf("seek: invalid target offset %d", target) 109 } 110 c.offset = target 111 return c.offset, nil 112 } 113 114 // Tell reports the current offset of the cursor. 115 func (c *Cursor) Tell() int64 { return c.offset } 116 117 // Close implements the [io.Closer] interface. A File does not have a system 118 // descriptor, so "closing" performs a flush but does not invalidate the file. 119 func (c *Cursor) Close() error { _, err := c.file.Flush(c.ctx); return err } 120 121 // Stat implements part of the [fs.File] interface. 122 func (c *Cursor) Stat() (fs.FileInfo, error) { return c.file.FileInfo(), nil } 123 124 // FileInfo implements the fs.FileInfo interface for a [File]. 125 type FileInfo struct { 126 name string 127 size int64 128 mode fs.FileMode 129 modTime time.Time 130 file *File 131 } 132 133 func (n FileInfo) Name() string { return n.name } 134 func (n FileInfo) Size() int64 { return n.size } 135 func (n FileInfo) Mode() fs.FileMode { return n.mode } 136 func (n FileInfo) ModTime() time.Time { return n.modTime } 137 func (n FileInfo) IsDir() bool { return n.mode.IsDir() } 138 139 // Sys returns the the [*file.File] whose stat record n carries. 140 func (n FileInfo) Sys() any { return n.file } 141 142 // DirEntry implements the fs.DirEntry interface. 143 type DirEntry struct { 144 ctx context.Context 145 parent *File 146 name string 147 file *File 148 } 149 150 func (d DirEntry) getFile() (*File, error) { 151 if d.file == nil { 152 f, err := d.parent.Open(d.ctx, d.name) 153 if err != nil { 154 return nil, err 155 } 156 d.file = f 157 } 158 return d.file, nil 159 } 160 161 // Name implements part of the [fs.DirEntry] interface. 162 func (d DirEntry) Name() string { return d.name } 163 164 func (d DirEntry) IsDir() bool { 165 f, err := d.getFile() 166 if err == nil { 167 return f.Stat().Mode.IsDir() 168 } 169 return false 170 } 171 172 // Type implements part of the [fs.DirEntry] interface. 173 func (d DirEntry) Type() fs.FileMode { 174 f, err := d.getFile() 175 if err == nil { 176 return f.Stat().Mode.Type() 177 } 178 return 0 179 } 180 181 // Info implements part of the [fs.DirEntry] interface. 182 // The concrete type of a successful result is [FileInfo]. 183 func (d DirEntry) Info() (fs.FileInfo, error) { 184 f, err := d.getFile() 185 if err != nil { 186 return nil, err 187 } 188 return f.FileInfo(), nil 189 }