-- |
-- 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
  RawDirStream
dsHandle   <- OsPath -> IO RawDirStream
Raw.openRawDirStream OsPath
root
  Counter
dsIsClosed <- Int -> IO Counter
Counter.new Int
0
  let stream :: DirStream
stream = DirStream{RawDirStream
dsHandle :: RawDirStream
dsHandle :: RawDirStream
dsHandle, Counter
dsIsClosed :: Counter
dsIsClosed :: Counter
dsIsClosed, Weak DirStream
dsFin :: Weak DirStream
dsFin :: Weak DirStream
dsFin}
  Weak DirStream
dsFin <- DirStream -> DirStream -> Maybe (IO ()) -> IO (Weak DirStream)
forall k v. k -> v -> Maybe (IO ()) -> IO (Weak v)
mkWeak DirStream
stream DirStream
stream (IO () -> Maybe (IO ())
forall a. a -> Maybe a
Just (DirStream -> IO ()
closeDirStreamInternal DirStream
stream))
  DirStream -> IO DirStream
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure DirStream
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
  !Int
oldVal <- Counter -> Int -> Int -> IO Int
Counter.cas Counter
dsIsClosed Int
0 Int
1
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
oldVal Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
    RawDirStream -> IO ()
Raw.closeRawDirStream RawDirStream
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