{-# LANGUAgE CPP #-}
-- | Temporary file and directory support.
--
-- Strongly inspired by\/stolen from the <https://github.com/feuerbach/temporary> package.
--
-- @since 0.1.0.0
module UnliftIO.Temporary
  ( withSystemTempFile
  , withSystemTempDirectory
  , withTempFile
  , withTempDirectory
  ) where

import Control.Monad.IO.Unlift
import Control.Monad (liftM)
import UnliftIO.Exception
import System.Directory
import System.IO (Handle, openTempFile, hClose)
import System.IO.Error
import System.Posix.Internals (c_getpid)
import System.FilePath ((</>))

#ifdef mingw32_HOST_OS
import System.Directory       ( createDirectory )
#else
import qualified System.Posix
#endif

-- | Create and use a temporary file in the system standard temporary directory.
--
-- Behaves exactly the same as 'withTempFile', except that the parent temporary directory
-- will be that returned by 'getCanonicalTemporaryDirectory'.
--
-- @since 0.1.0.0
withSystemTempFile :: MonadUnliftIO m =>
                      String   -- ^ File name template. See 'openTempFile'.
                   -> (FilePath -> Handle -> m a) -- ^ Callback that can use the file
                   -> m a
withSystemTempFile :: String -> (String -> Handle -> m a) -> m a
withSystemTempFile String
template String -> Handle -> m a
action = IO String -> m String
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO String
getCanonicalTemporaryDirectory m String -> (String -> m a) -> m a
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \String
tmpDir -> String -> String -> (String -> Handle -> m a) -> m a
forall (m :: * -> *) a.
MonadUnliftIO m =>
String -> String -> (String -> Handle -> m a) -> m a
withTempFile String
tmpDir String
template String -> Handle -> m a
action

-- | Create and use a temporary directory in the system standard temporary directory.
--
-- Behaves exactly the same as 'withTempDirectory', except that the parent temporary directory
-- will be that returned by 'getCanonicalTemporaryDirectory'.
--
-- @since 0.1.0.0
withSystemTempDirectory :: MonadUnliftIO m =>
                           String   -- ^ Directory name template. See 'openTempFile'.
                        -> (FilePath -> m a) -- ^ Callback that can use the directory.
                        -> m a
withSystemTempDirectory :: String -> (String -> m a) -> m a
withSystemTempDirectory String
template String -> m a
action = IO String -> m String
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO String
getCanonicalTemporaryDirectory m String -> (String -> m a) -> m a
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \String
tmpDir -> String -> String -> (String -> m a) -> m a
forall (m :: * -> *) a.
MonadUnliftIO m =>
String -> String -> (String -> m a) -> m a
withTempDirectory String
tmpDir String
template String -> m a
action


-- | Use a temporary filename that doesn't already exist.
--
-- Creates a new temporary file inside the given directory, making use of the
-- template. The temp file is deleted after use. For example:
--
-- > withTempFile "src" "sdist." $ \tmpFile hFile -> do ...
--
-- The @tmpFile@ will be file in the given directory, e.g.
-- @src/sdist.342@.
--
-- @since 0.1.0.0
withTempFile :: MonadUnliftIO m =>
                FilePath -- ^ Temp dir to create the file in.
             -> String   -- ^ File name template. See 'openTempFile'.
             -> (FilePath -> Handle -> m a) -- ^ Callback that can use the file.
             -> m a
withTempFile :: String -> String -> (String -> Handle -> m a) -> m a
withTempFile String
tmpDir String
template String -> Handle -> m a
action =
  m (String, Handle)
-> ((String, Handle) -> m ()) -> ((String, Handle) -> m a) -> m a
forall (m :: * -> *) a b c.
MonadUnliftIO m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket
    (IO (String, Handle) -> m (String, Handle)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> String -> IO (String, Handle)
openTempFile String
tmpDir String
template))
    (\(String
name, Handle
handle') -> IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Handle -> IO ()
hClose Handle
handle' IO () -> IO () -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO () -> IO ()
forall (m :: * -> *). MonadUnliftIO m => m () -> m ()
ignoringIOErrors (String -> IO ()
removeFile String
name)))
    ((String -> Handle -> m a) -> (String, Handle) -> m a
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry String -> Handle -> m a
action)

-- | Create and use a temporary directory.
--
-- Creates a new temporary directory inside the given directory, making use
-- of the template. The temp directory is deleted after use. For example:
--
-- > withTempDirectory "src" "sdist." $ \tmpDir -> do ...
--
-- The @tmpDir@ will be a new subdirectory of the given directory, e.g.
-- @src/sdist.342@.
--
-- @since 0.1.0.0
withTempDirectory :: MonadUnliftIO m =>
                     FilePath -- ^ Temp directory to create the directory in.
                  -> String   -- ^ Directory name template. See 'openTempFile'.
                  -> (FilePath -> m a) -- ^ Callback that can use the directory.
                  -> m a
withTempDirectory :: String -> String -> (String -> m a) -> m a
withTempDirectory String
targetDir String
template =
  m String -> (String -> m ()) -> (String -> m a) -> m a
forall (m :: * -> *) a b c.
MonadUnliftIO m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket
    (IO String -> m String
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> String -> IO String
createTempDirectory String
targetDir String
template))
    (IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> (String -> IO ()) -> String -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ()
forall (m :: * -> *). MonadUnliftIO m => m () -> m ()
ignoringIOErrors (IO () -> IO ()) -> (String -> IO ()) -> String -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> IO ()
removeDirectoryRecursive)

-- | Return the absolute and canonical path to the system temporary
-- directory.
--
-- >>> setCurrentDirectory "/home/feuerbach/"
-- >>> setEnv "TMPDIR" "."
-- >>> getTemporaryDirectory
-- "."
-- >>> getCanonicalTemporaryDirectory
-- "/home/feuerbach"
getCanonicalTemporaryDirectory :: IO FilePath
getCanonicalTemporaryDirectory :: IO String
getCanonicalTemporaryDirectory = IO String
getTemporaryDirectory IO String -> (String -> IO String) -> IO String
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> IO String
canonicalizePath

-- | Create a temporary directory. See 'withTempDirectory'.
createTempDirectory
  :: FilePath -- ^ Temp directory to create the directory in.
  -> String -- ^ Directory name template.
  -> IO FilePath
createTempDirectory :: String -> String -> IO String
createTempDirectory String
dir String
template = do
  CPid
pid <- IO CPid
c_getpid
  CPid -> IO String
forall a. (Num a, Show a) => a -> IO String
findTempName CPid
pid
  where
    findTempName :: a -> IO String
findTempName a
x = do
      let dirpath :: String
dirpath = String
dir String -> String -> String
</> String
template String -> String -> String
forall a. [a] -> [a] -> [a]
++ a -> String
forall a. Show a => a -> String
show a
x
      Either IOError ()
r <- IO () -> IO (Either IOError ())
forall (m :: * -> *) e a.
(MonadUnliftIO m, Exception e) =>
m a -> m (Either e a)
try (IO () -> IO (Either IOError ()))
-> IO () -> IO (Either IOError ())
forall a b. (a -> b) -> a -> b
$ String -> IO ()
mkPrivateDir String
dirpath
      case Either IOError ()
r of
        Right ()
_ -> String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
dirpath
        Left  IOError
e | IOError -> Bool
isAlreadyExistsError IOError
e -> a -> IO String
findTempName (a
xa -> a -> a
forall a. Num a => a -> a -> a
+a
1)
                | Bool
otherwise              -> IOError -> IO String
forall a. IOError -> IO a
ioError IOError
e


mkPrivateDir :: String -> IO ()
#ifdef mingw32_HOST_OS
mkPrivateDir s = createDirectory s
#else
mkPrivateDir :: String -> IO ()
mkPrivateDir String
s = String -> FileMode -> IO ()
System.Posix.createDirectory String
s FileMode
0o700
#endif

ignoringIOErrors :: MonadUnliftIO m => m () -> m ()
ignoringIOErrors :: m () -> m ()
ignoringIOErrors = (Either IOError () -> ()) -> m (Either IOError ()) -> m ()
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM (() -> Either IOError () -> ()
forall a b. a -> b -> a
const ()) (m (Either IOError ()) -> m ())
-> (m () -> m (Either IOError ())) -> m () -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. m () -> m (Either IOError ())
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> m (Either IOError a)
tryIO -- yes, it's just void, but for pre-AMP GHCs