packages feed

hnix-store-readonly-0.1.0.0: src/System/Nix/Store/ReadOnly.hs

{-# 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 storeDir ty h nm =
 System.Nix.StorePath.unsafeMakeStorePath storeHash nm
 where
  storeHash = System.Nix.StorePath.mkStorePathHashPart @hashAlgo s
  s =
    Data.ByteString.intercalate ":" $
      ty:fmap Data.Text.Encoding.encodeUtf8
        [ algoName @hashAlgo
        , System.Nix.Hash.encodeDigestWith Base16 h
        , Data.Text.pack . Data.ByteString.Char8.unpack $ System.Nix.StorePath.unStoreDir storeDir
        , System.Nix.StorePath.unStorePathName nm
        ]

makeTextPath
  :: StoreDir
  -> StorePathName
  -> Digest SHA256
  -> HashSet StorePath
  -> StorePath
makeTextPath storeDir nm h refs = makeStorePath storeDir ty h nm
 where
  ty =
    Data.ByteString.intercalate
      ":"
      $ "text"
      : Data.List.sort
          (System.Nix.StorePath.storePathToRawFilePath storeDir
           <$> Data.HashSet.toList refs)

makeFixedOutputPath
  :: forall hashAlgo
  .  NamedAlgo hashAlgo
  => StoreDir
  -> FileIngestionMethod
  -> Digest hashAlgo
  -> StorePathName
  -> StorePath
makeFixedOutputPath storeDir recursive h =
  if recursive == FileIngestionMethod_FileRecursive
     && (algoName @hashAlgo) == "sha256"
  then makeStorePath storeDir "source" h
  else makeStorePath storeDir "output:out" h'
 where
  h' =
    Crypto.Hash.hash @ByteString @SHA256
      $  "fixed:out:"
      <> Data.Text.Encoding.encodeUtf8 (algoName @hashAlgo)
      <> (if recursive == FileIngestionMethod_FileRecursive then ":r:" else ":")
      <> Data.Text.Encoding.encodeUtf8 (System.Nix.Hash.encodeDigestWith Base16 h)
      <> ":"

computeStorePathForText
  :: StoreDir
  -> StorePathName
  -> ByteString
  -> (HashSet StorePath -> StorePath)
computeStorePathForText storeDir nm =
  makeTextPath storeDir nm
  . 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 name pth recursive _pathFilter _repair = do
  selectedHash <-
    if recursive == FileIngestionMethod_FileRecursive
      then recursiveContentHash
      else flatContentHash
  pure $ makeFixedOutputPath storeDir recursive selectedHash name
 where
  recursiveContentHash :: IO (Digest SHA256)
  recursiveContentHash =
    Crypto.Hash.hashFinalize
    <$> execStateT streamNarUpdate (Crypto.Hash.hashInit @SHA256)

  streamNarUpdate :: StateT (Context SHA256) IO ()
  streamNarUpdate =
    System.Nix.Nar.streamNarIO
      System.Nix.Nar.narEffectsIO
      pth
      (modify . flip (Crypto.Hash.hashUpdate @ByteString @SHA256))

  flatContentHash :: IO (Digest SHA256)
  flatContentHash =
    Crypto.Hash.hashlazy
    <$> System.Nix.Nar.narReadFile
          System.Nix.Nar.narEffectsIO
          pth