| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- package keyvalue
- import (
- "context"
- "io"
- "path"
- "time"
- "github.com/hack-pad/hackpadfs"
- "github.com/hack-pad/hackpadfs/keyvalue/blob"
- )
- var (
- _ interface {
- hackpadfs.File
- io.ReaderAt
- io.WriterAt
- blob.Reader
- blob.ReaderAt
- blob.Writer
- blob.WriterAt
- hackpadfs.DirReaderFile
- hackpadfs.ReadWriterFile
- hackpadfs.SeekerFile
- hackpadfs.TruncaterFile
- } = &file{}
- )
- type file struct {
- *fileData
- offset int64
- flag int
- }
- type fileData struct {
- fs *FS
- path string // path is stored as the "key", keeping it here is for generating hackpadfs.FileInfo's
- modeOverride *hackpadfs.FileMode
- modTimeOverride time.Time
- runOnceFileRecord
- }
- func (f *fileData) Mode() hackpadfs.FileMode {
- if f.modeOverride != nil {
- return *f.modeOverride
- }
- return f.runOnceFileRecord.Mode()
- }
- func (f *fileData) ModTime() time.Time {
- var zero time.Time
- if f.modTimeOverride != zero {
- return f.modTimeOverride
- }
- return f.runOnceFileRecord.ModTime()
- }
- // getFile returns a file for 'path' if it exists, os.ErrNotExist otherwise
- func (fs *FS) getFile(path string) (*file, error) {
- if !hackpadfs.ValidPath(path) {
- return nil, hackpadfs.ErrInvalid
- }
- f := fileData{
- path: path,
- fs: fs,
- }
- txn, err := fs.store.Transaction(TransactionOptions{
- Mode: TransactionReadOnly,
- })
- if err != nil {
- return nil, err
- }
- txn.Get(path)
- results, err := txn.Commit(context.Background())
- if err != nil {
- return nil, err
- }
- f.runOnceFileRecord.record, err = results[0].Record, results[0].Err
- return &file{fileData: &f}, err
- }
- // setFile write the 'file' data to the store at 'path'. If 'file' is nil, the file is deleted.
- func (fs *FS) setFile(path string, file FileRecord) error {
- var contents blob.Blob
- if file != nil && file.Mode().IsRegular() {
- var err error
- contents, err = file.Data()
- if err != nil {
- return err
- }
- }
- txn, err := fs.store.Transaction(TransactionOptions{
- Mode: TransactionReadWrite,
- })
- if err == nil {
- err = fs.setFileTxn(txn, path, file, contents)
- }
- if err == nil {
- _, err = txn.Commit(context.Background())
- }
- return err
- }
- func (fs *FS) setFileTxn(txn Transaction, path string, file FileRecord, contents blob.Blob) error {
- if !hackpadfs.ValidPath(path) {
- return hackpadfs.ErrInvalid
- }
- if contents == nil && file != nil && file.Mode().IsRegular() {
- panic("Contents must not be nil for regular file")
- }
- txn.Set(path, file, contents)
- return nil
- }
- type fileInfo struct {
- Record FileRecord
- Path string
- }
- func (f fileInfo) Name() string {
- return path.Base(f.Path)
- }
- func (f fileInfo) Size() int64 {
- return f.Record.Size()
- }
- func (f fileInfo) Mode() hackpadfs.FileMode {
- return f.Record.Mode()
- }
- func (f fileInfo) ModTime() time.Time {
- return f.Record.ModTime()
- }
- func (f fileInfo) IsDir() bool {
- return f.Record.Mode().IsDir()
- }
- func (f fileInfo) Sys() interface{} {
- return f.Record.Sys()
- }
- func (fs *FS) newFile(path string, flag int, mode hackpadfs.FileMode) *file {
- return &file{
- flag: flag,
- fileData: &fileData{
- fs: fs,
- path: path,
- runOnceFileRecord: runOnceFileRecord{
- record: NewBaseFileRecord(0, time.Now(), mode, nil,
- func() (blob.Blob, error) {
- return blob.NewBytes(nil), nil
- },
- nil,
- ),
- },
- },
- }
- }
- func (f *fileData) save() error {
- return f.fs.setFile(f.path, f)
- }
- func (f *fileData) info() hackpadfs.FileInfo {
- return fileInfo{Record: f, Path: f.path}
- }
- func (f *file) Close() error {
- if f.fileData == nil {
- return hackpadfs.ErrClosed
- }
- f.fileData = nil
- return nil
- }
- func (f *file) updateModTime() {
- f.modTimeOverride = time.Now()
- }
- func (f *file) Read(p []byte) (n int, err error) {
- n, err = f.ReadAt(p, f.offset)
- f.offset += int64(n)
- return
- }
- func (f *file) ReadBlob(length int) (blob blob.Blob, n int, err error) {
- blob, n, err = f.ReadBlobAt(length, f.offset)
- f.offset += int64(n)
- return
- }
- func (f *file) ReadAt(p []byte, off int64) (n int, err error) {
- blob, n, err := f.ReadBlobAt(len(p), off)
- if blob != nil {
- copy(p, blob.Bytes())
- }
- return n, err
- }
- func (f *file) ReadBlobAt(length int, off int64) (b blob.Blob, n int, err error) {
- if off >= int64(f.Size()) {
- return nil, 0, io.EOF
- }
- max := int64(f.Size())
- end := off + int64(length)
- if end > max {
- end = max
- }
- data, err := f.Data()
- if err != nil {
- return nil, 0, err
- }
- b, err = blob.View(data, off, end)
- if err != nil {
- return nil, 0, err
- }
- n = b.Len()
- if off+int64(n) == max {
- return b, n, io.EOF
- }
- return b, n, nil
- }
- func (f *file) Seek(offset int64, whence int) (int64, error) {
- newOffset := f.offset
- switch whence {
- case io.SeekStart:
- newOffset = offset
- case io.SeekCurrent:
- newOffset += offset
- case io.SeekEnd:
- newOffset = int64(f.Size()) + offset
- default:
- return 0, &hackpadfs.PathError{Op: "seek", Path: f.path, Err: hackpadfs.ErrInvalid}
- }
- if newOffset < 0 {
- return 0, &hackpadfs.PathError{Op: "seek", Path: f.path, Err: hackpadfs.ErrInvalid}
- }
- f.offset = newOffset
- return newOffset, nil
- }
- func (f *file) Write(p []byte) (n int, err error) {
- n, err = f.WriteBlob(blob.NewBytes(p))
- return
- }
- func (f *file) WriteBlob(p blob.Blob) (n int, err error) {
- n, err = f.writeBlobAt("write", p, f.offset)
- f.offset += int64(n)
- return
- }
- func (f *file) WriteAt(p []byte, off int64) (n int, err error) {
- return f.WriteBlobAt(blob.NewBytes(p), off)
- }
- func (f *file) WriteBlobAt(p blob.Blob, off int64) (n int, err error) {
- return f.writeBlobAt("writeat", p, off)
- }
- func (f *file) writeBlobAt(op string, p blob.Blob, off int64) (n int, err error) {
- if f.flag&hackpadfs.FlagAppend != 0 {
- off = int64(f.Size())
- }
- endIndex := off + int64(p.Len())
- if int64(f.Size()) < endIndex {
- data, err := f.Data()
- if err != nil {
- return 0, &hackpadfs.PathError{Op: op, Path: f.path, Err: err}
- }
- err = blob.Grow(data, endIndex-int64(f.Size()))
- if err != nil {
- return 0, &hackpadfs.PathError{Op: op, Path: f.path, Err: err}
- }
- }
- data, err := f.Data()
- if err != nil {
- return 0, &hackpadfs.PathError{Op: op, Path: f.path, Err: err}
- }
- n, err = blob.Set(data, p, off)
- if err != nil {
- return n, &hackpadfs.PathError{Op: op, Path: f.path, Err: err}
- }
- if n != 0 {
- f.updateModTime()
- }
- err = f.save()
- return
- }
- func (f *file) Stat() (hackpadfs.FileInfo, error) {
- return fileInfo{Record: &f.runOnceFileRecord, Path: f.path}, nil
- }
- func (f *file) Truncate(size int64) error {
- if f.Mode().IsDir() {
- return &hackpadfs.PathError{Op: "truncate", Path: f.path, Err: hackpadfs.ErrIsDir}
- }
- length := int64(f.Size())
- switch {
- case size < 0:
- return &hackpadfs.PathError{Op: "truncate", Path: f.path, Err: hackpadfs.ErrInvalid}
- case size == length:
- return nil
- case size > length:
- data, err := f.Data()
- if err != nil {
- return &hackpadfs.PathError{Op: "truncate", Path: f.path, Err: err}
- }
- err = blob.Grow(data, size-length)
- if err != nil {
- return &hackpadfs.PathError{Op: "truncate", Path: f.path, Err: err}
- }
- case size < length:
- data, err := f.Data()
- if err != nil {
- return &hackpadfs.PathError{Op: "truncate", Path: f.path, Err: err}
- }
- err = blob.Truncate(data, size)
- if err != nil {
- return &hackpadfs.PathError{Op: "truncate", Path: f.path, Err: err}
- }
- }
- f.updateModTime()
- return f.save()
- }
- func (f *file) ReadDir(n int) ([]hackpadfs.DirEntry, error) {
- dirNames, err := f.ReadDirNames()
- if err != nil {
- return nil, &hackpadfs.PathError{Op: "readdir", Path: f.path, Err: err}
- }
- start, end := f.offset, f.offset+int64(n)
- if n <= 0 {
- start, end = 0, int64(len(dirNames))
- } else if end > int64(len(dirNames)) {
- end = int64(len(dirNames))
- }
- offsetAdd := end - start
- var entries []hackpadfs.DirEntry
- for _, name := range dirNames[start:end] {
- entry, err := newDirEntry(f.fs, f.path, name)
- if err != nil {
- return nil, err
- }
- entries = append(entries, entry)
- }
- f.offset += offsetAdd
- return entries, nil
- }
- type dirEntry struct {
- info hackpadfs.FileInfo
- baseName string
- }
- func newDirEntry(fs hackpadfs.FS, basePath, name string) (*dirEntry, error) {
- info, err := hackpadfs.Stat(fs, path.Join(basePath, name))
- return &dirEntry{
- baseName: name,
- info: info,
- }, err
- }
- func (d *dirEntry) Name() string {
- return d.baseName
- }
- func (d *dirEntry) IsDir() bool {
- return d.info.Mode().IsDir()
- }
- func (d *dirEntry) Type() hackpadfs.FileMode {
- return d.info.Mode().Type()
- }
- func (d *dirEntry) Info() (hackpadfs.FileInfo, error) {
- return d.info, nil
- }
- func (f *file) Chmod(mode hackpadfs.FileMode) error {
- newMode := (f.Mode() & ^chmodBits) | (mode & chmodBits)
- f.modeOverride = &newMode
- return f.save()
- }
|