-- |
-- 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