packages feed

hnix-store-core-0.6.0.0: src/System/Nix/Internal/Nar/Effects.hs

{-# language KindSignatures      #-}
{-# language RankNTypes          #-}
{-# language ScopedTypeVariables #-}

module System.Nix.Internal.Nar.Effects
  ( NarEffects(..)
  , narEffectsIO
  ) where

import qualified Data.ByteString             as Bytes
import qualified Data.ByteString.Lazy        as Bytes.Lazy
import qualified System.Directory            as Directory
import           System.Posix.Files          ( createSymbolicLink
                                        , fileSize
                                        , getFileStatus
                                        , isDirectory
                                        , readSymbolicLink
                                        )
import qualified System.IO                   as IO
import qualified Control.Monad.IO.Class      as IO
import           Control.Monad.Trans.Control (MonadBaseControl)
import qualified Control.Exception.Lifted    as Exception.Lifted
import qualified Control.Monad.Fail          as MonadFail

data NarEffects (m :: * -> *) = NarEffects {
    narReadFile   :: FilePath -> m Bytes.Lazy.ByteString
  , narWriteFile  :: FilePath -> Bytes.Lazy.ByteString -> m ()
  , narStreamFile :: FilePath -> m (Maybe Bytes.ByteString) -> m ()
  , narListDir    :: FilePath -> m [FilePath]
  , narCreateDir  :: FilePath -> m ()
  , narCreateLink :: FilePath -> FilePath -> m ()
  , narGetPerms   :: FilePath -> m Directory.Permissions
  , narSetPerms   :: FilePath -> Directory.Permissions ->  m ()
  , narIsDir      :: FilePath -> m Bool
  , narIsSymLink  :: FilePath -> m Bool
  , narFileSize   :: FilePath -> m Int64
  , narReadLink   :: FilePath -> m FilePath
  , narDeleteDir  :: FilePath -> m ()
  , narDeleteFile :: FilePath -> m ()
}


-- | A particular @NarEffects@ that uses regular POSIX for file manipulation
--   You would replace this with your own @NarEffects@ if you wanted a
--   different backend
narEffectsIO
  :: (IO.MonadIO m,
      MonadFail.MonadFail m,
      MonadBaseControl IO m
     ) => NarEffects m
narEffectsIO = NarEffects {
    narReadFile   = IO.liftIO . Bytes.Lazy.readFile
  , narWriteFile  = \a -> IO.liftIO . Bytes.Lazy.writeFile a
  , narStreamFile = streamStringOutIO
  , narListDir    = IO.liftIO . Directory.listDirectory
  , narCreateDir  = IO.liftIO . Directory.createDirectory
  , narCreateLink = \f -> IO.liftIO . createSymbolicLink f
  , narGetPerms   = IO.liftIO . Directory.getPermissions
  , narSetPerms   = \f -> IO.liftIO . Directory.setPermissions f
  , narIsDir      = fmap isDirectory . IO.liftIO . getFileStatus
  , narIsSymLink  = IO.liftIO . Directory.pathIsSymbolicLink
  , narFileSize   = fmap (fromIntegral . fileSize) . IO.liftIO . getFileStatus
  , narReadLink   = IO.liftIO . readSymbolicLink
  , narDeleteDir  = IO.liftIO . Directory.removeDirectoryRecursive
  , narDeleteFile = IO.liftIO . Directory.removeFile
  }


-- | This default implementation for @narStreamFile@ requires @IO.MonadIO@
streamStringOutIO
  :: forall m
  .(IO.MonadIO m,
    MonadFail.MonadFail m,
    MonadBaseControl IO m
  ) => FilePath
  -> m (Maybe Bytes.ByteString)
  -> m ()
streamStringOutIO f getChunk =
  Exception.Lifted.bracket
    (IO.liftIO $ IO.openFile f WriteMode)
    (IO.liftIO . IO.hClose)
    go
  `Exception.Lifted.catch`
    cleanupException
 where
  go :: IO.Handle -> m ()
  go handle = do
    chunk <- getChunk
    case chunk of
      Nothing -> pass
      Just c  -> do
        IO.liftIO $ Bytes.hPut handle c
        go handle
  cleanupException (e :: Exception.Lifted.SomeException) = do
    IO.liftIO $ Directory.removeFile f
    MonadFail.fail $
      "Failed to stream string to " <> f <> ": " <> show e