module Keydir (mapEntriesToKeydir, getValueFromKeydir, buildKeyDir, listKeysFromKeydir) where

import Caskfile (listCaskFiles, readEntries, readEntryFromPos)
import qualified Data.ByteString.Lazy as B
import qualified Data.Map as Map
import Entry (Entry (..), FieldSize, Key, Timestamp, Value, matchChecksum)
import System.FilePath ((</>))

data KeydirEntry = KeydirEntry FilePath FieldSize Int Timestamp
  deriving (Int -> KeydirEntry -> ShowS
[KeydirEntry] -> ShowS
KeydirEntry -> String
(Int -> KeydirEntry -> ShowS)
-> (KeydirEntry -> String)
-> ([KeydirEntry] -> ShowS)
-> Show KeydirEntry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> KeydirEntry -> ShowS
showsPrec :: Int -> KeydirEntry -> ShowS
$cshow :: KeydirEntry -> String
show :: KeydirEntry -> String
$cshowList :: [KeydirEntry] -> ShowS
showList :: [KeydirEntry] -> ShowS
Show, KeydirEntry -> KeydirEntry -> Bool
(KeydirEntry -> KeydirEntry -> Bool)
-> (KeydirEntry -> KeydirEntry -> Bool) -> Eq KeydirEntry
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: KeydirEntry -> KeydirEntry -> Bool
== :: KeydirEntry -> KeydirEntry -> Bool
$c/= :: KeydirEntry -> KeydirEntry -> Bool
/= :: KeydirEntry -> KeydirEntry -> Bool
Eq)

type Keydir = Map.Map Key KeydirEntry

mapEntriesToKeydir :: FilePath -> [(Int, Entry)] -> Keydir
mapEntriesToKeydir :: String -> [(Int, Entry)] -> Keydir
mapEntriesToKeydir String
_ [] = Keydir
forall k a. Map k a
Map.empty
mapEntriesToKeydir String
filepath ((Int
offset, Entry Checksum
_ Timestamp
timestamp Timestamp
ksize Timestamp
vsize Key
key Key
_) : [(Int, Entry)]
entries) =
  let ksize' :: Int
ksize' = Timestamp -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Timestamp
ksize
   in let entry :: KeydirEntry
entry = String -> Timestamp -> Int -> Timestamp -> KeydirEntry
KeydirEntry String
filepath Timestamp
vsize (Int
ksize' Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
offset) Timestamp
timestamp
       in Key -> KeydirEntry -> Keydir -> Keydir
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Key
key KeydirEntry
entry (Keydir -> Keydir) -> Keydir -> Keydir
forall a b. (a -> b) -> a -> b
$ String -> [(Int, Entry)] -> Keydir
mapEntriesToKeydir String
filepath [(Int, Entry)]
entries

getValueFromKeydir :: Keydir -> Key -> IO (Maybe Value)
getValueFromKeydir :: Keydir -> Key -> IO (Maybe Key)
getValueFromKeydir Keydir
keydir Key
key = do
  let keydir' :: Maybe KeydirEntry
keydir' = Key -> Keydir -> Maybe KeydirEntry
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Key
key Keydir
keydir
  case Maybe KeydirEntry
keydir' of
    Just (KeydirEntry String
filepath Timestamp
vsize Int
offset Timestamp
_) -> do
      entry :: Entry
entry@(Entry Checksum
_ Timestamp
_ Timestamp
_ Timestamp
_ Key
_ Key
value) <- String -> Timestamp -> Timestamp -> Timestamp -> IO Entry
readEntryFromPos String
filepath (Key -> Timestamp
B.length Key
key) Timestamp
vsize (Timestamp -> IO Entry) -> Timestamp -> IO Entry
forall a b. (a -> b) -> a -> b
$ Int -> Timestamp
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
offset
      Maybe Key -> IO (Maybe Key)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Key -> IO (Maybe Key)) -> Maybe Key -> IO (Maybe Key)
forall a b. (a -> b) -> a -> b
$ if Entry -> Bool
matchChecksum Entry
entry then Key -> Maybe Key
forall a. a -> Maybe a
Just Key
value else Maybe Key
forall a. Maybe a
Nothing
    Maybe KeydirEntry
Nothing -> Maybe Key -> IO (Maybe Key)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe Key
forall a. Maybe a
Nothing

compareByTimestamp :: KeydirEntry -> KeydirEntry -> KeydirEntry
compareByTimestamp :: KeydirEntry -> KeydirEntry -> KeydirEntry
compareByTimestamp left :: KeydirEntry
left@(KeydirEntry String
_ Timestamp
_ Int
_ Timestamp
timestamp) right :: KeydirEntry
right@(KeydirEntry String
_ Timestamp
_ Int
_ Timestamp
timestamp') =
  if Timestamp
timestamp Timestamp -> Timestamp -> Bool
forall a. Ord a => a -> a -> Bool
> Timestamp
timestamp' then KeydirEntry
left else KeydirEntry
right

buildKeyDir :: FilePath -> IO Keydir
buildKeyDir :: String -> IO Keydir
buildKeyDir String
dirpath = do
  [String]
caskfiles <- String -> IO [String]
listCaskFiles String
dirpath
  [Keydir]
keydirs <-
    (String -> IO Keydir) -> [String] -> IO [Keydir]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM
      ( \String
caskfile -> do
          let caskpath :: String
caskpath = String
dirpath String -> ShowS
</> String
caskfile
          [(Int, Entry)]
entries <- String -> IO [(Int, Entry)]
readEntries String
caskpath
          Keydir -> IO Keydir
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Keydir -> IO Keydir) -> Keydir -> IO Keydir
forall a b. (a -> b) -> a -> b
$ String -> [(Int, Entry)] -> Keydir
mapEntriesToKeydir String
caskpath [(Int, Entry)]
entries
      )
      [String]
caskfiles
  Keydir -> IO Keydir
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Keydir -> IO Keydir) -> Keydir -> IO Keydir
forall a b. (a -> b) -> a -> b
$ (KeydirEntry -> KeydirEntry -> KeydirEntry) -> [Keydir] -> Keydir
forall (f :: * -> *) k a.
(Foldable f, Ord k) =>
(a -> a -> a) -> f (Map k a) -> Map k a
Map.unionsWith KeydirEntry -> KeydirEntry -> KeydirEntry
compareByTimestamp [Keydir]
keydirs

listKeysFromKeydir :: Keydir -> [Key]
listKeysFromKeydir :: Keydir -> [Key]
listKeysFromKeydir = Keydir -> [Key]
forall k a. Map k a -> [k]
Map.keys