module Erebos.Storage.Internal ( Storage'(..), Storage, PartialStorage, RefDigest(..), WatchID, startWatchID, nextWatchID, WatchList(..), WatchListItem(..), watchListAdd, watchListDel, refDigestFromByteString, showRefDigest, showRefDigestParts, readRefDigest, hashToRefDigest, StorageCompleteness(..), StorageBackend(..), Complete, Partial, ioLoadBytesFromStorage, Generation(..), HeadID(..), HeadTypeID(..), ) where import Control.Arrow import Control.Concurrent import Control.DeepSeq import Control.Monad.Identity import Crypto.Hash import Data.Bits import Data.ByteArray (ByteArrayAccess, ScrubbedBytes) import Data.ByteArray qualified as BA import Data.ByteString (ByteString) import Data.ByteString.Char8 qualified as BC import Data.ByteString.Lazy qualified as BL import Data.HashTable.IO qualified as HT import Data.Hashable import Data.Kind import Data.Typeable import Foreign.Storable (peek) import System.IO.Unsafe (unsafePerformIO) import Erebos.UUID (UUID) import Erebos.Util data Storage' c = forall bck. (StorageBackend bck, BackendCompleteness bck ~ c) => Storage { stBackend :: bck , stRefGeneration :: MVar (HT.BasicHashTable RefDigest Generation) , stRefRoots :: MVar (HT.BasicHashTable RefDigest [RefDigest]) } type Storage = Storage' Complete type PartialStorage = Storage' Partial instance Eq (Storage' c) where Storage { stBackend = b } == Storage { stBackend = b' } | Just b'' <- cast b' = b == b'' | otherwise = False instance Show (Storage' c) where show Storage { stBackend = b } = show b ++ showParentStorage b showParentStorage :: StorageBackend bck => bck -> String showParentStorage bck | Just (st :: Storage) <- cast (backendParent bck) = "@" ++ show st | Just (st :: PartialStorage) <- cast (backendParent bck) = "@" ++ show st | otherwise = "" class (Eq bck, Show bck, Typeable bck, Typeable (BackendParent bck)) => StorageBackend bck where type BackendCompleteness bck :: Type -> Type type BackendCompleteness bck = Complete type BackendParent bck :: Type type BackendParent bck = () backendParent :: bck -> BackendParent bck default backendParent :: BackendParent bck ~ () => bck -> BackendParent bck backendParent _ = () backendLoadBytes :: bck -> RefDigest -> IO (Maybe BL.ByteString) default backendLoadBytes :: BackendParent bck ~ Storage => bck -> RefDigest -> IO (Maybe BL.ByteString) backendLoadBytes bck = case backendParent bck of Storage { stBackend = bck' } -> backendLoadBytes bck' backendStoreBytes :: bck -> RefDigest -> BL.ByteString -> IO () default backendStoreBytes :: BackendParent bck ~ Storage => bck -> RefDigest -> BL.ByteString -> IO () backendStoreBytes bck = case backendParent bck of Storage { stBackend = bck' } -> backendStoreBytes bck' backendLoadHeads :: bck -> HeadTypeID -> IO [ ( HeadID, RefDigest ) ] default backendLoadHeads :: BackendParent bck ~ Storage => bck -> HeadTypeID -> IO [ ( HeadID, RefDigest ) ] backendLoadHeads bck = case backendParent bck of Storage { stBackend = bck' } -> backendLoadHeads bck' backendLoadHead :: bck -> HeadTypeID -> HeadID -> IO (Maybe RefDigest) default backendLoadHead :: BackendParent bck ~ Storage => bck -> HeadTypeID -> HeadID -> IO (Maybe RefDigest) backendLoadHead bck = case backendParent bck of Storage { stBackend = bck' } -> backendLoadHead bck' backendStoreHead :: bck -> HeadTypeID -> HeadID -> RefDigest -> IO () default backendStoreHead :: BackendParent bck ~ Storage => bck -> HeadTypeID -> HeadID -> RefDigest -> IO () backendStoreHead bck = case backendParent bck of Storage { stBackend = bck' } -> backendStoreHead bck' backendReplaceHead :: bck -> HeadTypeID -> HeadID -> RefDigest -> RefDigest -> IO (Either (Maybe RefDigest) RefDigest) default backendReplaceHead :: BackendParent bck ~ Storage => bck -> HeadTypeID -> HeadID -> RefDigest -> RefDigest -> IO (Either (Maybe RefDigest) RefDigest) backendReplaceHead bck = case backendParent bck of Storage { stBackend = bck' } -> backendReplaceHead bck' backendWatchHead :: bck -> HeadTypeID -> HeadID -> (RefDigest -> IO ()) -> IO WatchID default backendWatchHead :: BackendParent bck ~ Storage => bck -> HeadTypeID -> HeadID -> (RefDigest -> IO ()) -> IO WatchID backendWatchHead bck = case backendParent bck of Storage { stBackend = bck' } -> backendWatchHead bck' backendUnwatchHead :: bck -> WatchID -> IO () default backendUnwatchHead :: BackendParent bck ~ Storage => bck -> WatchID -> IO () backendUnwatchHead bck = case backendParent bck of Storage { stBackend = bck' } -> backendUnwatchHead bck' backendListKeys :: bck -> IO [ RefDigest ] default backendListKeys :: BackendParent bck ~ Storage => bck -> IO [ RefDigest ] backendListKeys bck = case backendParent bck of Storage { stBackend = bck' } -> backendListKeys bck' backendLoadKey :: bck -> RefDigest -> IO (Maybe ScrubbedBytes) default backendLoadKey :: BackendParent bck ~ Storage => bck -> RefDigest -> IO (Maybe ScrubbedBytes) backendLoadKey bck = case backendParent bck of Storage { stBackend = bck' } -> backendLoadKey bck' backendStoreKey :: bck -> RefDigest -> ScrubbedBytes -> IO () default backendStoreKey :: BackendParent bck ~ Storage => bck -> RefDigest -> ScrubbedBytes -> IO () backendStoreKey bck = case backendParent bck of Storage { stBackend = bck' } -> backendStoreKey bck' backendRemoveKey :: bck -> RefDigest -> IO () default backendRemoveKey :: BackendParent bck ~ Storage => bck -> RefDigest -> IO () backendRemoveKey bck = case backendParent bck of Storage { stBackend = bck' } -> backendRemoveKey bck' newtype WatchID = WatchID Int deriving (Eq, Ord) startWatchID :: WatchID startWatchID = WatchID 1 nextWatchID :: WatchID -> WatchID nextWatchID (WatchID n) = WatchID (n + 1) data WatchList = WatchList { wlNext :: WatchID , wlList :: [ WatchListItem ] } data WatchListItem = WatchListItem { wlID :: WatchID , wlHead :: ( HeadTypeID, HeadID ) , wlFun :: RefDigest -> IO () } watchListAdd :: HeadTypeID -> HeadID -> (RefDigest -> IO ()) -> WatchList -> ( WatchList, WatchID ) watchListAdd tid hid cb wl = ( wl', wlNext wl ) where wl' = wl { wlNext = nextWatchID (wlNext wl) , wlList = WatchListItem { wlID = wlNext wl , wlHead = (tid, hid) , wlFun = cb } : wlList wl } watchListDel :: WatchID -> WatchList -> WatchList watchListDel wid wl = wl { wlList = filter ((/= wid) . wlID) $ wlList wl } newtype RefDigest = RefDigest (Digest Blake2b_256) deriving (Eq, Ord, NFData, ByteArrayAccess) instance Show RefDigest where show = BC.unpack . showRefDigest instance Hashable RefDigest where hashWithSalt salt ref = salt `xor` unsafePerformIO (BA.withByteArray ref peek) showRefDigestParts :: RefDigest -> (ByteString, ByteString) showRefDigestParts x = (BC.pack "blake2", showHex x) showRefDigest :: RefDigest -> ByteString showRefDigest = showRefDigestParts >>> \(alg, hex) -> alg <> BC.pack "#" <> hex readRefDigest :: ByteString -> Maybe RefDigest readRefDigest x = case BC.split '#' x of [alg, dgst] | BA.convert alg == BC.pack "blake2" -> refDigestFromByteString =<< readHex dgst _ -> Nothing refDigestFromByteString :: ByteString -> Maybe RefDigest refDigestFromByteString = fmap RefDigest . digestFromByteString hashToRefDigest :: BL.ByteString -> RefDigest hashToRefDigest = RefDigest . hashFinalize . hashUpdates hashInit . BL.toChunks newtype Generation = Generation Int deriving (Eq, Show) -- | UUID of individual Erebos storage head. newtype HeadID = HeadID UUID deriving (Eq, Ord, Show) -- | UUID of Erebos storage head type. newtype HeadTypeID = HeadTypeID UUID deriving (Eq, Ord) type Complete = Identity type Partial = Either RefDigest class (Traversable compl, Monad compl, Typeable compl) => StorageCompleteness compl where type LoadResult compl a :: Type returnLoadResult :: compl a -> LoadResult compl a unsafeLoadBytes :: Storage' compl -> RefDigest -> IO (compl BL.ByteString) instance StorageCompleteness Complete where type LoadResult Complete a = a returnLoadResult = runIdentity unsafeLoadBytes st dgst = maybe (error $ "Ref not found in complete storage: "++show dgst) Identity <$> ioLoadBytesFromStorage st dgst instance StorageCompleteness Partial where type LoadResult Partial a = Either RefDigest a returnLoadResult = id unsafeLoadBytes st dgst = maybe (Left dgst) Right <$> ioLoadBytesFromStorage st dgst ioLoadBytesFromStorage :: Storage' c -> RefDigest -> IO (Maybe BL.ByteString) ioLoadBytesFromStorage Storage {..} dgst = backendLoadBytes stBackend dgst >>= \case Just bytes -> return $ Just bytes Nothing | Just (parent :: Storage) <- cast (backendParent stBackend) -> ioLoadBytesFromStorage parent dgst | Just (parent :: PartialStorage) <- cast (backendParent stBackend) -> ioLoadBytesFromStorage parent dgst | otherwise -> return Nothing