#include "inline.hs"

-- |
-- Module      : Streamly.Internal.FileSystem.DirIO
-- Copyright   : (c) 2018 Composewell Technologies
--
-- License     : BSD3
-- Maintainer  : streamly@composewell.com
-- Portability : GHC
--
--  API Design notes:
--
-- The paths returned by "read" can be absolute (/usr/bin/ls), relative to
-- current directory (./bin/ls) or path segments relative to current dir
-- (bin/ls). To accomodate all the cases we can provide a prefix to attach
-- to the paths being generated. Alternatively, we could take the approach
-- of the higher layer doing that, but it is more efficient to allocate the
-- path buffer once rather than modifying it later. We can do this by
-- passing a fold to transform the output.
--
-- Also it may be more efficient to apply a filter to the paths right here
-- instead of applying it in a layer above. Cut the output at the source
-- rather than generate and then discard it later. We can do this by
-- passing a fold to filter the input.
--
-- When reading a symlink directory we can resolve the symlink and read the
-- destination directory or we can just emit the file it is pointing to and
-- the read can happen next at the higher level, in the traversal logic
-- (concatIterate). Not sure if one approach has any significant perf impact
-- over the other. Similar thinking applies to a mount point as well. Also, if
-- we resolve the symlinks in concatIterate, then each resolution will be
-- counted as depth level increment whereas if we resolve that at lower level
-- then it won't. We can do this by passing an option to modify the behavior.
--
-- When resolving cyclic directory symlinks one way to curtail it is ELOOP
-- which gives up if it encounters too many level. Another way is to use
-- the inode information to check if we are traversing an already traversed
-- inode, this is in general helpful in a graph traversal. We can ignore
-- ELOOP by passing an option but it may be inefficient because we may
-- encounter the loop from any node in the cycle.
--
-- If we encounter an error reading a directory because of permission
-- issues should we ignore it in this low level API or catch it in the
-- higher level traversal functionality? Similarly, if there are broken
-- symlinks, where to handle the error? Need to check performance when
-- handling it in ListDir. Suppressing the error at the lower level may be
-- more efficient than propagating it up and then handling it there. We can
-- do this by passing an option.
--
-- Returning the metadata:
--
-- Specific scans can be used to return the metadata in the output stream if
-- needed. However, we may need three different APIs:
-- one with fast metadata, and
-- another with full metadata. In the two cases the fold input would be
-- different.
--
-- * readMinimal: read only the path names, no metadata
-- * readStandard: read the path and minimal metadata
-- * readFull: read full metadata
--
-- NOTE: Full metadata can be read by mapping a stat call to a stream of paths
-- rather than via readdir API. Does it help the performance to do it in the
-- readdir API?

-- Design pattern:
--
-- By passing a scan we can process the output right at the source and produce
-- a cooked output. Otherwise we may have to produce a stream of intermediate
-- structures which may have more per item overhead and that overhead may not
-- get eliminated by fusion. For example, a fold can directly write the CString
-- from readdir to the output buffer whereas if we output the Path then we will
-- incur an overhead of intermediate structure.

module Streamly.Internal.FileSystem.DirIO
    (
    -- XXX Create a Metadata or Meta module for stat, access, getxattr, chmod,
    -- chown, utime, rename operations.
    --
    -- * Metadata
    -- getMetadata GetMetadata (followSymlinks, noAutoMount - see fstatat)

    -- * Configuration
      module Streamly.Internal.FileSystem.DirOptions

    -- * Streams
    , read

    -- Is there a benefit in providing a low level recursive read or
    -- concatIterate is good enough? Could be more efficient for non-concurrent
    -- reads by using a local loop. Or during concurrent reads use
    -- non-concurrent reads as we go deeper down in the tree.
    -- , readAttrsRecursive

    , readFiles
    , readDirs
    , readEither
    , readEitherPaths
    , readEitherChunks

    -- We can implement this in terms of readAttrsRecursive without losing
    -- perf.
    -- , readEitherRecursive -- Options: acyclic, follow symlinks
    -- , readAncestors -- read the parent chain using the .. entry.
    -- , readAncestorsAttrs

    -- * Unfolds
    -- | Use the more convenient stream APIs instead of unfolds where possible.
    , reader
    , fileReader
    , dirReader
    , eitherReader
    , eitherReaderPaths

      {-
    , toStreamWithBufferOf

    , readChunks
    , readChunksWithBufferOf

    , toChunksWithBufferOf
    , toChunks

    , write
    , writeWithBufferOf

    -- Byte stream write (Streams)
    , fromStream
    , fromStreamWithBufferOf

    -- -- * Array Write
    , writeArray
    , writeChunks
    , writeChunksWithBufferOf

    -- -- * Array stream Write
    , fromChunks
    , fromChunksWithBufferOf
    -}
    )
where

import Control.Monad.Catch (MonadCatch)
import Control.Monad.IO.Class (MonadIO(..))
import Data.Bifunctor (bimap)
import Data.Either (isRight, isLeft, fromLeft, fromRight)
import Streamly.Data.Stream (Stream)
import Streamly.Internal.Data.Unfold.Type (Unfold(..))
import Streamly.Internal.FileSystem.Path (Path)
#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
import qualified Streamly.Internal.Data.Fold as Fold
import Streamly.Internal.FileSystem.Windows.ReadDir (eitherReader, reader)
#else
import Streamly.Internal.FileSystem.Posix.ReadDir
    ( readEitherChunks, eitherReader, reader)
#endif
import qualified Streamly.Internal.Data.Stream as S
import qualified Streamly.Data.Unfold as UF
import qualified Streamly.Internal.FileSystem.Path as Path

import Streamly.Internal.FileSystem.DirOptions
import Prelude hiding (read)

{-
{-# INLINABLE readArrayUpto #-}
readArrayUpto :: Int -> Handle -> IO (Array Word8)
readArrayUpto size h = do
    ptr <- mallocPlainForeignPtrBytes size
    -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
    withForeignPtr ptr $ \p -> do
        n <- hGetBufSome h p size
        let v = Array
                { aStart = ptr
                , arrEnd   = p `plusPtr` n
                , arrBound = p `plusPtr` size
                }
        -- XXX shrink only if the diff is significant
        shrinkToFit v

-------------------------------------------------------------------------------
-- Stream of Arrays IO
-------------------------------------------------------------------------------

-- | @toChunksWithBufferOf size h@ reads a stream of arrays from file handle @h@.
-- The maximum size of a single array is specified by @size@. The actual size
-- read may be less than or equal to @size@.
{-# INLINE _toChunksWithBufferOf #-}
_toChunksWithBufferOf :: MonadIO m => Int -> Handle -> Stream m (Array Word8)
_toChunksWithBufferOf size h = go
  where
    -- XXX use cons/nil instead
    go = mkStream $ \_ yld _ stp -> do
        arr <- liftIO $ readArrayUpto size h
        if A.length arr == 0
        then stp
        else yld arr go

-- | @toChunksWithBufferOf size handle@ reads a stream of arrays from the file
-- handle @handle@.  The maximum size of a single array is limited to @size@.
-- The actual size read may be less than or equal to @size@.
--
-- @since 0.7.0
{-# INLINE_NORMAL toChunksWithBufferOf #-}
toChunksWithBufferOf :: MonadIO m => Int -> Handle -> Stream m (Array Word8)
toChunksWithBufferOf size h = D.fromStreamD (D.Stream step ())
  where
    {-# INLINE_LATE step #-}
    step _ _ = do
        arr <- liftIO $ readArrayUpto size h
        return $
            case A.length arr of
                0 -> D.Stop
                _ -> D.Yield arr ()

-- | Unfold the tuple @(bufsize, handle)@ into a stream of 'Word8' arrays.
-- Read requests to the IO device are performed using a buffer of size
-- @bufsize@.  The size of an array in the resulting stream is always less than
-- or equal to @bufsize@.
--
-- @since 0.7.0
{-# INLINE_NORMAL readChunksWithBufferOf #-}
readChunksWithBufferOf :: MonadIO m => Unfold m (Int, Handle) (Array Word8)
readChunksWithBufferOf = Unfold step return
    where
    {-# INLINE_LATE step #-}
    step (size, h) = do
        arr <- liftIO $ readArrayUpto size h
        return $
            case A.length arr of
                0 -> D.Stop
                _ -> D.Yield arr (size, h)

-- XXX read 'Array a' instead of Word8

-- | @toChunks handle@ reads a stream of arrays from the specified file
-- handle.  The maximum size of a single array is limited to
-- @defaultChunkSize@. The actual size read may be less than or equal to
-- @defaultChunkSize@.
--
-- > toChunks = toChunksWithBufferOf defaultChunkSize
--
-- @since 0.7.0
{-# INLINE toChunks #-}
toChunks :: MonadIO m => Handle -> Stream m (Array Word8)
toChunks = toChunksWithBufferOf defaultChunkSize

-- | Unfolds a handle into a stream of 'Word8' arrays. Requests to the IO
-- device are performed using a buffer of size
-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'. The
-- size of arrays in the resulting stream are therefore less than or equal to
-- 'Streamly.Internal.Data.Array.Type.defaultChunkSize'.
--
-- @since 0.7.0
{-# INLINE readChunks #-}
readChunks :: MonadIO m => Unfold m Handle (Array Word8)
readChunks = UF.first readChunksWithBufferOf defaultChunkSize

-------------------------------------------------------------------------------
-- Read a Directory to Stream
-------------------------------------------------------------------------------

-- TODO for concurrent streams implement readahead IO. We can send multiple
-- read requests at the same time. For serial case we can use async IO. We can
-- also control the read throughput in mbps or IOPS.

-- | Unfolds the tuple @(bufsize, handle)@ into a byte stream, read requests
-- to the IO device are performed using buffers of @bufsize@.
--
-- @since 0.7.0
{-# INLINE readWithBufferOf #-}
readWithBufferOf :: MonadIO m => Unfold m (Int, Handle) Word8
readWithBufferOf = UF.many readChunksWithBufferOf A.read

-- | @toStreamWithBufferOf bufsize handle@ reads a byte stream from a file
-- handle, reads are performed in chunks of up to @bufsize@.
--
-- /Pre-release/
{-# INLINE toStreamWithBufferOf #-}
toStreamWithBufferOf :: MonadIO m => Int -> Handle -> Stream m Word8
toStreamWithBufferOf chunkSize h = AS.concat $ toChunksWithBufferOf chunkSize h
-}

-- read child node names from a dir filtering out . and ..
--
-- . and .. are an implementation artifact, and should probably not be used in
-- user level abstractions.
--
-- . does not seem to have any useful purpose. If we have the path of the dir
-- then we will resolve it to get the inode of the dir so the . entry would be
-- redundant. If we have the inode of the dir to read the dir then it is
-- redundant. Is this for cross check when doing fsck?
--
-- For .. we have the readAncestors API, we should not have this in the
-- readChildren API.

-- XXX exception handling

-- XXX We can use a more general mechanism to filter the contents of a
-- directory. We can just stat each child and pass on the stat information. We
-- can then use that info to do a general filtering. "find" like filters can be
-- created.

{-# INLINE eitherReaderPaths #-}
eitherReaderPaths ::(MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    Unfold m Path (Either Path Path)
eitherReaderPaths :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
eitherReaderPaths ReadOptions -> ReadOptions
f =
    let </> :: Path -> Path -> Path
(</>) = Path -> Path -> Path
Path.join
     in ((Path, Either Path Path) -> Either Path Path)
-> Unfold m Path (Path, Either Path Path)
-> Unfold m Path (Either Path Path)
forall a b. (a -> b) -> Unfold m Path a -> Unfold m Path b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(Path
dir, Either Path Path
x) -> (Path -> Path)
-> (Path -> Path) -> Either Path Path -> Either Path Path
forall a b c d. (a -> b) -> (c -> d) -> Either a c -> Either b d
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap (Path
dir Path -> Path -> Path
</>) (Path
dir Path -> Path -> Path
</>) Either Path Path
x)
            (Unfold m Path (Path, Either Path Path)
 -> Unfold m Path (Either Path Path))
-> Unfold m Path (Path, Either Path Path)
-> Unfold m Path (Either Path Path)
forall a b. (a -> b) -> a -> b
$ Unfold m Path (Either Path Path)
-> Unfold m Path (Path, Either Path Path)
forall (m :: * -> *) a b.
Functor m =>
Unfold m a b -> Unfold m a (a, b)
UF.carry ((ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
eitherReader ReadOptions -> ReadOptions
f)

--
-- | Read files only.
--
--  /Internal/
--
{-# INLINE fileReader #-}
fileReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    Unfold m Path Path
fileReader :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path Path
fileReader ReadOptions -> ReadOptions
f = (Either Path Path -> Path)
-> Unfold m Path (Either Path Path) -> Unfold m Path Path
forall a b. (a -> b) -> Unfold m Path a -> Unfold m Path b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Path -> Either Path Path -> Path
forall b a. b -> Either a b -> b
fromRight Path
forall a. HasCallStack => a
undefined) (Unfold m Path (Either Path Path) -> Unfold m Path Path)
-> Unfold m Path (Either Path Path) -> Unfold m Path Path
forall a b. (a -> b) -> a -> b
$ (Either Path Path -> Bool)
-> Unfold m Path (Either Path Path)
-> Unfold m Path (Either Path Path)
forall (m :: * -> *) b a.
Monad m =>
(b -> Bool) -> Unfold m a b -> Unfold m a b
UF.filter Either Path Path -> Bool
forall a b. Either a b -> Bool
isRight ((ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
eitherReader ReadOptions -> ReadOptions
f)

-- | Read directories only. Filter out "." and ".." entries.
--
--  /Internal/
--
{-# INLINE dirReader #-}
dirReader :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    Unfold m Path Path
dirReader :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path Path
dirReader ReadOptions -> ReadOptions
f = (Either Path Path -> Path)
-> Unfold m Path (Either Path Path) -> Unfold m Path Path
forall a b. (a -> b) -> Unfold m Path a -> Unfold m Path b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Path -> Either Path Path -> Path
forall a b. a -> Either a b -> a
fromLeft Path
forall a. HasCallStack => a
undefined) (Unfold m Path (Either Path Path) -> Unfold m Path Path)
-> Unfold m Path (Either Path Path) -> Unfold m Path Path
forall a b. (a -> b) -> a -> b
$ (Either Path Path -> Bool)
-> Unfold m Path (Either Path Path)
-> Unfold m Path (Either Path Path)
forall (m :: * -> *) b a.
Monad m =>
(b -> Bool) -> Unfold m a b -> Unfold m a b
UF.filter Either Path Path -> Bool
forall a b. Either a b -> Bool
isLeft ((ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
eitherReader ReadOptions -> ReadOptions
f)

-- | Raw read of a directory.
--
-- /Pre-release/
{-# INLINE read #-}
read :: (MonadIO m, MonadCatch m) =>
    Path -> Stream m Path
read :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
Path -> Stream m Path
read = Unfold m Path Path -> Path -> Stream m Path
forall (m :: * -> *) a b.
Applicative m =>
Unfold m a b -> a -> Stream m b
S.unfold Unfold m Path Path
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
Unfold m Path Path
reader

-- | Read directories as Left and files as Right. Filter out "." and ".."
-- entries. The output contains the names of the directories and files.
--
-- /Pre-release/
{-# INLINE readEither #-}
readEither :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    Path -> Stream m (Either Path Path)
readEither :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path)
readEither ReadOptions -> ReadOptions
f = Unfold m Path (Either Path Path)
-> Path -> Stream m (Either Path Path)
forall (m :: * -> *) a b.
Applicative m =>
Unfold m a b -> a -> Stream m b
S.unfold ((ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path (Either Path Path)
eitherReader ReadOptions -> ReadOptions
f)

-- | Like 'readEither' but prefix the names of the files and directories with
-- the supplied directory path.
{-# INLINE readEitherPaths #-}
readEitherPaths :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    Path -> Stream m (Either Path Path)
readEitherPaths :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path)
readEitherPaths ReadOptions -> ReadOptions
f Path
dir =
    let </> :: Path -> Path -> Path
(</>) = Path -> Path -> Path
Path.join
     in (Either Path Path -> Either Path Path)
-> Stream m (Either Path Path) -> Stream m (Either Path Path)
forall a b. (a -> b) -> Stream m a -> Stream m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((Path -> Path)
-> (Path -> Path) -> Either Path Path -> Either Path Path
forall a b c d. (a -> b) -> (c -> d) -> Either a c -> Either b d
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap (Path
dir Path -> Path -> Path
</>) (Path
dir Path -> Path -> Path
</>)) (Stream m (Either Path Path) -> Stream m (Either Path Path))
-> Stream m (Either Path Path) -> Stream m (Either Path Path)
forall a b. (a -> b) -> a -> b
$ (ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Path -> Stream m (Either Path Path)
readEither ReadOptions -> ReadOptions
f Path
dir

#if defined(mingw32_HOST_OS) || defined(__MINGW32__)
-- XXX Implement a custom version of readEitherChunks (like for Posix) for
-- windows as well. Also implement readEitherByteChunks.
--
-- XXX For a fast custom implementation of traversal, the Right could be the
-- final array chunk including all files and dirs to be written to IO. The Left
-- could be list of dirs to be traversed.
--
-- This is a generic (but slower?) version of readEitherChunks using
-- eitherReaderPaths.
{-# INLINE readEitherChunks #-}
readEitherChunks :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    [Path] -> Stream m (Either [Path] [Path])
readEitherChunks f dirs =
    -- XXX Need to use a take to limit the group size. There will be separate
    -- limits for dir and files groups.
     S.groupsWhile grouper collector
        $ S.unfoldEach (eitherReaderPaths f)
        $ S.fromList dirs

    where

    -- XXX We can use a refold "Either dirs files" and yield the one that fills
    -- and pass the remainder to the next Refold.
    grouper first next =
        case first of
            Left _ -> isLeft next
            Right _ -> isRight next

    collector = Fold.foldl' step (Right [])

    step b x =
        case x of
            Left x1 ->
                case b of
                    Right _ -> Left [x1] -- initial
                    _ -> either (\xs -> Left (x1:xs)) Right b
            Right x1 -> fmap (x1:) b
#endif

-- | Read files only.
--
--  /Internal/
--
{-# INLINE readFiles #-}
readFiles :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    Path -> Stream m Path
readFiles :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Path -> Stream m Path
readFiles ReadOptions -> ReadOptions
f = Unfold m Path Path -> Path -> Stream m Path
forall (m :: * -> *) a b.
Applicative m =>
Unfold m a b -> a -> Stream m b
S.unfold ((ReadOptions -> ReadOptions) -> Unfold m Path Path
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path Path
fileReader ReadOptions -> ReadOptions
f)

-- | Read directories only.
--
--  /Internal/
--
{-# INLINE readDirs #-}
readDirs :: (MonadIO m, MonadCatch m) => (ReadOptions -> ReadOptions) ->
    Path -> Stream m Path
readDirs :: forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Path -> Stream m Path
readDirs ReadOptions -> ReadOptions
f = Unfold m Path Path -> Path -> Stream m Path
forall (m :: * -> *) a b.
Applicative m =>
Unfold m a b -> a -> Stream m b
S.unfold ((ReadOptions -> ReadOptions) -> Unfold m Path Path
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
(ReadOptions -> ReadOptions) -> Unfold m Path Path
dirReader ReadOptions -> ReadOptions
f)

{-
-------------------------------------------------------------------------------
-- Writing
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-- Array IO (output)
-------------------------------------------------------------------------------

-- | Write an 'Array' to a file handle.
--
-- @since 0.7.0
{-# INLINABLE writeArray #-}
writeArray :: Storable a => Handle -> Array a -> IO ()
writeArray _ arr | A.length arr == 0 = return ()
writeArray h Array{..} = withForeignPtr aStart $ \p -> hPutBuf h p aLen
    where
    aLen =
        let p = unsafeForeignPtrToPtr aStart
        in arrEnd `minusPtr` p

-------------------------------------------------------------------------------
-- Stream of Arrays IO
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-- Writing
-------------------------------------------------------------------------------

-- | Write a stream of arrays to a handle.
--
-- @since 0.7.0
{-# INLINE fromChunks #-}
fromChunks :: (MonadIO m, Storable a)
    => Handle -> Stream m (Array a) -> m ()
fromChunks h m = S.mapM_ (liftIO . writeArray h) m

-- | @fromChunksWithBufferOf bufsize handle stream@ writes a stream of arrays
-- to @handle@ after coalescing the adjacent arrays in chunks of @bufsize@.
-- The chunk size is only a maximum and the actual writes could be smaller as
-- we do not split the arrays to fit exactly to the specified size.
--
-- @since 0.7.0
{-# INLINE fromChunksWithBufferOf #-}
fromChunksWithBufferOf :: (MonadIO m, Storable a)
    => Int -> Handle -> Stream m (Array a) -> m ()
fromChunksWithBufferOf n h xs = fromChunks h $ AS.compact n xs

-- | @fromStreamWithBufferOf bufsize handle stream@ writes @stream@ to @handle@
-- in chunks of @bufsize@.  A write is performed to the IO device as soon as we
-- collect the required input size.
--
-- @since 0.7.0
{-# INLINE fromStreamWithBufferOf #-}
fromStreamWithBufferOf :: MonadIO m => Int -> Handle -> Stream m Word8 -> m ()
fromStreamWithBufferOf n h m = fromChunks h $ S.pinnedChunksOf n m
-- fromStreamWithBufferOf n h m = fromChunks h $ AS.chunksOf n m

-- > write = 'writeWithBufferOf' A.defaultChunkSize
--
-- | Write a byte stream to a file handle. Accumulates the input in chunks of
-- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing.
--
-- NOTE: This may perform better than the 'write' fold, you can try this if you
-- need some extra perf boost.
--
-- @since 0.7.0
{-# INLINE fromStream #-}
fromStream :: MonadIO m => Handle -> Stream m Word8 -> m ()
fromStream = fromStreamWithBufferOf defaultChunkSize

-- | Write a stream of arrays to a handle. Each array in the stream is written
-- to the device as a separate IO request.
--
-- @since 0.7.0
{-# INLINE writeChunks #-}
writeChunks :: (MonadIO m, Storable a) => Handle -> Fold m (Array a) ()
writeChunks h = FL.drainBy (liftIO . writeArray h)

-- | @writeChunksWithBufferOf bufsize handle@ writes a stream of arrays
-- to @handle@ after coalescing the adjacent arrays in chunks of @bufsize@.
-- We never split an array, if a single array is bigger than the specified size
-- it emitted as it is. Multiple arrays are coalesed as long as the total size
-- remains below the specified size.
--
-- @since 0.7.0
{-# INLINE writeChunksWithBufferOf #-}
writeChunksWithBufferOf :: (MonadIO m, Storable a)
    => Int -> Handle -> Fold m (Array a) ()
writeChunksWithBufferOf n h = lpackArraysChunksOf n (writeChunks h)

-- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.
--
-- XXX test this
-- Note that if you use a chunk size less than 8K (GHC's default buffer
-- size) then you are advised to use 'NOBuffering' mode on the 'Handle' in case you
-- do not want buffering to occur at GHC level as well. Same thing applies to
-- writes as well.

-- | @writeWithBufferOf reqSize handle@ writes the input stream to @handle@.
-- Bytes in the input stream are collected into a buffer until we have a chunk
-- of @reqSize@ and then written to the IO device.
--
-- @since 0.7.0
{-# INLINE writeWithBufferOf #-}
writeWithBufferOf :: MonadIO m => Int -> Handle -> Fold m Word8 ()
writeWithBufferOf n h = FL.groupsOf n (pinnedWriteNUnsafe n) (writeChunks h)

-- > write = 'writeWithBufferOf' A.defaultChunkSize
--
-- | Write a byte stream to a file handle. Accumulates the input in chunks of
-- up to 'Streamly.Internal.Data.Array.Type.defaultChunkSize' before writing
-- to the IO device.
--
-- @since 0.7.0
{-# INLINE write #-}
write :: MonadIO m => Handle -> Fold m Word8 ()
write = writeWithBufferOf defaultChunkSize
-}