{-# language DataKinds #-}
{-# language ScopedTypeVariables #-}

module System.Nix.ReadonlyStore where


import qualified Data.ByteString               as BS
import qualified Data.HashSet                  as HS
import           System.Nix.Hash
import           System.Nix.Nar
import           System.Nix.StorePath
import           Crypto.Hash                    ( Context
                                                , Digest
                                                , hash
                                                , hashlazy
                                                , hashInit
                                                , hashUpdate
                                                , hashFinalize
                                                , SHA256
                                                )


makeStorePath
  :: forall h
   . (NamedAlgo h)
  => FilePath
  -> ByteString
  -> Digest h
  -> StorePathName
  -> StorePath
makeStorePath :: FilePath -> ByteString -> Digest h -> StorePathName -> StorePath
makeStorePath FilePath
fp ByteString
ty Digest h
h StorePathName
nm = StorePathHashPart -> StorePathName -> FilePath -> StorePath
StorePath (ByteString -> StorePathHashPart
coerce ByteString
storeHash) StorePathName
nm FilePath
fp
 where
  storeHash :: ByteString
storeHash = ByteString -> ByteString
forall a. HashAlgorithm a => ByteString -> ByteString
mkStorePathHash @h ByteString
s

  s :: ByteString
s =
    ByteString -> [ByteString] -> ByteString
BS.intercalate ByteString
":" ([ByteString] -> ByteString) -> [ByteString] -> ByteString
forall a b. (a -> b) -> a -> b
$
      ByteString
tyByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:(Text -> ByteString) -> [Text] -> [ByteString]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8
        [ NamedAlgo h => Text
forall a. NamedAlgo a => Text
algoName @h
        , BaseEncoding -> Digest h -> Text
forall a. BaseEncoding -> Digest a -> Text
encodeDigestWith BaseEncoding
Base16 Digest h
h
        , FilePath -> Text
forall a. ToText a => a -> Text
toText FilePath
fp
        , StorePathName -> Text
coerce StorePathName
nm
        ]

makeTextPath
  :: FilePath -> StorePathName -> Digest SHA256 -> StorePathSet -> StorePath
makeTextPath :: FilePath
-> StorePathName -> Digest SHA256 -> StorePathSet -> StorePath
makeTextPath FilePath
fp StorePathName
nm Digest SHA256
h StorePathSet
refs = FilePath
-> ByteString -> Digest SHA256 -> StorePathName -> StorePath
forall h.
NamedAlgo h =>
FilePath -> ByteString -> Digest h -> StorePathName -> StorePath
makeStorePath FilePath
fp ByteString
ty Digest SHA256
h StorePathName
nm
 where
  ty :: ByteString
ty =
    ByteString -> [ByteString] -> ByteString
BS.intercalate ByteString
":" ([ByteString] -> ByteString) -> [ByteString] -> ByteString
forall a b. (a -> b) -> a -> b
$ ByteString
"text" ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
: [ByteString] -> [ByteString]
forall a. Ord a => [a] -> [a]
sort (StorePath -> ByteString
storePathToRawFilePath (StorePath -> ByteString) -> [StorePath] -> [ByteString]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> StorePathSet -> [StorePath]
forall a. HashSet a -> [a]
HS.toList StorePathSet
refs)

makeFixedOutputPath
  :: forall hashAlgo
  .  NamedAlgo hashAlgo
  => FilePath
  -> Bool
  -> Digest hashAlgo
  -> StorePathName
  -> StorePath
makeFixedOutputPath :: FilePath -> Bool -> Digest hashAlgo -> StorePathName -> StorePath
makeFixedOutputPath FilePath
fp Bool
recursive Digest hashAlgo
h =
  if Bool
recursive Bool -> Bool -> Bool
&& (NamedAlgo hashAlgo => Text
forall a. NamedAlgo a => Text
algoName @hashAlgo) Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"sha256"
    then FilePath
-> ByteString -> Digest hashAlgo -> StorePathName -> StorePath
forall h.
NamedAlgo h =>
FilePath -> ByteString -> Digest h -> StorePathName -> StorePath
makeStorePath FilePath
fp ByteString
"source" Digest hashAlgo
h
    else FilePath
-> ByteString -> Digest SHA256 -> StorePathName -> StorePath
forall h.
NamedAlgo h =>
FilePath -> ByteString -> Digest h -> StorePathName -> StorePath
makeStorePath FilePath
fp ByteString
"output:out" Digest SHA256
h'
 where
  h' :: Digest SHA256
h' =
    (ByteArrayAccess ByteString, HashAlgorithm SHA256) =>
ByteString -> Digest SHA256
forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
ba -> Digest a
hash @ByteString @SHA256
      (ByteString -> Digest SHA256) -> ByteString -> Digest SHA256
forall a b. (a -> b) -> a -> b
$  ByteString
"fixed:out:"
      ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 (NamedAlgo hashAlgo => Text
forall a. NamedAlgo a => Text
algoName @hashAlgo)
      ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> (if Bool
recursive then ByteString
":r:" else ByteString
":")
      ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 (BaseEncoding -> Digest hashAlgo -> Text
forall a. BaseEncoding -> Digest a -> Text
encodeDigestWith BaseEncoding
Base16 Digest hashAlgo
h)
      ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
":"

computeStorePathForText
  :: FilePath -> StorePathName -> ByteString -> (StorePathSet -> StorePath)
computeStorePathForText :: FilePath
-> StorePathName -> ByteString -> StorePathSet -> StorePath
computeStorePathForText FilePath
fp StorePathName
nm = FilePath
-> StorePathName -> Digest SHA256 -> StorePathSet -> StorePath
makeTextPath FilePath
fp StorePathName
nm (Digest SHA256 -> StorePathSet -> StorePath)
-> (ByteString -> Digest SHA256)
-> ByteString
-> StorePathSet
-> StorePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Digest SHA256
forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
ba -> Digest a
hash

computeStorePathForPath
  :: StorePathName        -- ^ Name part of the newly created `StorePath`
  -> FilePath             -- ^ Local `FilePath` to add
  -> Bool                 -- ^ Add target directory recursively
  -> (FilePath -> Bool)   -- ^ Path filter function
  -> Bool                 -- ^ Only used by local store backend
  -> IO StorePath
computeStorePathForPath :: StorePathName
-> FilePath -> Bool -> (FilePath -> Bool) -> Bool -> IO StorePath
computeStorePathForPath StorePathName
name FilePath
pth Bool
recursive FilePath -> Bool
_pathFilter Bool
_repair = do
  Digest SHA256
selectedHash <- if Bool
recursive then IO (Digest SHA256)
recursiveContentHash else IO (Digest SHA256)
flatContentHash
  StorePath -> IO StorePath
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StorePath -> IO StorePath) -> StorePath -> IO StorePath
forall a b. (a -> b) -> a -> b
$ FilePath -> Bool -> Digest SHA256 -> StorePathName -> StorePath
forall hashAlgo.
NamedAlgo hashAlgo =>
FilePath -> Bool -> Digest hashAlgo -> StorePathName -> StorePath
makeFixedOutputPath FilePath
"/nix/store" Bool
recursive Digest SHA256
selectedHash StorePathName
name
 where
  recursiveContentHash :: IO (Digest SHA256)
  recursiveContentHash :: IO (Digest SHA256)
recursiveContentHash = Context SHA256 -> Digest SHA256
forall a. HashAlgorithm a => Context a -> Digest a
hashFinalize (Context SHA256 -> Digest SHA256)
-> IO (Context SHA256) -> IO (Digest SHA256)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> StateT (Context SHA256) IO ()
-> Context SHA256 -> IO (Context SHA256)
forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m s
execStateT StateT (Context SHA256) IO ()
streamNarUpdate (HashAlgorithm SHA256 => Context SHA256
forall a. HashAlgorithm a => Context a
hashInit @SHA256)
  streamNarUpdate :: StateT (Context SHA256) IO ()
  streamNarUpdate :: StateT (Context SHA256) IO ()
streamNarUpdate = NarEffects IO -> FilePath -> NarSource (StateT (Context SHA256) IO)
forall (m :: * -> *).
MonadIO m =>
NarEffects IO -> FilePath -> NarSource m
streamNarIO NarEffects IO
forall (m :: * -> *).
(MonadIO m, MonadFail m, MonadBaseControl IO m) =>
NarEffects m
narEffectsIO FilePath
pth ((Context SHA256 -> Context SHA256) -> StateT (Context SHA256) IO ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Context SHA256 -> Context SHA256)
 -> StateT (Context SHA256) IO ())
-> (ByteString -> Context SHA256 -> Context SHA256)
-> ByteString
-> StateT (Context SHA256) IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Context SHA256 -> ByteString -> Context SHA256)
-> ByteString -> Context SHA256 -> Context SHA256
forall a b c. (a -> b -> c) -> b -> a -> c
flip ((ByteArrayAccess ByteString, HashAlgorithm SHA256) =>
Context SHA256 -> ByteString -> Context SHA256
forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
Context a -> ba -> Context a
hashUpdate @ByteString @SHA256))

  flatContentHash :: IO (Digest SHA256)
  flatContentHash :: IO (Digest SHA256)
flatContentHash = ByteString -> Digest SHA256
forall a. HashAlgorithm a => ByteString -> Digest a
hashlazy (ByteString -> Digest SHA256)
-> IO ByteString -> IO (Digest SHA256)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> NarEffects IO -> FilePath -> IO ByteString
forall (m :: * -> *). NarEffects m -> FilePath -> m ByteString
narReadFile NarEffects IO
forall (m :: * -> *).
(MonadIO m, MonadFail m, MonadBaseControl IO m) =>
NarEffects m
narEffectsIO FilePath
pth