github.com/adowair/kvdb@v0.0.0-20231101174258-ceca93b03596/kv/util.go (about)

     1  package kv
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/gofrs/flock"
    14  )
    15  
    16  // ErrNotExist is returned when attempting to perform an operation on a key
    17  // which does not exist.
    18  var ErrNotExist = errors.New("key does not exist")
    19  
    20  // ErrBadFormat is returned when a read file contains data which cannot be
    21  // properly parsed into an Entry.
    22  var ErrBadFormat = errors.New("malformed data")
    23  
    24  type Entry struct {
    25  	FirstEdited time.Time
    26  	LastEdited  time.Time
    27  	Value       string
    28  }
    29  
    30  // delimiter is the internal symbol used to separate fields of an Entry
    31  // in its encoded, on-disk format.
    32  const delimiter = ','
    33  
    34  // read is a wrapper around readFrom() which reads a key
    35  // from the current directory.
    36  func read(key string) (*Entry, error) {
    37  	wd, err := os.Getwd()
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	path := filepath.Join(wd, key)
    43  	return readFrom(path)
    44  }
    45  
    46  // readFrom is an internal function that gets the contents of the file at path,
    47  // and parses it into an Entry if possible. The function takes an exclusive
    48  // advisory lock on the file, on which other kvdb instances will block.
    49  func readFrom(path string) (*Entry, error) {
    50  	if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
    51  		return nil, ErrNotExist
    52  	} else if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	lock := flock.New(path)
    57  	err := lock.Lock()
    58  	defer lock.Unlock()
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	b, err := os.ReadFile(path)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	raw := bytes.SplitN(b, []byte{delimiter}, 3)
    69  	if len(raw) != 3 {
    70  		return nil, fmt.Errorf("%w, missing value or timestamp %q", ErrBadFormat, b)
    71  	}
    72  
    73  	firstEdited, err := strconv.ParseInt(string(raw[0]), 10, 64)
    74  	if err != nil {
    75  		return nil, fmt.Errorf("%w, could not parse first-set timestamp %q, %w",
    76  			ErrBadFormat, raw[0], err)
    77  	}
    78  
    79  	lastEdited, err := strconv.ParseInt(string(raw[1]), 10, 64)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("%w, could not parse last-set timestamp %q, %w",
    82  			ErrBadFormat, raw[1], err)
    83  	}
    84  
    85  	value := raw[2]
    86  	if len(value) == 0 {
    87  		return nil, fmt.Errorf("%w, missing value %q", ErrBadFormat, b)
    88  	}
    89  
    90  	return &Entry{
    91  		FirstEdited: time.Unix(firstEdited, 0),
    92  		LastEdited:  time.Unix(lastEdited, 0),
    93  		Value:       string(value),
    94  	}, nil
    95  }
    96  
    97  // write is a wrapper around writeTo() which writes a key to the current
    98  // directory.
    99  func write(key string, entry *Entry) error {
   100  	wd, err := os.Getwd()
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	path := filepath.Join(wd, key)
   106  	return writeTo(path, entry)
   107  }
   108  
   109  // writeTo is an internal function that serializes an Entry, and then
   110  // writes it to the given path. The function takes an exclusive
   111  // advisory lock on the file, on which other kvdb instances will block.
   112  func writeTo(path string, entry *Entry) error {
   113  	if len(entry.Value) == 0 {
   114  		return fmt.Errorf("%w, storing empty \"\" is not allowed", ErrBadFormat)
   115  	}
   116  
   117  	data := fmt.Sprintf(
   118  		"%d%c%d%c%s",
   119  		entry.FirstEdited.Unix(),
   120  		delimiter,
   121  		entry.LastEdited.Unix(),
   122  		delimiter,
   123  		entry.Value)
   124  
   125  	lock := flock.New(path)
   126  	err := lock.Lock()
   127  	defer lock.Unlock()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	return os.WriteFile(path, []byte(data), os.ModePerm)
   133  }
   134  
   135  func delete(key string) error {
   136  	wd, err := os.Getwd()
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	path := filepath.Join(wd, key)
   142  	return deleteFile(path)
   143  }
   144  
   145  func deleteFile(path string) error {
   146  	lock := flock.New(path)
   147  	err := lock.Lock()
   148  	defer lock.Unlock()
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	err = os.Remove(path)
   154  	if errors.Is(err, fs.ErrNotExist) {
   155  		return nil
   156  	}
   157  
   158  	return err
   159  }