module Bitcask (open, put, get, delete, listKeys, merge, close) where
import Caskfile (getCurrentFileId, prependEntry, removePrevFiles)
import qualified Data.ByteString.Lazy as B
import Data.String.Interpolate (i)
import Entry (Entry (..), Key, Value, buildEntry, nanosSinceEpoch)
import Keydir (buildKeyDir, getValueFromKeydir, listKeysFromKeydir)
import System.FileLock (FileLock, SharedExclusive (..), tryLockFile, unlockFile)
import System.FilePath (dropFileName, (</>))
data Handle = Handle FilePath Bool FileLock
instance Show Handle where
show :: Handle -> String
show (Handle String
filepath Bool
isWriter FileLock
_) = [i|Handle - #{filepath} #{isWriter} lockfile|]
tombstone :: B.ByteString
tombstone :: ByteString
tombstone = ByteString
"__BITCASK_TOMBSTONE__"
open :: String -> Bool -> IO (Either String Handle)
open :: String -> Bool -> IO (Either String Handle)
open String
dirpath Bool
isWriter = do
Int
currentFileid <- String -> IO Int
getCurrentFileId String
dirpath
let filepath :: String
filepath = String
dirpath String -> ShowS
</> Int -> String
forall a. Show a => a -> String
show Int
currentFileid String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
".cask"
let lockpath :: String
lockpath = String
dirpath String -> ShowS
</> String
"cask.lock"
let locktype :: SharedExclusive
locktype = if Bool
isWriter then SharedExclusive
Exclusive else SharedExclusive
Shared
Maybe FileLock
flock <- String -> SharedExclusive -> IO (Maybe FileLock)
tryLockFile String
lockpath SharedExclusive
locktype
Either String Handle -> IO (Either String Handle)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String Handle -> IO (Either String Handle))
-> Either String Handle -> IO (Either String Handle)
forall a b. (a -> b) -> a -> b
$ case Maybe FileLock
flock of
Just FileLock
flock' -> Handle -> Either String Handle
forall a b. b -> Either a b
Right (Handle -> Either String Handle) -> Handle -> Either String Handle
forall a b. (a -> b) -> a -> b
$ String -> Bool -> FileLock -> Handle
Handle String
filepath Bool
isWriter FileLock
flock'
Maybe FileLock
Nothing -> String -> Either String Handle
forall a b. a -> Either a b
Left String
"Unable to lock file"
put :: Handle -> Key -> Value -> IO (Either String Entry)
put :: Handle -> ByteString -> ByteString -> IO (Either String Entry)
put (Handle String
filepath Bool
isWriter FileLock
_) ByteString
key ByteString
value = do
if Bool
isWriter
then do
Timestamp
timestamp <- IO Timestamp
nanosSinceEpoch
let entry :: Entry
entry = Timestamp -> ByteString -> ByteString -> Entry
buildEntry Timestamp
timestamp ByteString
key ByteString
value
Entry
entry' <- String -> Entry -> IO Entry
prependEntry String
filepath Entry
entry
Either String Entry -> IO (Either String Entry)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String Entry -> IO (Either String Entry))
-> Either String Entry -> IO (Either String Entry)
forall a b. (a -> b) -> a -> b
$ Entry -> Either String Entry
forall a b. b -> Either a b
Right Entry
entry'
else Either String Entry -> IO (Either String Entry)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String Entry -> IO (Either String Entry))
-> Either String Entry -> IO (Either String Entry)
forall a b. (a -> b) -> a -> b
$ String -> Either String Entry
forall a b. a -> Either a b
Left String
"Not a writer instance"
get :: Handle -> Key -> IO (Maybe Value)
get :: Handle -> ByteString -> IO (Maybe ByteString)
get (Handle String
filepath Bool
_ FileLock
_) ByteString
key = do
Keydir
keydir <- String -> IO Keydir
buildKeyDir (ShowS
dropFileName String
filepath)
Maybe ByteString
value <- Keydir -> ByteString -> IO (Maybe ByteString)
getValueFromKeydir Keydir
keydir ByteString
key
Maybe ByteString -> IO (Maybe ByteString)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe ByteString -> IO (Maybe ByteString))
-> Maybe ByteString -> IO (Maybe ByteString)
forall a b. (a -> b) -> a -> b
$ case Maybe ByteString
value of
Just ByteString
value' -> if ByteString
value' ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
tombstone then Maybe ByteString
forall a. Maybe a
Nothing else ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
value'
Maybe ByteString
Nothing -> Maybe ByteString
forall a. Maybe a
Nothing
delete :: Handle -> Key -> IO (Either String ())
delete :: Handle -> ByteString -> IO (Either String ())
delete Handle
handle ByteString
key = do
Either String Entry
e <- Handle -> ByteString -> ByteString -> IO (Either String Entry)
put Handle
handle ByteString
key ByteString
tombstone
Either String () -> IO (Either String ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String () -> IO (Either String ()))
-> Either String () -> IO (Either String ())
forall a b. (a -> b) -> a -> b
$ case Either String Entry
e of
Left String
err -> String -> Either String ()
forall a b. a -> Either a b
Left String
err
Either String Entry
_ -> () -> Either String ()
forall a b. b -> Either a b
Right ()
listKeys :: Handle -> IO [Key]
listKeys :: Handle -> IO [ByteString]
listKeys (Handle String
filepath Bool
_ FileLock
_) = do
Keydir
keydir <- String -> IO Keydir
buildKeyDir (ShowS
dropFileName String
filepath)
[ByteString] -> IO [ByteString]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([ByteString] -> IO [ByteString])
-> [ByteString] -> IO [ByteString]
forall a b. (a -> b) -> a -> b
$ Keydir -> [ByteString]
listKeysFromKeydir Keydir
keydir
merge :: Handle -> IO (Either String Handle)
merge :: Handle -> IO (Either String Handle)
merge (Handle String
filepath' Bool
True FileLock
filelock) = do
let dirpath :: String
dirpath = ShowS
dropFileName String
filepath'
Int
currentFileid <- String -> IO Int
getCurrentFileId String
dirpath
let filepath :: String
filepath = String
dirpath String -> ShowS
</> Int -> String
forall a. Show a => a -> String
show Int
currentFileid String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
".cask"
let handle' :: Handle
handle' = String -> Bool -> FileLock -> Handle
Handle String
filepath Bool
True FileLock
filelock
Keydir
keydir <- String -> IO Keydir
buildKeyDir (ShowS
dropFileName String
filepath)
let keys :: [ByteString]
keys = Keydir -> [ByteString]
listKeysFromKeydir Keydir
keydir
(ByteString -> IO (Either String Entry)) -> [ByteString] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_
( \ByteString
key -> do
Maybe ByteString
value <- Handle -> ByteString -> IO (Maybe ByteString)
get Handle
handle' ByteString
key
case Maybe ByteString
value of
Just ByteString
value' -> Handle -> ByteString -> ByteString -> IO (Either String Entry)
put Handle
handle' ByteString
key ByteString
value'
Maybe ByteString
Nothing -> Either String Entry -> IO (Either String Entry)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String Entry -> IO (Either String Entry))
-> Either String Entry -> IO (Either String Entry)
forall a b. (a -> b) -> a -> b
$ String -> Either String Entry
forall a b. a -> Either a b
Left String
"wasd"
)
[ByteString]
keys
String -> IO ()
removePrevFiles String
filepath
Either String Handle -> IO (Either String Handle)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String Handle -> IO (Either String Handle))
-> Either String Handle -> IO (Either String Handle)
forall a b. (a -> b) -> a -> b
$ Handle -> Either String Handle
forall a b. b -> Either a b
Right Handle
handle'
merge Handle
_ = do
Either String Handle -> IO (Either String Handle)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String Handle -> IO (Either String Handle))
-> Either String Handle -> IO (Either String Handle)
forall a b. (a -> b) -> a -> b
$ String -> Either String Handle
forall a b. a -> Either a b
Left String
"Unable to merge on this instance"
close :: Handle -> IO ()
close :: Handle -> IO ()
close (Handle String
_ Bool
_ FileLock
filelock) = FileLock -> IO ()
unlockFile FileLock
filelock