    -- |
    -- Module:     System.Directory.OsPath.Streaming.Internal
    -- Copyright:  (c) Sergey Vinokurov 2024
    -- License:    Apache-2.0 (see LICENSE)
    -- Maintainer: serg.foo@gmail.com

    {-# LANGUAGE BangPatterns   #-}
    {-# LANGUAGE MagicHash      #-}
    {-# LANGUAGE NamedFieldPuns #-}
    {-# LANGUAGE RecursiveDo    #-}
    {-# LANGUAGE UnboxedTuples  #-}

    module System.Directory.OsPath.Streaming.Internal
      ( DirStream(..)
      , openDirStream
      , readDirStream
      , closeDirStream

      , readDirStreamWithCache
      ) where

    import Control.Concurrent.Counter (Counter)
    import qualified Control.Concurrent.Counter as Counter
    import Control.Monad (when)
    import System.Mem.Weak (Weak, mkWeak, finalize)
    import System.OsPath (OsPath)

    import qualified System.Directory.OsPath.Streaming.Internal.Raw as Raw
    import System.Directory.OsPath.Types
    import System.Directory.OsPath.Utils (touch)

    -- | Abstract handle to directory contents.
    --
    -- May be closed multiple times and will be automatically closed by GC
    -- when it goes out of scope.
    data DirStream = DirStream
      { DirStream -> RawDirStream
    dsHandle   :: !Raw.RawDirStream
      , DirStream -> Counter
    dsIsClosed :: {-# UNPACK #-} !Counter
      , DirStream -> Weak DirStream
    dsFin      :: {-# UNPACK #-} !(Weak DirStream)
      }

    openDirStream :: OsPath -> IO DirStream
    openDirStream :: OsPath -> IO DirStream
    openDirStream OsPath
    root = mdo
      dsHandle   <- OsPath -> IO RawDirStream
    Raw.openRawDirStream OsPath
    root
      dsIsClosed <- Counter.new 0
      let stream = DirStream{RawDirStream
    dsHandle :: RawDirStream
    dsHandle :: RawDirStream
    dsHandle, Counter
    dsIsClosed :: Counter
    dsIsClosed :: Counter
    dsIsClosed, Weak DirStream
    dsFin :: Weak DirStream
    dsFin :: Weak DirStream
    dsFin}
      dsFin <- mkWeak stream stream (Just (closeDirStreamInternal stream))
      pure stream

    -- | Deallocate directory handle. It’s safe to close 'DirStream' multiple times,
    -- unlike the underlying OS-specific directory stream handle.
    closeDirStream :: DirStream -> IO ()
    closeDirStream :: DirStream -> IO ()
    closeDirStream DirStream
    stream = do
      -- Finalize ourselves to do it only once instead of running finalizer
      -- in GC afterwards once more.
      Weak DirStream -> IO ()
    forall v. Weak v -> IO ()
    finalize (DirStream -> Weak DirStream
    dsFin DirStream
    stream)
      DirStream -> IO ()
    forall x. x -> IO ()
    touch DirStream
    stream

    closeDirStreamInternal :: DirStream -> IO ()
    closeDirStreamInternal :: DirStream -> IO ()
    closeDirStreamInternal DirStream{RawDirStream
    dsHandle :: DirStream -> RawDirStream
    dsHandle :: RawDirStream
    dsHandle, Counter
    dsIsClosed :: DirStream -> Counter
    dsIsClosed :: Counter
    dsIsClosed} = do
      !oldVal <- Counter -> Int -> Int -> IO Int
    Counter.cas Counter
    dsIsClosed Int
    0 Int
    1
      when (oldVal == 0) $
        Raw.closeRawDirStream dsHandle

    readDirStream :: DirStream -> IO (Maybe (OsPath, FileType))
    readDirStream :: DirStream -> IO (Maybe (OsPath, FileType))
    readDirStream = RawDirStream -> IO (Maybe (OsPath, FileType))
    Raw.readRawDirStream (RawDirStream -> IO (Maybe (OsPath, FileType)))
    -> (DirStream -> RawDirStream)
    -> DirStream
    -> IO (Maybe (OsPath, FileType))
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . DirStream -> RawDirStream
    dsHandle

    readDirStreamWithCache
      :: Raw.DirReadCache
      -> DirStream
      -> IO (Maybe (OsPath, Basename OsPath, FileType))
    readDirStreamWithCache :: DirReadCache
    -> DirStream -> IO (Maybe (OsPath, Basename OsPath, FileType))
    readDirStreamWithCache DirReadCache
    cache =
      DirReadCache
    -> RawDirStream -> IO (Maybe (OsPath, Basename OsPath, FileType))
    Raw.readRawDirStreamWithCache DirReadCache
    cache (RawDirStream -> IO (Maybe (OsPath, Basename OsPath, FileType)))
    -> (DirStream -> RawDirStream)
    -> DirStream
    -> IO (Maybe (OsPath, Basename OsPath, FileType))
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . DirStream -> RawDirStream
    dsHandle