module System.Directory
(
isFile
, isDirectory
, fileSize
, rename
, modified
, listDirectory
, createDirectory
, createTree
, removeFile
, removeDirectory
, removeTree
, getWorkingDirectory
, setWorkingDirectory
, getHomeDirectory
, getDesktopDirectory
, getDocumentsDirectory
, getAppDataDirectory
, getAppCacheDirectory
, getAppConfigDirectory
) where
import Prelude hiding (FilePath)
import qualified Control.Exception as Exc
import qualified Data.Text as T
import qualified System.Environment as SE
import System.FilePath (FilePath, append)
import System.FilePath.CurrentOS (currentOS)
import qualified System.FilePath.Rules as R
import System.IO.Error (isDoesNotExistError)
#ifdef CABAL_OS_WINDOWS
import Data.Bits ((.|.))
import Data.Time ( UTCTime(..)
, fromGregorian
, secondsToDiffTime
, picosecondsToDiffTime)
import qualified System.Win32 as Win32
#else
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import qualified System.Posix as Posix
#endif
import qualified "directory" System.Directory as SD
import System.FileIO.Internal (encode, decode)
isFile :: FilePath -> IO Bool
isFile path = SD.doesFileExist (encode path)
isDirectory :: FilePath -> IO Bool
isDirectory path = SD.doesDirectoryExist (encode path)
modified :: FilePath -> IO UTCTime
modified path = do
#ifdef CABAL_OS_WINDOWS
info <- withHANDLE path Win32.getFileInformationByHandle
let ftime = Win32.bhfiLastWriteTime info
stime <- Win32.fileTimeToSystemTime ftime
let date = fromGregorian
(fromIntegral (Win32.wYear stime))
(fromIntegral (Win32.wMonth stime))
(fromIntegral (Win32.wDay stime))
let seconds = secondsToDiffTime $
(toInteger (Win32.wHour stime) * 3600) +
(toInteger (Win32.wMinute stime) * 60) +
(toInteger (Win32.wSecond stime))
let msecs = picosecondsToDiffTime $
(toInteger (Win32.wMilliseconds stime) * 1000000000)
return (UTCTime date (seconds + msecs))
#else
stat <- Posix.getFileStatus (encode path)
let mtime = Posix.modificationTime stat
return (posixSecondsToUTCTime (realToFrac mtime))
#endif
fileSize :: FilePath -> IO Integer
fileSize path = do
#ifdef CABAL_OS_WINDOWS
info <- withHANDLE path Win32.getFileInformationByHandle
return (toInteger (Win32.bhfiSize info))
#else
stat <- Posix.getFileStatus (encode path)
return (toInteger (Posix.fileSize stat))
#endif
rename :: FilePath -> FilePath -> IO ()
rename old new =
let old' = encode old in
let new' = encode new in
#ifdef CABAL_OS_WINDOWS
Win32.moveFileEx old' new' Win32.mOVEFILE_REPLACE_EXISTING
#else
Posix.rename old' new'
#endif
createDirectory :: Bool
-> FilePath -> IO ()
createDirectory False path =
let path' = encode path in
#ifdef CABAL_OS_WINDOWS
Win32.createDirectory path' Nothing
#else
Posix.createDirectory path' 0o777
#endif
createDirectory True path = SD.createDirectoryIfMissing False (encode path)
createTree :: FilePath -> IO ()
createTree path = SD.createDirectoryIfMissing True (encode path)
listDirectory :: FilePath -> IO [FilePath]
listDirectory path = fmap cleanup contents where
contents = SD.getDirectoryContents (encode path)
cleanup = map decode . filter (`notElem` [".", ".."])
removeFile :: FilePath -> IO ()
removeFile path =
let path' = encode path in
#ifdef CABAL_OS_WINDOWS
Win32.deleteFile path'
#else
Posix.removeLink path'
#endif
removeDirectory :: FilePath -> IO ()
removeDirectory path =
let path' = encode path in
#ifdef CABAL_OS_WINDOWS
Win32.removeDirectory path'
#else
Posix.removeDirectory path'
#endif
removeTree :: FilePath -> IO ()
removeTree path = SD.removeDirectoryRecursive (encode path)
getWorkingDirectory :: IO FilePath
getWorkingDirectory = do
#ifdef CABAL_OS_WINDOWS
raw <- Win32.getCurrentDirectory
#else
raw <- Posix.getWorkingDirectory
#endif
return (decode raw)
setWorkingDirectory :: FilePath -> IO ()
setWorkingDirectory path =
let path' = encode path in
#ifdef CABAL_OS_WINDOWS
Win32.setCurrentDirectory path'
#else
Posix.changeWorkingDirectory path'
#endif
getHomeDirectory :: IO FilePath
getHomeDirectory = fmap decode SD.getHomeDirectory
getDesktopDirectory :: IO FilePath
getDesktopDirectory = xdg "XDG_DESKTOP_DIR" Nothing
(homeSlash "Desktop")
getDocumentsDirectory :: IO FilePath
getDocumentsDirectory = xdg "XDG_DOCUMENTS_DIR" Nothing
#ifdef CABAL_OS_WINDOWS
(fmap decode SD.getUserDocumentsDirectory)
#else
(homeSlash "Documents")
#endif
getAppDataDirectory :: T.Text -> IO FilePath
getAppDataDirectory label = xdg "XDG_DATA_HOME" (Just label)
#ifdef CABAL_OS_WINDOWS
(fmap decode (SD.getAppUserDataDirectory ""))
#else
(homeSlash ".local/share")
#endif
getAppCacheDirectory :: T.Text -> IO FilePath
getAppCacheDirectory label = xdg "XDG_CACHE_HOME" (Just label)
#ifdef CABAL_OS_WINDOWS
(homeSlash "Local Settings\\Cache")
#else
(homeSlash ".cache")
#endif
getAppConfigDirectory :: T.Text -> IO FilePath
getAppConfigDirectory label = xdg "XDG_CONFIG_HOME" (Just label)
#ifdef CABAL_OS_WINDOWS
(homeSlash "Local Settings")
#else
(homeSlash ".config")
#endif
homeSlash :: String -> IO FilePath
homeSlash path = do
home <- getHomeDirectory
return (append home (decode path))
getenv :: String -> IO (Maybe String)
getenv key = Exc.catch
(fmap Just (SE.getEnv key))
(\e -> if isDoesNotExistError e
then return Nothing
else Exc.throwIO e)
xdg :: String -> Maybe T.Text -> IO FilePath -> IO FilePath
xdg envkey label fallback = do
env <- getenv envkey
dir <- case env of
Just var -> return (decode var)
Nothing -> fallback
return $ case label of
Just text -> append dir (R.fromText currentOS text)
Nothing -> dir
#ifdef CABAL_OS_WINDOWS
withHANDLE :: FilePath -> (Win32.HANDLE -> IO a) -> IO a
withHANDLE path = Exc.bracket open close where
open = Win32.createFile
(encode path)
Win32.gENERIC_READ
(Win32.fILE_SHARE_READ .|. Win32.fILE_SHARE_WRITE)
Nothing
Win32.oPEN_EXISTING
0
Nothing
close = Win32.closeHandle
#endif