packages feed

Z-IO-0.8.1.0: include/fs_shared.hs

-- This file should be included from both base and threaded FS module

-- | File bundled with offset.
--
-- Reading or writing using 'Input' \/ 'Output' instance will automatically increase offset.
-- 'FilePtr' and its operations are NOT thread safe, use 'MVar' 'FilePtr' in multiple threads.
--
-- The notes on linux 'writeFileP' applied to 'FilePtr' too.
data FilePtr = FilePtr {-# UNPACK #-} !File
                       {-# UNPACK #-} !(PrimIORef Int64)

-- |  Create a file offset bundle from an 'File'.
--
newFilePtr :: File       -- ^ the file we're reading
           -> Int64      -- ^ initial offset
           -> IO FilePtr
newFilePtr uvf off = FilePtr uvf <$> newPrimIORef off

-- | Get current offset.
getFilePtrOffset :: FilePtr -> IO Int64
getFilePtrOffset (FilePtr _ offsetRef) = readPrimIORef offsetRef

-- | Change current offset.
setFilePtrOffset :: FilePtr -> Int64 -> IO ()
setFilePtrOffset (FilePtr _ offsetRef) = writePrimIORef offsetRef

instance Input FilePtr where
    readInput (FilePtr file offsetRef) buf bufSiz =
        readPrimIORef offsetRef >>= \ off -> do
            l <- readFileP file buf bufSiz off
            writePrimIORef offsetRef (off + fromIntegral l)
            return l

instance Output FilePtr where
    writeOutput (FilePtr file offsetRef) buf bufSiz =
        readPrimIORef offsetRef >>= \ off -> do
            writeFileP file buf bufSiz off
            writePrimIORef offsetRef (off + fromIntegral bufSiz)

-- | Quickly open a file and read its content.
readFile :: HasCallStack => CBytes -> IO V.Bytes
readFile filename = do
    withResource (initFile filename O_RDONLY DEFAULT_FILE_MODE) $ \ file -> do
        readAll' =<< newBufferedInput file

-- | Quickly open a file and read its content as UTF8 text.
readTextFile :: HasCallStack => CBytes -> IO T.Text
readTextFile filename = T.validate <$> readFile filename

-- | Quickly open a file and write some content.
writeFile :: HasCallStack => CBytes -> V.Bytes -> IO ()
writeFile filename content = do
    withResource (initFile filename (O_WRONLY .|. O_CREAT) DEFAULT_FILE_MODE) $ \ file -> do
        withPrimVectorSafe content (writeOutput file)

-- | Quickly open a file and write some content as UTF8 text.
writeTextFile :: HasCallStack => CBytes -> T.Text -> IO ()
writeTextFile filename content = writeFile filename (T.getUTF8Bytes content)

-- | Quickly open a file and read its content as a JSON value.
-- Throw 'OtherError' with name @EPARSE@ if JSON value is not parsed.
readJSONFile :: (HasCallStack, JSON.JSON a) => CBytes -> IO a
readJSONFile filename = unwrap "EPARSE" . JSON.decode' =<< readFile filename

-- | Quickly open a file and write a JSON Value.
writeJSONFile :: (HasCallStack, JSON.JSON a) => CBytes -> a -> IO ()
writeJSONFile filename x = writeFile filename (JSON.encode x)

--------------------------------------------------------------------------------

-- | Find all files and directories within a given directory with a predicator.
--
-- @
--  import Z.IO.FileSystem.FilePath (splitExtension)
--  -- find all haskell source file within current dir
--  scandirRecursively "."  (\\ p _ -> (== ".hs") . snd \<$\> splitExtension p)
-- @
scandirRecursively :: HasCallStack => CBytes -> (CBytes -> DirEntType -> IO Bool) -> IO [CBytes]
scandirRecursively dir p = loop [] =<< P.normalize dir
  where
    loop acc0 pdir =
        foldM (\ acc (d,t) -> do
            d' <- pdir `P.join` d
            r <- p d' t
            let acc' = if r then (d':acc) else acc
            if (t == DirEntDir)
            then loop acc' d'
            else return acc'
        ) acc0 =<< scandir pdir

--------------------------------------------------------------------------------

-- | Does given path exist?
--
doesPathExist :: CBytes -> IO Bool
doesPathExist path = maybe False (const True) <$> stat' path

-- | Returns 'True' if the argument file exists and is either a file or a
-- symbolic link to a file, and 'False' otherwise.
doesFileExist :: CBytes -> IO Bool
doesFileExist path = maybe False isFileSt <$> stat' path

-- | Returns 'True' if the argument directory exists and is either a directory or a
-- symbolic link to a directory, and 'False' otherwise.
doesDirExist :: CBytes -> IO Bool
doesDirExist path = maybe False isDirSt <$> stat' path

--------------------------------------------------------------------------------

-- | If given path is a symbolic link?
isLink :: HasCallStack => CBytes -> IO Bool
isLink = fmap isLinkSt . lstat

-- | If given path is a directory or a symbolic link to a directory?
isDir :: HasCallStack => CBytes -> IO Bool
isDir = fmap isDirSt . stat

-- | If given path is a file or a symbolic link to a file?
isFile :: HasCallStack => CBytes -> IO Bool
isFile = fmap isFileSt . stat

-- | Shortcut to @\\ st -> stMode st .&. S_IFMT == S_IFLNK@
--
-- Note you should use 'lstat' to get the link's stat.
isLinkSt :: FStat -> Bool
isLinkSt st = stMode st .&. S_IFMT == S_IFLNK

-- | Shortcut to @\\ st -> stMode st .&. S_IFMT == S_IFDIR@
isDirSt :: FStat -> Bool
isDirSt st = stMode st .&. S_IFMT == S_IFDIR

-- | Shortcut to @\\ st -> stMode st .&. S_IFMT == S_IFREG@
isFileSt :: FStat -> Bool
isFileSt st = stMode st .&. S_IFMT == S_IFREG

-------------------------------------------------------------------------------

-- | Make a temporary file under system 'Env.getTempDir' and automatically clean after used.
--
-- This is a shortcut to 'mkstemp', the file name use @Z-IO-@ as prefix, and will be removed after use.
--
-- >>> withResource initTempFile $ printStd
-- ("/var/folders/3l/cfdy03vd1gvd1x75gg_js7280000gn/T/Z-IO-bYgZDX",File 13)
--
initTempFile :: HasCallStack => Resource (CBytes, File)
initTempFile = do
    d <- liftIO $ Env.getTempDir
    mkstemp d "Z-IO-" False

-- | Make a temporary directory under system 'Env.getTempDir' and automatically clean after used.
--
-- This is a shortcut to 'mkdtemp', the directory name use @Z-IO-@ as prefix, and will be removed(with files within it) after use.
--
-- >>> withResource initTempDir $ printStd
-- "/tmp/Z-IO-xfWR0L"
--
initTempDir :: HasCallStack => Resource CBytes
initTempDir = initResource (Env.getTempDir >>= (`P.join` "Z-IO-") >>= mkdtemp) rmrf