{-# LANGUAGE OverloadedStrings #-}

module System.Nix.Store.ReadOnly
  ( makeStorePath
  , makeTextPath
  , makeFixedOutputPath
  , computeStorePathForText
  , computeStorePathForPath
  ) where

import Control.Monad.State (StateT, execStateT, modify)
import Crypto.Hash (Context, Digest, SHA256)
import Data.ByteString (ByteString)
import Data.HashSet (HashSet)
import System.Nix.Hash (BaseEncoding(Base16), NamedAlgo(algoName))
import System.Nix.Store.Types (FileIngestionMethod(..), PathFilter, RepairMode)
import System.Nix.StorePath (StoreDir, StorePath, StorePathName)

import qualified Crypto.Hash
import qualified Data.ByteString.Char8
import qualified Data.ByteString
import qualified Data.HashSet
import qualified Data.List
import qualified Data.Text
import qualified Data.Text.Encoding
import qualified System.Nix.Hash
import qualified System.Nix.Nar
import qualified System.Nix.StorePath

makeStorePath
  :: forall hashAlgo
   . (NamedAlgo hashAlgo)
  => StoreDir
  -> ByteString
  -> Digest hashAlgo
  -> StorePathName
  -> StorePath
makeStorePath :: forall hashAlgo.
NamedAlgo hashAlgo =>
StoreDir
-> ByteString -> Digest hashAlgo -> StorePathName -> StorePath
makeStorePath StoreDir
storeDir ByteString
ty Digest hashAlgo
h StorePathName
nm =
 StorePathHashPart -> StorePathName -> StorePath
System.Nix.StorePath.unsafeMakeStorePath StorePathHashPart
storeHash StorePathName
nm
 where
  storeHash :: StorePathHashPart
storeHash = forall hashAlgo.
HashAlgorithm hashAlgo =>
ByteString -> StorePathHashPart
System.Nix.StorePath.mkStorePathHashPart @hashAlgo ByteString
s
  s :: ByteString
s =
    ByteString -> [ByteString] -> ByteString
Data.ByteString.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 a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> ByteString
Data.Text.Encoding.encodeUtf8
        [ forall a. NamedAlgo a => Text
algoName @hashAlgo
        , BaseEncoding -> Digest hashAlgo -> Text
forall a. BaseEncoding -> Digest a -> Text
System.Nix.Hash.encodeDigestWith BaseEncoding
Base16 Digest hashAlgo
h
        , String -> Text
Data.Text.pack (String -> Text) -> (ByteString -> String) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> String
Data.ByteString.Char8.unpack (ByteString -> Text) -> ByteString -> Text
forall a b. (a -> b) -> a -> b
$ StoreDir -> ByteString
System.Nix.StorePath.unStoreDir StoreDir
storeDir
        , StorePathName -> Text
System.Nix.StorePath.unStorePathName StorePathName
nm
        ]

makeTextPath
  :: StoreDir
  -> StorePathName
  -> Digest SHA256
  -> HashSet StorePath
  -> StorePath
makeTextPath :: StoreDir
-> StorePathName -> Digest SHA256 -> HashSet StorePath -> StorePath
makeTextPath StoreDir
storeDir StorePathName
nm Digest SHA256
h HashSet StorePath
refs = StoreDir
-> ByteString -> Digest SHA256 -> StorePathName -> StorePath
forall hashAlgo.
NamedAlgo hashAlgo =>
StoreDir
-> ByteString -> Digest hashAlgo -> StorePathName -> StorePath
makeStorePath StoreDir
storeDir ByteString
ty Digest SHA256
h StorePathName
nm
 where
  ty :: ByteString
ty =
    ByteString -> [ByteString] -> ByteString
Data.ByteString.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]
Data.List.sort
          (StoreDir -> StorePath -> ByteString
System.Nix.StorePath.storePathToRawFilePath StoreDir
storeDir
           (StorePath -> ByteString) -> [StorePath] -> [ByteString]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> HashSet StorePath -> [StorePath]
forall a. HashSet a -> [a]
Data.HashSet.toList HashSet StorePath
refs)

makeFixedOutputPath
  :: forall hashAlgo
  .  NamedAlgo hashAlgo
  => StoreDir
  -> FileIngestionMethod
  -> Digest hashAlgo
  -> StorePathName
  -> StorePath
makeFixedOutputPath :: forall hashAlgo.
NamedAlgo hashAlgo =>
StoreDir
-> FileIngestionMethod
-> Digest hashAlgo
-> StorePathName
-> StorePath
makeFixedOutputPath StoreDir
storeDir FileIngestionMethod
recursive Digest hashAlgo
h =
  if FileIngestionMethod
recursive FileIngestionMethod -> FileIngestionMethod -> Bool
forall a. Eq a => a -> a -> Bool
== FileIngestionMethod
FileIngestionMethod_FileRecursive
     Bool -> Bool -> Bool
&& (forall a. NamedAlgo a => Text
algoName @hashAlgo) Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"sha256"
  then StoreDir
-> ByteString -> Digest hashAlgo -> StorePathName -> StorePath
forall hashAlgo.
NamedAlgo hashAlgo =>
StoreDir
-> ByteString -> Digest hashAlgo -> StorePathName -> StorePath
makeStorePath StoreDir
storeDir ByteString
"source" Digest hashAlgo
h
  else StoreDir
-> ByteString -> Digest SHA256 -> StorePathName -> StorePath
forall hashAlgo.
NamedAlgo hashAlgo =>
StoreDir
-> ByteString -> Digest hashAlgo -> StorePathName -> StorePath
makeStorePath StoreDir
storeDir ByteString
"output:out" Digest SHA256
h'
 where
  h' :: Digest SHA256
h' =
    forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
ba -> Digest a
Crypto.Hash.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
Data.Text.Encoding.encodeUtf8 (forall a. NamedAlgo a => Text
algoName @hashAlgo)
      ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> (if FileIngestionMethod
recursive FileIngestionMethod -> FileIngestionMethod -> Bool
forall a. Eq a => a -> a -> Bool
== FileIngestionMethod
FileIngestionMethod_FileRecursive then ByteString
":r:" else ByteString
":")
      ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Text -> ByteString
Data.Text.Encoding.encodeUtf8 (BaseEncoding -> Digest hashAlgo -> Text
forall a. BaseEncoding -> Digest a -> Text
System.Nix.Hash.encodeDigestWith BaseEncoding
Base16 Digest hashAlgo
h)
      ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
":"

computeStorePathForText
  :: StoreDir
  -> StorePathName
  -> ByteString
  -> (HashSet StorePath -> StorePath)
computeStorePathForText :: StoreDir
-> StorePathName -> ByteString -> HashSet StorePath -> StorePath
computeStorePathForText StoreDir
storeDir StorePathName
nm =
  StoreDir
-> StorePathName -> Digest SHA256 -> HashSet StorePath -> StorePath
makeTextPath StoreDir
storeDir StorePathName
nm
  (Digest SHA256 -> HashSet StorePath -> StorePath)
-> (ByteString -> Digest SHA256)
-> ByteString
-> HashSet StorePath
-> StorePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Digest SHA256
forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
ba -> Digest a
Crypto.Hash.hash

computeStorePathForPath
  :: StoreDir
  -> StorePathName        -- ^ Name part of the newly created `StorePath`
  -> FilePath             -- ^ Local `FilePath` to add
  -> FileIngestionMethod  -- ^ Add target directory recursively
  -> PathFilter           -- ^ Path filter function
  -> RepairMode           -- ^ Only used by local store backend
  -> IO StorePath
computeStorePathForPath :: StoreDir
-> StorePathName
-> String
-> FileIngestionMethod
-> PathFilter
-> RepairMode
-> IO StorePath
computeStorePathForPath StoreDir
storeDir StorePathName
name String
pth FileIngestionMethod
recursive PathFilter
_pathFilter RepairMode
_repair = do
  Digest SHA256
selectedHash <-
    if FileIngestionMethod
recursive FileIngestionMethod -> FileIngestionMethod -> Bool
forall a. Eq a => a -> a -> Bool
== FileIngestionMethod
FileIngestionMethod_FileRecursive
      then IO (Digest SHA256)
recursiveContentHash
      else IO (Digest SHA256)
flatContentHash
  StorePath -> IO StorePath
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StorePath -> IO StorePath) -> StorePath -> IO StorePath
forall a b. (a -> b) -> a -> b
$ StoreDir
-> FileIngestionMethod
-> Digest SHA256
-> StorePathName
-> StorePath
forall hashAlgo.
NamedAlgo hashAlgo =>
StoreDir
-> FileIngestionMethod
-> Digest hashAlgo
-> StorePathName
-> StorePath
makeFixedOutputPath StoreDir
storeDir FileIngestionMethod
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
Crypto.Hash.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 (forall a. HashAlgorithm a => Context a
Crypto.Hash.hashInit @SHA256)

  streamNarUpdate :: StateT (Context SHA256) IO ()
  streamNarUpdate :: StateT (Context SHA256) IO ()
streamNarUpdate =
    NarEffects IO -> String -> NarSource (StateT (Context SHA256) IO)
forall (m :: * -> *).
MonadIO m =>
NarEffects IO -> String -> NarSource m
System.Nix.Nar.streamNarIO
      NarEffects IO
forall (m :: * -> *).
(MonadIO m, MonadFail m, MonadBaseControl IO m) =>
NarEffects m
System.Nix.Nar.narEffectsIO
      String
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 (forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
Context a -> ba -> Context a
Crypto.Hash.hashUpdate @ByteString @SHA256))

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