    {-# 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 template action = liftIO getCanonicalTemporaryDirectory >>= \tmpDir -> withTempFile tmpDir template 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 template action = liftIO getCanonicalTemporaryDirectory >>= \tmpDir -> withTempDirectory tmpDir template 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 tmpDir template action =
      bracket
        (liftIO (openTempFile tmpDir template))
        (\(name, handle') -> liftIO (hClose handle' >> ignoringIOErrors (removeFile name)))
        (uncurry 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 targetDir template =
      bracket
        (liftIO (createTempDirectory targetDir template))
        (liftIO . ignoringIOErrors . 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 = getTemporaryDirectory >>= canonicalizePath

    -- | Create a temporary directory. See 'withTempDirectory'.
    createTempDirectory
      :: FilePath -- ^ Temp directory to create the directory in.
      -> String -- ^ Directory name template.
      -> IO FilePath
    createTempDirectory dir template = do
      pid <- c_getpid
      findTempName pid
      where
        findTempName x = do
          let dirpath = dir </> template ++ show x
          r <- try $ mkPrivateDir dirpath
          case r of
            Right _ -> return dirpath
            Left  e | isAlreadyExistsError e -> findTempName (x+1)
                    | otherwise              -> ioError e

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

    ignoringIOErrors :: MonadUnliftIO m => m () -> m ()
    ignoringIOErrors = liftM (const ()) . tryIO -- yes, it's just void, but for pre-AMP GHCs