Add file reading functions
This commit is contained in:
parent
1a2c77c6a6
commit
9ac10a6a7d
@ -60,6 +60,9 @@ module HPath.IO
|
||||
-- * File renaming/moving
|
||||
, renameFile
|
||||
, moveFile
|
||||
-- * File reading
|
||||
, readFile
|
||||
, readFileEOF
|
||||
-- * File permissions
|
||||
, newFilePerms
|
||||
, newDirPerms
|
||||
@ -97,6 +100,17 @@ import Data.ByteString
|
||||
(
|
||||
ByteString
|
||||
)
|
||||
import Data.ByteString.Builder
|
||||
(
|
||||
Builder
|
||||
, byteString
|
||||
, toLazyByteString
|
||||
)
|
||||
import qualified Data.ByteString.Lazy as L
|
||||
import Data.ByteString.Unsafe
|
||||
(
|
||||
unsafePackCStringFinalizer
|
||||
)
|
||||
import Data.Foldable
|
||||
(
|
||||
for_
|
||||
@ -112,6 +126,10 @@ import Data.Maybe
|
||||
(
|
||||
catMaybes
|
||||
)
|
||||
import Data.Monoid
|
||||
(
|
||||
(<>)
|
||||
)
|
||||
import Data.Word
|
||||
(
|
||||
Word8
|
||||
@ -145,7 +163,7 @@ import GHC.IO.Exception
|
||||
import HPath
|
||||
import HPath.Internal
|
||||
import HPath.IO.Errors
|
||||
import Prelude hiding (readFile)
|
||||
import Prelude hiding (readFile, writeFile)
|
||||
import System.IO.Error
|
||||
(
|
||||
catchIOError
|
||||
@ -845,6 +863,74 @@ moveFile from to cm = do
|
||||
|
||||
|
||||
|
||||
--------------------
|
||||
--[ File Reading ]--
|
||||
--------------------
|
||||
|
||||
|
||||
-- |Read the given file at once into memory as a strict ByteString.
|
||||
-- Symbolic links are followed, no sanity checks on file size
|
||||
-- or file type. File must exist.
|
||||
--
|
||||
-- Note: the size of the file is determined in advance, as to only
|
||||
-- have one allocation.
|
||||
--
|
||||
-- Safety/reliability concerns:
|
||||
--
|
||||
-- * since amount of bytes to read is determined in advance,
|
||||
-- the file might be read partially only if something else is
|
||||
-- appending to it while reading
|
||||
-- * the whole file is read into memory!
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `InappropriateType` if file is not a regular file or a symlink
|
||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||
-- containting it
|
||||
-- - `NoSuchThing` if the file does not exist
|
||||
readFile :: Path Abs -> IO ByteString
|
||||
readFile p = withAbsPath p $ \fp ->
|
||||
bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd -> do
|
||||
stat <- PF.getFdStatus fd
|
||||
let fsize = PF.fileSize stat
|
||||
SPB.fdRead fd (fromIntegral fsize)
|
||||
|
||||
|
||||
-- |Read the given file in chunks of size `8192` into memory until
|
||||
-- `fread` returns 0. Returns a lazy ByteString, because it uses
|
||||
-- Builders under the hood.
|
||||
--
|
||||
-- Safety/reliability concerns:
|
||||
--
|
||||
-- * the whole file is read into memory!
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `InappropriateType` if file is not a regular file or a symlink
|
||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||
-- containting it
|
||||
-- - `NoSuchThing` if the file does not exist
|
||||
readFileEOF :: Path Abs -> IO L.ByteString
|
||||
readFileEOF p = withAbsPath p $ \fp ->
|
||||
bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd ->
|
||||
allocaBytes (fromIntegral bufSize) $ \buf -> read' fd buf mempty
|
||||
where
|
||||
bufSize :: CSize
|
||||
bufSize = 8192
|
||||
read' :: Fd -> Ptr Word8 -> Builder -> IO L.ByteString
|
||||
read' fd buf builder = do
|
||||
size <- SPB.fdReadBuf fd buf bufSize
|
||||
if size == 0
|
||||
then return $ toLazyByteString builder
|
||||
else do
|
||||
readBS <- unsafePackCStringFinalizer buf
|
||||
(fromIntegral size)
|
||||
mempty
|
||||
read' fd buf (builder <> byteString readBS)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
--[ File Permissions]--
|
||||
|
86
test/HPath/IO/ReadFileEOFSpec.hs
Normal file
86
test/HPath/IO/ReadFileEOFSpec.hs
Normal file
@ -0,0 +1,86 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
module HPath.IO.ReadFileEOFSpec where
|
||||
|
||||
|
||||
import Test.Hspec
|
||||
import System.IO.Error
|
||||
(
|
||||
ioeGetErrorType
|
||||
)
|
||||
import GHC.IO.Exception
|
||||
(
|
||||
IOErrorType(..)
|
||||
)
|
||||
import System.Process
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
upTmpDir :: IO ()
|
||||
upTmpDir = do
|
||||
setTmpDir "ReadFileEOFSpec"
|
||||
createTmpDir
|
||||
|
||||
setupFiles :: IO ()
|
||||
setupFiles = do
|
||||
createRegularFile' "fileWithContent"
|
||||
createRegularFile' "fileWithoutContent"
|
||||
createSymlink' "inputFileSymL" "fileWithContent"
|
||||
createDir' "alreadyExistsD"
|
||||
createRegularFile' "noPerms"
|
||||
noPerms "noPerms"
|
||||
createDir' "noPermsD"
|
||||
createRegularFile' "noPermsD/inputFile"
|
||||
noPerms "noPermsD"
|
||||
writeFile' "fileWithContent" "Blahfaselgagaga"
|
||||
|
||||
|
||||
cleanupFiles :: IO ()
|
||||
cleanupFiles = do
|
||||
deleteFile' "fileWithContent"
|
||||
deleteFile' "fileWithoutContent"
|
||||
deleteFile' "inputFileSymL"
|
||||
deleteDir' "alreadyExistsD"
|
||||
normalFilePerms "noPerms"
|
||||
deleteFile' "noPerms"
|
||||
normalDirPerms "noPermsD"
|
||||
deleteFile' "noPermsD/inputFile"
|
||||
deleteDir' "noPermsD"
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
describe "HPath.IO.readFileEOF" $ do
|
||||
|
||||
-- successes --
|
||||
it "readFileEOF (Strict) file with content, everything clear" $ do
|
||||
out <- readFileEOF' "fileWithContent"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFileEOF (Strict) symlink, everything clear" $ do
|
||||
out <- readFileEOF' "inputFileSymL"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFileEOF (Strict) empty file, everything clear" $ do
|
||||
out <- readFileEOF' "fileWithoutContent"
|
||||
out `shouldBe` ""
|
||||
|
||||
|
||||
-- posix failures --
|
||||
it "readFileEOF (Strict) directory, wrong file type" $ do
|
||||
readFileEOF' "alreadyExistsD"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
|
||||
|
||||
it "readFileEOF (Strict) file, no permissions" $ do
|
||||
readFileEOF' "noPerms"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFileEOF (Strict) file, no permissions on dir" $ do
|
||||
readFileEOF' "noPermsD/inputFile"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFileEOF (Strict) file, no such file" $ do
|
||||
readFileEOF' "lalala"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
|
86
test/HPath/IO/ReadFileSpec.hs
Normal file
86
test/HPath/IO/ReadFileSpec.hs
Normal file
@ -0,0 +1,86 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
module HPath.IO.ReadFileSpec where
|
||||
|
||||
|
||||
import Test.Hspec
|
||||
import System.IO.Error
|
||||
(
|
||||
ioeGetErrorType
|
||||
)
|
||||
import GHC.IO.Exception
|
||||
(
|
||||
IOErrorType(..)
|
||||
)
|
||||
import System.Process
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
upTmpDir :: IO ()
|
||||
upTmpDir = do
|
||||
setTmpDir "ReadFileSpec"
|
||||
createTmpDir
|
||||
|
||||
setupFiles :: IO ()
|
||||
setupFiles = do
|
||||
createRegularFile' "fileWithContent"
|
||||
createRegularFile' "fileWithoutContent"
|
||||
createSymlink' "inputFileSymL" "fileWithContent"
|
||||
createDir' "alreadyExistsD"
|
||||
createRegularFile' "noPerms"
|
||||
noPerms "noPerms"
|
||||
createDir' "noPermsD"
|
||||
createRegularFile' "noPermsD/inputFile"
|
||||
noPerms "noPermsD"
|
||||
writeFile' "fileWithContent" "Blahfaselgagaga"
|
||||
|
||||
|
||||
cleanupFiles :: IO ()
|
||||
cleanupFiles = do
|
||||
deleteFile' "fileWithContent"
|
||||
deleteFile' "fileWithoutContent"
|
||||
deleteFile' "inputFileSymL"
|
||||
deleteDir' "alreadyExistsD"
|
||||
normalFilePerms "noPerms"
|
||||
deleteFile' "noPerms"
|
||||
normalDirPerms "noPermsD"
|
||||
deleteFile' "noPermsD/inputFile"
|
||||
deleteDir' "noPermsD"
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
describe "HPath.IO.readFile" $ do
|
||||
|
||||
-- successes --
|
||||
it "readFile (Strict) file with content, everything clear" $ do
|
||||
out <- readFile' "fileWithContent"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFile (Strict) symlink, everything clear" $ do
|
||||
out <- readFile' "inputFileSymL"
|
||||
out `shouldBe` "Blahfaselgagaga"
|
||||
|
||||
it "readFile (Strict) empty file, everything clear" $ do
|
||||
out <- readFile' "fileWithoutContent"
|
||||
out `shouldBe` ""
|
||||
|
||||
|
||||
-- posix failures --
|
||||
it "readFile (Strict) directory, wrong file type" $ do
|
||||
readFile' "alreadyExistsD"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
|
||||
|
||||
it "readFile (Strict) file, no permissions" $ do
|
||||
readFile' "noPerms"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFile (Strict) file, no permissions on dir" $ do
|
||||
readFile' "noPermsD/inputFile"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "readFile (Strict) file, no such file" $ do
|
||||
readFile' "lalala"
|
||||
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)
|
@ -28,6 +28,7 @@ import Data.IORef
|
||||
)
|
||||
import HPath.IO
|
||||
import HPath.IO.Errors
|
||||
import Prelude hiding (readFile)
|
||||
import Data.Maybe
|
||||
(
|
||||
fromJust
|
||||
@ -46,6 +47,7 @@ import Data.ByteString
|
||||
(
|
||||
ByteString
|
||||
)
|
||||
import qualified Data.ByteString.Lazy as L
|
||||
import System.Posix.Files.ByteString
|
||||
(
|
||||
groupExecuteMode
|
||||
@ -243,6 +245,12 @@ normalDirPerms path =
|
||||
withTmpDir path $ \p -> setFileMode (P.fromAbs p) newDirPerms
|
||||
|
||||
|
||||
normalFilePerms :: ByteString -> IO ()
|
||||
{-# NOINLINE normalFilePerms #-}
|
||||
normalFilePerms path =
|
||||
withTmpDir path $ \p -> setFileMode (P.fromAbs p) newFilePerms
|
||||
|
||||
|
||||
getFileType' :: ByteString -> IO FileType
|
||||
{-# NOINLINE getFileType' #-}
|
||||
getFileType' path = withTmpDir path getFileType
|
||||
@ -288,3 +296,13 @@ allDirectoryContents' :: ByteString -> IO [ByteString]
|
||||
allDirectoryContents' ip =
|
||||
withTmpDir ip $ \p -> DT.allDirectoryContents' (P.fromAbs p)
|
||||
|
||||
|
||||
readFile' :: ByteString -> IO ByteString
|
||||
{-# NOINLINE readFile' #-}
|
||||
readFile' p = withTmpDir p readFile
|
||||
|
||||
|
||||
readFileEOF' :: ByteString -> IO L.ByteString
|
||||
{-# NOINLINE readFileEOF' #-}
|
||||
readFileEOF' p = withTmpDir p readFileEOF
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user