mirror of https://github.com/voidlizard/hbs2
196 lines
6.5 KiB
Haskell
196 lines
6.5 KiB
Haskell
{-# Language AllowAmbiguousTypes #-}
|
||
{-# Language UndecidableInstances #-}
|
||
module HBS2.Data.Bundle where
|
||
|
||
import HBS2.Prelude
|
||
import HBS2.Storage
|
||
import HBS2.Storage.Operations.ByteString
|
||
import HBS2.Hash
|
||
import HBS2.Data.Types.Refs
|
||
import HBS2.Data.Types.SignedBox
|
||
import HBS2.Net.Proto.Types
|
||
import HBS2.Net.Auth.Credentials
|
||
import HBS2.Data.Detect
|
||
|
||
import Data.Word
|
||
|
||
import Codec.Compression.GZip as GZip
|
||
import Codec.Serialise
|
||
import Control.Monad
|
||
import Data.ByteString.Lazy.Char8 (ByteString)
|
||
import Data.ByteString.Lazy.Char8 qualified as LBS
|
||
import Data.Either
|
||
|
||
import Streaming.Prelude qualified as S
|
||
import Streaming()
|
||
|
||
{- HLINT ignore "Use newtype instead of data" -}
|
||
|
||
data BundleRefValue s =
|
||
BundleRefValue (SignedBox BundleRef s)
|
||
deriving stock (Generic)
|
||
|
||
instance ForSignedBox e => Serialise (BundleRefValue e)
|
||
|
||
data BundleRef =
|
||
BundleRefSimple HashRef
|
||
deriving stock (Generic)
|
||
|
||
instance Serialise BundleRef
|
||
|
||
|
||
makeBundleRefValue :: forall s . (ForSignedBox s, Signatures s)
|
||
=> PubKey 'Sign s
|
||
-> PrivKey 'Sign s
|
||
-> BundleRef
|
||
-> BundleRefValue s
|
||
|
||
makeBundleRefValue pk sk ref = BundleRefValue $ makeSignedBox @s pk sk ref
|
||
|
||
-- у нас может быть много способов хранить данные:
|
||
-- сжимать целиком (эффективно, но медленно)
|
||
-- сжимать по секциям (быстрее, но менее эффективно)
|
||
data BundleHead =
|
||
BundleHeadSimple
|
||
{ bundleHeadSectionSize :: Word16
|
||
}
|
||
deriving stock (Generic,Show)
|
||
|
||
instance Serialise BundleHead
|
||
|
||
bundleHeadSize :: Integral a => a
|
||
bundleHeadSize = 64
|
||
|
||
data BundleSection =
|
||
BundleSection
|
||
{ _bundleSectionSize :: Word32
|
||
, _bundleSectionHash :: Maybe HashRef
|
||
}
|
||
deriving stock (Generic,Show)
|
||
|
||
instance Serialise BundleSection
|
||
|
||
sectionHeadSize :: Integral a => a
|
||
sectionHeadSize = 64
|
||
|
||
-- TODO: implement-create-bundle
|
||
-- создаёт bundle в том же самом хранилище,
|
||
-- допустим.
|
||
-- Возвращает HashRef
|
||
-- Если нам нужен сам бандл - всегда сможем
|
||
-- сделать hbs2 cat.
|
||
-- t HashRef -- это что-то, что даёт нам список хэшей?
|
||
-- для каждого хэша надо бы уметь считать
|
||
-- его данные, и куда-то их записать в лог.
|
||
-- лог может быть очень большим. как его
|
||
-- писать-то? допустим, через tmp файл -- тогда
|
||
-- довольно прямолинейно, но функция требует
|
||
-- файловой системы.
|
||
-- можно поблочно, но это прямо довольно-таки сложно
|
||
-- и неатомарно. Как же сделать?
|
||
-- непрямолинейное решение:
|
||
-- пишем в буфер (какой?)
|
||
-- как понаписали достат. кол - то что? меркл
|
||
-- дерево у нас создаётся через toPtree
|
||
|
||
createBundle :: ( MonadIO m
|
||
, Storage sto HbSync ByteString IO
|
||
)
|
||
=> sto
|
||
-> [HashRef]
|
||
-> m (Maybe HashRef)
|
||
|
||
createBundle sto refs = do
|
||
let readBlock = liftIO . getBlock sto
|
||
|
||
-- FIXME: handle-errors-on-missed-blocks
|
||
blocks <- S.toList_ $ forM_ refs $ \hr -> do
|
||
deepScan ScanDeep (const none) (fromHashRef hr) readBlock $ \ha -> do
|
||
blk' <- readBlock ha
|
||
let href = HashRef ha
|
||
maybe1 blk' none $ \blk -> do
|
||
let compressed = compressWith params blk
|
||
let size = LBS.length compressed
|
||
let section = BundleSection (fromIntegral size) (Just href)
|
||
|
||
let sbs = serialise section
|
||
let pad = sectionHeadSize - LBS.length sbs
|
||
let pads = LBS.replicate pad '\x0'
|
||
S.yield (sbs <> pads <> compressed)
|
||
|
||
let buHead = serialise (BundleHeadSimple sectionHeadSize)
|
||
let buPadded = buHead <> LBS.replicate (bundleHeadSize - LBS.length buHead) '\x0'
|
||
|
||
let blob = buPadded <> mconcat blocks
|
||
|
||
-- FIXME: streamed-write-as-merkle
|
||
wtf <- liftIO $ writeAsMerkle sto blob
|
||
pure $ Just (HashRef wtf)
|
||
|
||
where
|
||
params = defaultCompressParams { compressLevel = bestSpeed }
|
||
|
||
data ImportError =
|
||
ImportBlocksMissed [HashRef]
|
||
| ImportBadJournal
|
||
deriving stock (Eq,Show)
|
||
|
||
missedBlocks :: ImportError -> [HashRef]
|
||
missedBlocks = \case
|
||
ImportBlocksMissed blk -> blk
|
||
_ -> mempty
|
||
|
||
class MonadIO m => ImportBundle bundle m where
|
||
importBundle :: forall sto . Storage sto HbSync ByteString m
|
||
=> sto
|
||
-> ( (Maybe HashRef, ByteString) -> m () )
|
||
-> bundle
|
||
-> m (Either ImportError ())
|
||
|
||
|
||
instance MonadIO m => ImportBundle HashRef m where
|
||
-- можем только целиком считать в память, потом пройтись по секциям и записать объекты
|
||
-- по памяти капец, что может быть
|
||
importBundle sto action bundle = do
|
||
let h = fromHashRef bundle
|
||
|
||
res <- S.toList_ $
|
||
deepScan ScanDeep yieldMissed h (lift . getBlock sto) $ \ha -> do
|
||
-- FIXME: annoying-shit
|
||
when (ha /= h) do
|
||
lift (getBlock sto ha) >>= maybe (yieldMissed ha) (S.yield . Right)
|
||
|
||
let missed = lefts res
|
||
|
||
if not (null missed) then
|
||
pure $ Left $ ImportBlocksMissed $ foldMap missedBlocks missed
|
||
else do
|
||
let bss = mconcat (rights res)
|
||
let (hs, bs) = LBS.splitAt bundleHeadSize bss
|
||
-- FIXME: fix-possible-memory-exhausting
|
||
case deserialiseOrFail @BundleHead hs of
|
||
Left{} -> pure $ Left ImportBadJournal
|
||
Right hd -> go hd bs
|
||
|
||
where
|
||
yieldMissed = S.yield . Left . ImportBlocksMissed . pure . HashRef
|
||
|
||
go hd bs
|
||
| LBS.null bs = pure $ Right ()
|
||
| otherwise = do
|
||
let ss = bundleHeadSectionSize hd
|
||
let (bsh, allBsRest) = LBS.splitAt sectionHeadSize bs
|
||
case deserialiseOrFail @BundleSection bsh of
|
||
Left{} -> do
|
||
pure $ Left ImportBadJournal
|
||
|
||
Right header -> do
|
||
let sz = fromIntegral $ _bundleSectionSize header
|
||
let (blk, rest) = LBS.splitAt sz allBsRest
|
||
if LBS.length blk /= sz then do
|
||
pure $ Left ImportBadJournal
|
||
else do
|
||
action (_bundleSectionHash header, GZip.decompress blk)
|
||
go hd rest
|
||
|