packages feed

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

{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}

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

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

data NarEffects (m :: * -> *) = NarEffects {
    narReadFile   :: FilePath -> m BSL.ByteString
  , narWriteFile  :: FilePath -> BSL.ByteString -> m ()
  , narStreamFile :: FilePath -> m (Maybe BS.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 . BSL.readFile
  , narWriteFile  = \a b -> IO.liftIO $ BSL.writeFile a b
  , narStreamFile = streamStringOutIO
  , narListDir    = IO.liftIO . Directory.listDirectory
  , narCreateDir  = IO.liftIO . Directory.createDirectory
  , narCreateLink = \f t -> IO.liftIO $ createSymbolicLink f t
  , narGetPerms   = IO.liftIO . Directory.getPermissions
  , narSetPerms   = \f p -> IO.liftIO $ Directory.setPermissions f p
  , narIsDir      = \d -> fmap isDirectory $ IO.liftIO (getFileStatus d)
  , narIsSymLink  = IO.liftIO . Directory.pathIsSymbolicLink
  , narFileSize   = \n -> fmap (fromIntegral . fileSize) $ IO.liftIO (getFileStatus n)
  , 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 BS.ByteString)
  -> m ()
streamStringOutIO f getChunk =
  Lifted.bracket
    (IO.liftIO (IO.openFile f IO.WriteMode)) (IO.liftIO . IO.hClose) go
  `Lifted.catch`
    cleanupException
  where
    go :: IO.Handle -> m ()
    go handle = do
      chunk <- getChunk
      case chunk of
        Nothing -> return ()
        Just c  -> do
          IO.liftIO $ BS.hPut handle c
          go handle
    cleanupException (e :: Lifted.SomeException) = do
      IO.liftIO $ Directory.removeFile f
      MonadFail.fail $
        "Failed to stream string to " ++ f ++ ": " ++ show e