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  }