github.com/scottcagno/storage@v1.8.0/pkg/_junk/_binary/datafile.go (about) 1 package binary 2 3 import ( 4 "errors" 5 "io" 6 "os" 7 "path/filepath" 8 "sync" 9 ) 10 11 var ( 12 ErrFileClosed = errors.New("error: file is closed") 13 ErrBadEntry = errors.New("error: bad entry") 14 ) 15 16 // DataFile is is a syncronized binary reader and writer 17 type DataFile struct { 18 sync.RWMutex 19 r *Reader 20 w *Writer 21 } 22 23 // OpenDataFile opens and returns a new datafile 24 func OpenDataFile(path string) (*DataFile, error) { 25 // create and sanitize the path 26 path, err := Touch(path) 27 if err != nil { 28 return nil, err 29 } 30 // open a new reader 31 r, err := OpenReader(path) 32 if err != nil { 33 return nil, err 34 } 35 // open a new writer 36 w, err := OpenWriter(path) 37 if err != nil { 38 return nil, err 39 } 40 // init data file and return 41 return &DataFile{ 42 r: r, 43 w: w, 44 }, nil 45 } 46 47 // WriteEntry writes an entry in an append-only fashion 48 func (d *DataFile) WriteEntry(e *Entry) (int64, error) { 49 // lock 50 d.Lock() 51 defer d.Unlock() 52 // write entry (sequentially, append-only) 53 return d.w.WriteEntry(e) 54 } 55 56 // ReadEntry attempts to read and return the next entry sequentially 57 func (d *DataFile) ReadEntry() (*Entry, error) { 58 // read lock 59 d.RLock() 60 defer d.RUnlock() 61 // read next entry sequentially 62 return d.r.ReadEntry() 63 } 64 65 // ReadEntryAt attempts to read and return an entry at the specified offset 66 func (d *DataFile) ReadEntryAt(offset int64) (*Entry, error) { 67 // lock 68 d.RLock() 69 defer d.RUnlock() 70 // read entry at specified offset 71 return d.r.ReadEntryAt(offset) 72 } 73 74 // Range iterates the entries as long as the provided boolean function is true 75 func (d *DataFile) Range(iter func(e *Entry) bool) error { 76 // lock 77 d.Lock() 78 defer d.Unlock() 79 // grab the reader's offset, so we can set it back later 80 offset, err := d.r.Offset() 81 if err != nil { 82 return err 83 } 84 // go to the beginning of the file 85 _, err = d.r.fd.Seek(0, io.SeekStart) 86 if err != nil { 87 return err 88 } 89 // start loop 90 for { 91 // read entry, and check for EOF 92 e, err := d.r.ReadEntry() 93 if err != nil { 94 if err == io.EOF || err == io.ErrUnexpectedEOF { 95 break 96 } 97 return err 98 } 99 // entry is good, lets pass it to our boolean function 100 if !iter(e) { 101 continue // if iter(e) returns false, continue to next entry 102 } 103 } 104 // we are done reading all the entries (hopefully), so 105 // we seek back to where we were at the start of this function 106 _, err = d.r.fd.Seek(offset, io.SeekStart) 107 if err != nil { 108 return err 109 } 110 return nil 111 } 112 113 // Close closes the DataFile 114 func (d *DataFile) Close() error { 115 // lock 116 d.Lock() 117 defer d.Unlock() 118 // close reader 119 err := d.r.Close() 120 if err != nil { 121 return err 122 } 123 // close writer 124 err = d.w.Close() 125 if err != nil { 126 return err 127 } 128 return nil 129 } 130 131 // Touch cleans and initializes the path, files and folders 132 func Touch(path string) (string, error) { 133 // get absolute path 134 path, err := filepath.Abs(path) 135 if err != nil { 136 return "", err 137 } 138 // convert any slashes 139 path = filepath.ToSlash(path) 140 // check to see if the path exists 141 _, err = os.Stat(path) 142 if os.IsNotExist(err) { 143 // create dirs if they need creating 144 dir, file := filepath.Split(path) 145 err = os.MkdirAll(dir, os.ModeDir) 146 if err != nil { 147 return "", err 148 } 149 // create and files if the need creating 150 fd, err := os.Create(dir + file) 151 if err != nil { 152 return "", err 153 } 154 // close, because we are just touching them 155 err = fd.Close() 156 if err != nil { 157 return "", err 158 } 159 } 160 // return sanitized path (creating any files or folders) 161 return path, nil 162 } 163 164 // Offset is a helper function that returns the current 165 // offset of the provided reader or writer 166 func Offset(rw io.ReadWriteSeeker) (int64, error) { 167 return rw.Seek(0, io.SeekCurrent) 168 }