git-annex-10.20260624: Utility/Hash/Blake3.hs
{- Blake3 convenience wrapper with b3hash support.
-
- Copyright 2026 Joey Hess <id@joeyh.name>
- Copyright 2022 edef <edef@edef.eu>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
module Utility.Hash.Blake3 (
blake3,
blake3IncrementalVerifier,
blake3File,
) where
import Utility.Hash.Types
import Utility.Hash.Incremental
import Utility.FileSystemEncoding
import Utility.OsPath
import Utility.Path
import Utility.FileSize
import Utility.Process
import Utility.Exception
import Utility.Monad
import qualified BLAKE3
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Data.IORef
import Control.Concurrent.MVar
import GHC.IO (unsafePerformIO)
finalize :: BLAKE3.Hasher -> BLAKE3.Digest BLAKE3.DEFAULT_DIGEST_LEN
finalize = BLAKE3.finalize
blake3 :: L.ByteString -> Hash
blake3 = Hash . encodeBS . show . finalize . L.foldlChunks ((. pure) . BLAKE3.update) (BLAKE3.init Nothing)
blake3IncrementalVerifier :: String -> (Hash -> Bool) -> IO IncrementalVerifier
blake3IncrementalVerifier desc samechecksum = do
v <- newIORef (Just (BLAKE3.init Nothing, 0))
return $ IncrementalVerifier
{ updateIncrementalVerifier = \b ->
modifyIORef' v $ \case
(Just (ctx', n)) ->
let !ctx'' = BLAKE3.update ctx' [b]
!n' = n + fromIntegral (S.length b)
in (Just (ctx'', n'))
Nothing -> Nothing
, finalizeIncrementalVerifier =
fmap (samechecksum . Hash . encodeBS . show . finalize . fst) <$> readIORef v
, unableIncrementalVerifier = writeIORef v Nothing
, positionIncrementalVerifier = fmap snd <$> readIORef v
, descIncrementalVerifier = desc
}
{- When b3sum is in the path, this uses it to hash a file. For large
- files, that is much faster due to supporting parallelism.
-
- This returns Nothing when b3sum is not in the path, or exits nonzero,
- or when the file is too small to be worth running it.
-}
blake3File :: OsPath -> IO (Maybe Hash)
blake3File file = takeMVar b3sumAvailable >>= \case
Just True -> do
putMVar b3sumAvailable (Just True)
runb3sum
Just False -> do
putMVar b3sumAvailable (Just False)
return Nothing
Nothing -> ifM (inSearchPath "b3sum")
( do
putMVar b3sumAvailable (Just True)
runb3sum
, do
putMVar b3sumAvailable (Just False)
return Nothing
)
where
runb3sum = ifM filelargeenough
( tryNonAsync runb3sum' >>= return . \case
Right output -> case lines output of
(hash:[]) | length hash == 64 ->
Just $ Hash $ encodeBS hash
_ -> Nothing
Left _ -> Nothing
, return Nothing
)
runb3sum' = readProcess "b3sum" ["--no-names", "--", fromOsPath file]
-- A file needs to be about 3mb in size before the overhead of
-- starting a b3sum process is worthwhile.
filelargeenough = tryNonAsync (getFileSize file) >>= return . \case
Right sz -> sz > 3000000
Left _ -> False
{- Used to avoid needing to check the PATH for b3sum each time
- blake3File is called. -}
{-# NOINLINE b3sumAvailable #-}
b3sumAvailable :: MVar (Maybe Bool)
b3sumAvailable = unsafePerformIO $ newMVar Nothing