|
| 1 | +module Streamly.Internal.FileSystem.File.Utils |
| 2 | + ( openFile |
| 3 | + , withFile |
| 4 | + ) where |
| 5 | + |
| 6 | +------------------------------------------------------------------------------- |
| 7 | +-- Imports |
| 8 | +------------------------------------------------------------------------------- |
| 9 | + |
| 10 | +import Control.Exception (mask, onException, try) |
| 11 | +import Control.Monad (when) |
| 12 | +import GHC.IO (catchException, unsafePerformIO) |
| 13 | +import GHC.IO.Exception (IOException(..)) |
| 14 | +import GHC.IO.Handle.Internals (addHandleFinalizer, handleFinalizer) |
| 15 | +import Streamly.Internal.FileSystem.Path (Path) |
| 16 | +import System.IO (IOMode(..), Handle, hSetBinaryMode, hClose) |
| 17 | + |
| 18 | +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) |
| 19 | +import qualified Streamly.Internal.FileSystem.Windows.File as Platform |
| 20 | +#else |
| 21 | +import qualified Streamly.Internal.FileSystem.Posix.File as Platform |
| 22 | +#endif |
| 23 | + |
| 24 | +import qualified Streamly.Internal.FileSystem.Path as Path |
| 25 | + |
| 26 | +#if !(MIN_VERSION_base(4,16,0)) |
| 27 | +import Control.Concurrent.MVar (MVar, addMVarFinalizer) |
| 28 | +import GHC.IO.Handle.Internals (debugIO) |
| 29 | +import GHC.IO.Handle.Types (Handle__, Handle(..)) |
| 30 | +#endif |
| 31 | + |
| 32 | +------------------------------------------------------------------------------- |
| 33 | +-- Utils |
| 34 | +------------------------------------------------------------------------------- |
| 35 | + |
| 36 | +#if !(MIN_VERSION_base(4,16,0)) |
| 37 | +type HandleFinalizer = FilePath -> MVar Handle__ -> IO () |
| 38 | + |
| 39 | +-- | Add a finalizer to a 'Handle'. Specifically, the finalizer |
| 40 | +-- will be added to the 'MVar' of a file handle or the write-side |
| 41 | +-- 'MVar' of a duplex handle. See Handle Finalizers for details. |
| 42 | +addHandleFinalizer :: Handle -> HandleFinalizer -> IO () |
| 43 | +addHandleFinalizer handle finalizer = do |
| 44 | + debugIO $ "Registering finalizer: " ++ show filepath |
| 45 | + addMVarFinalizer mv (finalizer filepath mv) |
| 46 | + where |
| 47 | + !(filepath, !mv) = case handle of |
| 48 | + FileHandle fp m -> (fp, m) |
| 49 | + DuplexHandle fp _ write_m -> (fp, write_m) |
| 50 | +#endif |
| 51 | + |
| 52 | +addFilePathToIOError :: String -> Path -> IOException -> IOException |
| 53 | +addFilePathToIOError fun fp ioe = unsafePerformIO $ do |
| 54 | + let fp' = Path.toString fp |
| 55 | + -- XXX Why is this important? |
| 56 | + -- deepseq will be introduced dependency because of this |
| 57 | + -- fp'' <- evaluate $ force fp' |
| 58 | + pure $ ioe{ ioe_location = fun, ioe_filename = Just fp' } |
| 59 | + |
| 60 | +augmentError :: String -> Path -> IO a -> IO a |
| 61 | +augmentError str osfp = flip catchException (ioError . addFilePathToIOError str osfp) |
| 62 | + |
| 63 | +withOpenFile' |
| 64 | + :: Path |
| 65 | + -> IOMode -> Bool -> Bool -> Bool |
| 66 | + -> (Handle -> IO r) -> Bool -> IO r |
| 67 | +withOpenFile' fp iomode binary existing cloExec action close_finally = |
| 68 | + mask $ \restore -> do |
| 69 | + hndl <- case (existing, cloExec) of |
| 70 | + (True, False) -> Platform.openExistingFile fp iomode |
| 71 | + (False, False) -> Platform.openFile fp iomode |
| 72 | + (True, True) -> Platform.openExistingFileWithCloseOnExec fp iomode |
| 73 | + (False, True) -> Platform.openFileWithCloseOnExec fp iomode |
| 74 | + addHandleFinalizer hndl handleFinalizer |
| 75 | + when binary $ hSetBinaryMode hndl True |
| 76 | + r <- restore (action hndl) `onException` hClose hndl |
| 77 | + when close_finally $ hClose hndl |
| 78 | + pure r |
| 79 | + |
| 80 | +-- | Open a file and return the 'Handle'. |
| 81 | +openFile :: Path -> IOMode -> IO Handle |
| 82 | +openFile osfp iomode = |
| 83 | + augmentError "openFile" osfp $ withOpenFile' osfp iomode False False False pure False |
| 84 | + |
| 85 | +-- | Run an action on a file. |
| 86 | +-- |
| 87 | +-- The 'Handle' is automatically closed afther the action. |
| 88 | +withFile :: Path -> IOMode -> (Handle -> IO r) -> IO r |
| 89 | +withFile osfp iomode act = (augmentError "withFile" osfp |
| 90 | + $ withOpenFile' osfp iomode False False False (try . act) True) |
| 91 | + >>= either ioError pure |
0 commit comments