diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e69de29 diff --git a/Bitcaspy.go b/Bitcaspy.go index 4efafa0..8354690 100644 --- a/Bitcaspy.go +++ b/Bitcaspy.go @@ -78,6 +78,17 @@ func Init(cfg ...Config) (*BitCaspy, error) { } } + // If not in readonly mode, generate a lock file to ensure that only one process is allowed to access the active datafile + if !opts.readOnly { + lockFilePath := filepath.Join(opts.dir, LOCKFILE) + if !exists(lockFilePath) { + _, err := getFLock(lockFilePath) + if err != nil { + return nil, err + } + } + } + // Create a new active datafile df, err := datafile.New(opts.dir, index) if err != nil { @@ -113,8 +124,99 @@ func Init(cfg ...Config) (*BitCaspy, error) { go BitCaspy.checkFileSize(BitCaspy.opts.checkFileSizeInterval) // if BitCaspy.opts.syncInterval != nil{ - // go BitCaspy + // go BitCaspy.syn // } return BitCaspy, nil } + +func (b *BitCaspy) Close() error { + b.Lock() + defer b.Unlock() + + // Generate Hint files from the keydir + if err := b.genrateHintFiles(); err != nil { + fmt.Errorf("Error generating Hint files from keydir: %v", err) + } + + // Close the active data file + if err := b.df.Close(); err != nil { + fmt.Errorf("Error closing active data file: %v", err) + } + + // Close all the stale data files + for _, df := range b.stale { + if err := df.Close(); err != nil { + fmt.Errorf("Error closing stale data file: %v", err) + } + } + return nil +} + +// Gets the key from the keydir and then checks for the key in the keydir hashmap +// Then it goes to the value offset in the data file +func (b *BitCaspy) Get(key string) ([]byte, error) { + record, err := b.get(key) + if err != nil { + return nil, err + } + if record.isExpired() { + return nil, ErrExpiredKey + } + if !record.isValidChecksum() { + return nil, ErrChecksumMismatch + } + return record.Value, nil +} + +// puts the key into the active data file and puts the key and inserts in the keyDir hashmap the fileId, vsize and offset at the data file +func (b *BitCaspy) Put(key string, value []byte) error { + if b.opts.readOnly { + return ErrReadOnly + } + // + return b.put(b.df, key, value, nil) +} + +func (b *BitCaspy) Delete(key string) error { + b.Lock() + defer b.Unlock() + if b.opts.readOnly { + return ErrReadOnly + } + return b.delete(key) +} + +func (b *BitCaspy) list_keys() []string { + b.Lock() + defer b.Unlock() + key_lists := make([]string, 0, len(b.KeyDir)) + + for key := range b.KeyDir { + key_lists = append(key_lists, key) + } + return key_lists +} + +func (b *BitCaspy) Fold(foldingFunc func(key string, value []byte, acc string) error) error { + b.Lock() + defer b.Unlock() + + for key, _ := range b.KeyDir { + value, err := b.Get(key) + if err != nil { + return err + } + if err := foldingFunc(key, value, "rohit"); err != nil { + return err + } + } + return nil +} + +func (b *BitCaspy) Sync() error { + b.Lock() + defer b.Unlock() + + return b.df.Sync() +} diff --git a/erros.go b/erros.go new file mode 100644 index 0000000..0f74491 --- /dev/null +++ b/erros.go @@ -0,0 +1,17 @@ +package bitcaspy + +import "errors" + +var ( + ErrLocked = errors.New("a lockfile already exists") + ErrReadOnly = errors.New("operation not allowed in read only mode") + + ErrChecksumMismatch = errors.New("invalid data: checksum does not match") + + ErrEmptyKey = errors.New("invalid key: key cannot be empty") + ErrExpiredKey = errors.New("invalid key: key is already expired") + ErrLargeKey = errors.New("invalid key: size cannot be more than 4294967296 bytes") + ErrNoKey = errors.New("invalid key: key is either deleted or expired or unset") + + ErrLargeValue = errors.New("invalid value: size cannot be more than 4294967296 bytes") +) diff --git a/ops.go b/ops.go index d18efc2..c39700b 100644 --- a/ops.go +++ b/ops.go @@ -12,7 +12,7 @@ import ( func (b *BitCaspy) get(key string) (Record, error) { meta, ok := b.KeyDir[key] if !ok { - return Record{}, nil + return Record{}, ErrNoKey } var ( @@ -20,6 +20,7 @@ func (b *BitCaspy) get(key string) (Record, error) { reader *datafile.DataFile ) reader = b.df + // Isnot in Active data file then go to stale data files if meta.id != b.df.ID() { reader, ok = b.stale[meta.id] if !ok { @@ -29,7 +30,7 @@ func (b *BitCaspy) get(key string) (Record, error) { data, err := reader.Read(meta.value_pos, meta.value_sz) if err != nil { - return Record{}, fmt.Errorf("Error reading the dat from database file %v", err) + return Record{}, fmt.Errorf("Error reading the data from database file %v", err) } //Decode the header