github.com/falafeljan/pkger@v0.18.0/pkging/mem/file.go (about) 1 package mem 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "path" 10 "path/filepath" 11 "time" 12 13 "github.com/markbates/pkger/here" 14 "github.com/markbates/pkger/pkging" 15 ) 16 17 const timeFmt = time.RFC3339Nano 18 19 var _ pkging.File = &File{} 20 21 type File struct { 22 Here here.Info 23 info *pkging.FileInfo 24 path here.Path 25 data []byte 26 parent here.Path 27 writer *bytes.Buffer 28 reader io.Reader 29 pkging pkging.Pkger 30 } 31 32 // Seek sets the offset for the next Read or Write on file to offset, interpreted according to whence: 0 means relative to the origin of the file, 1 means relative to the current offset, and 2 means relative to the end. It returns the new offset and an error, if any. 33 func (f *File) Seek(ofpkginget int64, whence int) (int64, error) { 34 if len(f.data) > 0 && f.reader == nil { 35 f.reader = bytes.NewReader(f.data) 36 } 37 38 if sk, ok := f.reader.(io.Seeker); ok { 39 return sk.Seek(ofpkginget, whence) 40 } 41 return 0, nil 42 } 43 44 // Close closes the File, rendering it unusable for I/O. 45 func (f *File) Close() error { 46 defer func() { 47 f.reader = nil 48 f.writer = nil 49 }() 50 if f.reader != nil { 51 if c, ok := f.reader.(io.Closer); ok { 52 if err := c.Close(); err != nil { 53 return err 54 } 55 } 56 } 57 58 if f.writer == nil { 59 return nil 60 } 61 62 f.data = f.writer.Bytes() 63 64 fi := f.info 65 fi.Details.Size = int64(len(f.data)) 66 fi.Details.ModTime = pkging.ModTime(time.Now()) 67 f.info = fi 68 return nil 69 } 70 71 // Read reads up to len(b) bytes from the File. It returns the number of bytes read and any error encountered. At end of file, Read returns 0, io.EOF. 72 func (f *File) Read(p []byte) (int, error) { 73 if len(f.data) > 0 && f.reader == nil { 74 f.reader = bytes.NewReader(f.data) 75 } 76 77 if f.reader != nil { 78 return f.reader.Read(p) 79 } 80 81 return 0, fmt.Errorf("unable to read %s", f.Name()) 82 } 83 84 // Write writes len(b) bytes to the File. It returns the number of bytes written and an error, if any. Write returns a non-nil error when n != len(b). 85 func (f *File) Write(b []byte) (int, error) { 86 if f.writer == nil { 87 f.writer = &bytes.Buffer{} 88 } 89 i, err := f.writer.Write(b) 90 return i, err 91 } 92 93 // Info returns the here.Info of the file 94 func (f File) Info() here.Info { 95 return f.Here 96 } 97 98 // Stat returns the FileInfo structure describing file. If there is an error, it will be of type *PathError. 99 func (f File) Stat() (os.FileInfo, error) { 100 if f.info == nil { 101 return nil, os.ErrNotExist 102 } 103 return f.info, nil 104 } 105 106 // Name retuns the name of the file in pkger format 107 func (f File) Name() string { 108 return f.path.String() 109 } 110 111 // Path returns the here.Path of the file 112 func (f File) Path() here.Path { 113 return f.path 114 } 115 116 func (f File) String() string { 117 return f.Path().String() 118 } 119 120 // Readdir reads the contents of the directory associated with file and returns a slice of up to n FileInfo values, as would be returned by Lstat, in directory order. Subsequent calls on the same file will yield further FileInfos. 121 // 122 // If n > 0, Readdir returns at most n FileInfo structures. In this case, if Readdir returns an empty slice, it will return a non-nil error explaining why. At the end of a directory, the error is io.EOF. 123 // 124 // If n <= 0, Readdir returns all the FileInfo from the directory in a single slice. In this case, if Readdir succeeds (reads all the way to the end of the directory), it returns the slice and a nil error. If it encounters an error before the end of the directory, Readdir returns the FileInfo read until that point and a non-nil error. 125 func (f *File) Readdir(count int) ([]os.FileInfo, error) { 126 var infos []os.FileInfo 127 root := f.Path().String() 128 err := f.pkging.Walk(root, func(path string, info os.FileInfo, err error) error { 129 if err != nil { 130 return err 131 } 132 133 if count > 0 && len(infos) == count { 134 return io.EOF 135 } 136 137 if root == path { 138 return nil 139 } 140 141 pt, err := f.pkging.Parse(path) 142 if err != nil { 143 return err 144 } 145 if pt.Name == f.parent.Name { 146 return nil 147 } 148 149 infos = append(infos, info) 150 if info.IsDir() && path != root { 151 return filepath.SkipDir 152 } 153 154 return nil 155 }) 156 157 if err != nil { 158 if _, ok := err.(*os.PathError); ok { 159 return infos, nil 160 } 161 if err != io.EOF { 162 return nil, err 163 } 164 } 165 return infos, nil 166 167 } 168 169 // Open implements the http.FileSystem interface. A FileSystem implements access to a collection of named files. The elements in a file path are separated by slash ('/', U+002F) characters, regardless of host operating system convention. 170 func (f *File) Open(name string) (http.File, error) { 171 pt, err := f.Here.Parse(name) 172 if err != nil { 173 return nil, err 174 } 175 176 if pt == f.path { 177 return f, nil 178 } 179 180 pt.Name = path.Join(f.Path().Name, pt.Name) 181 182 di, err := f.pkging.Open(pt.String()) 183 if err != nil { 184 return nil, err 185 } 186 187 fi, err := di.Stat() 188 if err != nil { 189 return nil, err 190 } 191 192 if fi.IsDir() { 193 d2 := &File{ 194 info: pkging.NewFileInfo(fi), 195 Here: di.Info(), 196 path: pt, 197 parent: f.path, 198 pkging: f.pkging, 199 } 200 di = d2 201 } 202 return di, nil 203 }