{-# LANGUAGE CPP              #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf       #-}

-- |
-- Module      :  Streamly.External.Posix.DirStream
-- Copyright   :  © 2020 Julian Ospald
-- License     :  BSD3
--
-- Maintainer  :  Julian Ospald <hasufell@posteo.de>
-- Stability   :  experimental
-- Portability :  portable
--
-- This module provides high-level file streaming API,
-- working with directory streams (POSIX).
module Streamly.External.Posix.DirStream
  (
  -- * Directory listing
    unfoldDirContents
  , dirContentsStream
  , dirContents
  )
where

import           Control.Exception.Safe
import           Control.Monad.IO.Class         ( liftIO
                                                , MonadIO
                                                )
import           Data.Word8
import           Prelude                 hiding ( readFile )
import           System.Posix.ByteString
import           System.Posix.Directory.ByteString
                                               as PosixBS
import           System.Posix.Foreign           ( DirType )
import           System.Posix.RawFilePath.Directory.Traversals
                                         hiding ( getDirectoryContents )
import qualified Data.ByteString               as BS
import qualified Streamly.Internal.Data.Stream.StreamD.Type
                                               as D
#if MIN_VERSION_streamly(0,7,1)
import qualified Streamly.Internal.Data.Unfold as SIU
#endif
#if MIN_VERSION_streamly(0,8,0)
import           Streamly.Prelude
import           Streamly.Internal.Data.Unfold.Type
#else
import           Streamly
import           Streamly.Internal.Data.Unfold.Types
import qualified Streamly.Internal.Prelude     as S
#endif


-- | Create an 'Unfold' of directory contents.
unfoldDirContents :: MonadIO m => Unfold m DirStream (DirType, RawFilePath)
unfoldDirContents :: Unfold m DirStream (DirType, RawFilePath)
unfoldDirContents = (DirStream -> m (Step DirStream (DirType, RawFilePath)))
-> (DirStream -> m DirStream)
-> Unfold m DirStream (DirType, RawFilePath)
forall (m :: * -> *) a b s.
(s -> m (Step s b)) -> (a -> m s) -> Unfold m a b
Unfold DirStream -> m (Step DirStream (DirType, RawFilePath))
forall (m :: * -> *).
MonadIO m =>
DirStream -> m (Step DirStream (DirType, RawFilePath))
step DirStream -> m DirStream
forall (m :: * -> *) a. Monad m => a -> m a
return
 where
  {-# INLINE [0] step #-}
  step :: DirStream -> m (Step DirStream (DirType, RawFilePath))
step DirStream
dirstream = do
    (DirType
typ, RawFilePath
e) <- IO (DirType, RawFilePath) -> m (DirType, RawFilePath)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (DirType, RawFilePath) -> m (DirType, RawFilePath))
-> IO (DirType, RawFilePath) -> m (DirType, RawFilePath)
forall a b. (a -> b) -> a -> b
$ DirStream -> IO (DirType, RawFilePath)
readDirEnt DirStream
dirstream
    Step DirStream (DirType, RawFilePath)
-> m (Step DirStream (DirType, RawFilePath))
forall (m :: * -> *) a. Monad m => a -> m a
return (Step DirStream (DirType, RawFilePath)
 -> m (Step DirStream (DirType, RawFilePath)))
-> Step DirStream (DirType, RawFilePath)
-> m (Step DirStream (DirType, RawFilePath))
forall a b. (a -> b) -> a -> b
$ if
      | RawFilePath -> Bool
BS.null RawFilePath
e                       -> Step DirStream (DirType, RawFilePath)
forall s a. Step s a
D.Stop
      | [Word8] -> RawFilePath
BS.pack [Word8
_period] RawFilePath -> RawFilePath -> Bool
forall a. Eq a => a -> a -> Bool
== RawFilePath
e          -> DirStream -> Step DirStream (DirType, RawFilePath)
forall s a. s -> Step s a
D.Skip DirStream
dirstream
      | [Word8] -> RawFilePath
BS.pack [Word8
_period, Word8
_period] RawFilePath -> RawFilePath -> Bool
forall a. Eq a => a -> a -> Bool
== RawFilePath
e -> DirStream -> Step DirStream (DirType, RawFilePath)
forall s a. s -> Step s a
D.Skip DirStream
dirstream
      | Bool
otherwise                       -> (DirType, RawFilePath)
-> DirStream -> Step DirStream (DirType, RawFilePath)
forall s a. a -> s -> Step s a
D.Yield (DirType
typ, RawFilePath
e) DirStream
dirstream


-- | Read the directory contents as a stream.
--
-- The DirStream is closed automatically, when the streamly stream exits
-- normally, aborts or gets garbage collected.
-- The stream must not be used after the dirstream is closed.
dirContentsStream :: (MonadCatch m, MonadAsync m, MonadMask m)
                  => DirStream
                  -> SerialT m (DirType, RawFilePath)
dirContentsStream :: DirStream -> SerialT m (DirType, RawFilePath)
dirContentsStream DirStream
ds =
#if MIN_VERSION_streamly(0,8,0)
  Unfold m DirStream (DirType, RawFilePath)
-> DirStream -> SerialT m (DirType, RawFilePath)
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a b.
(IsStream t, Monad m) =>
Unfold m a b -> a -> t m b
unfold ((DirStream -> m ())
-> Unfold m DirStream (DirType, RawFilePath)
-> Unfold m DirStream (DirType, RawFilePath)
forall (m :: * -> *) a c b.
(MonadAsync m, MonadCatch m) =>
(a -> m c) -> Unfold m a b -> Unfold m a b
SIU.finally (IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> (DirStream -> IO ()) -> DirStream -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DirStream -> IO ()
PosixBS.closeDirStream) Unfold m DirStream (DirType, RawFilePath)
forall (m :: * -> *).
MonadIO m =>
Unfold m DirStream (DirType, RawFilePath)
unfoldDirContents) (DirStream -> SerialT m (DirType, RawFilePath))
-> DirStream -> SerialT m (DirType, RawFilePath)
forall a b. (a -> b) -> a -> b
$ DirStream
ds
#else
#if MIN_VERSION_streamly(0,7,1)
  S.unfold (SIU.finallyIO (liftIO . PosixBS.closeDirStream) unfoldDirContents) $ ds
#else
  S.finally (liftIO . PosixBS.closeDirStream $ ds) . S.unfold unfoldDirContents $ ds
#endif
#endif


-- | Read the directory contents strictly as a list.
--
-- The DirStream is closed automatically.
dirContents :: (MonadCatch m, MonadAsync m, MonadMask m)
            => DirStream
            -> m [(DirType, RawFilePath)]
#if MIN_VERSION_streamly(0,8,0)
dirContents :: DirStream -> m [(DirType, RawFilePath)]
dirContents = SerialT m (DirType, RawFilePath) -> m [(DirType, RawFilePath)]
forall (m :: * -> *) a. Monad m => SerialT m a -> m [a]
toList (SerialT m (DirType, RawFilePath) -> m [(DirType, RawFilePath)])
-> (DirStream -> SerialT m (DirType, RawFilePath))
-> DirStream
-> m [(DirType, RawFilePath)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DirStream -> SerialT m (DirType, RawFilePath)
forall (m :: * -> *).
(MonadCatch m, MonadAsync m, MonadMask m) =>
DirStream -> SerialT m (DirType, RawFilePath)
dirContentsStream
#else
dirContents = S.toList . dirContentsStream
#endif