Redo file reading API
This commit is contained in:
parent
6a1f80bc17
commit
9b20ce2e72
@ -32,7 +32,7 @@ library
|
|||||||
System.Posix.FD
|
System.Posix.FD
|
||||||
c-sources: cbits/dirutils.c
|
c-sources: cbits/dirutils.c
|
||||||
|
|
||||||
-- other-modules:
|
other-modules: Streamly.ByteString
|
||||||
-- other-extensions:
|
-- other-extensions:
|
||||||
build-depends: base >= 4.8 && <5
|
build-depends: base >= 4.8 && <5
|
||||||
, IfElse
|
, IfElse
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
{-# LANGUAGE PackageImports #-}
|
{-# LANGUAGE PackageImports #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module HPath.IO
|
module HPath.IO
|
||||||
(
|
(
|
||||||
@ -66,7 +67,7 @@ module HPath.IO
|
|||||||
, moveFile
|
, moveFile
|
||||||
-- * File reading
|
-- * File reading
|
||||||
, readFile
|
, readFile
|
||||||
, readFileEOF
|
, readFileStream
|
||||||
-- * File writing
|
-- * File writing
|
||||||
, writeFile
|
, writeFile
|
||||||
, appendFile
|
, appendFile
|
||||||
@ -181,8 +182,14 @@ import HPath
|
|||||||
import HPath.Internal
|
import HPath.Internal
|
||||||
import HPath.IO.Errors
|
import HPath.IO.Errors
|
||||||
import Prelude hiding (appendFile, readFile, writeFile)
|
import Prelude hiding (appendFile, readFile, writeFile)
|
||||||
|
import Streamly
|
||||||
|
import Streamly.ByteString
|
||||||
|
import qualified Streamly.Data.Fold as FL
|
||||||
|
import Streamly.Memory.Array
|
||||||
import qualified Streamly.FileSystem.Handle as FH
|
import qualified Streamly.FileSystem.Handle as FH
|
||||||
|
import qualified Streamly.Internal.Data.Unfold as SU
|
||||||
import qualified Streamly.Internal.FileSystem.Handle as IFH
|
import qualified Streamly.Internal.FileSystem.Handle as IFH
|
||||||
|
import qualified Streamly.Internal.Memory.ArrayStream as AS
|
||||||
import qualified Streamly.Prelude as S
|
import qualified Streamly.Prelude as S
|
||||||
import qualified System.IO as SIO
|
import qualified System.IO as SIO
|
||||||
import System.IO.Error
|
import System.IO.Error
|
||||||
@ -867,19 +874,14 @@ moveFile from to cm = do
|
|||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
-- |Read the given file at once into memory as a strict ByteString.
|
-- |Read the given file *at once* into memory as a lazy ByteString.
|
||||||
-- Symbolic links are followed, no sanity checks on file size
|
-- Symbolic links are followed, no sanity checks on file size
|
||||||
-- or file type. File must exist.
|
-- or file type. File must exist. Uses Builders under the hood
|
||||||
--
|
-- (hence lazy ByteString).
|
||||||
-- Note: the size of the file is determined in advance, as to only
|
|
||||||
-- have one allocation.
|
|
||||||
--
|
--
|
||||||
-- Safety/reliability concerns:
|
-- Safety/reliability concerns:
|
||||||
--
|
--
|
||||||
-- * since amount of bytes to read is determined in advance,
|
-- * the whole file is read into memory, this doesn't read lazily
|
||||||
-- the file might be read partially only if something else is
|
|
||||||
-- appending to it while reading
|
|
||||||
-- * the whole file is read into memory!
|
|
||||||
--
|
--
|
||||||
-- Throws:
|
-- Throws:
|
||||||
--
|
--
|
||||||
@ -887,21 +889,15 @@ moveFile from to cm = do
|
|||||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||||
-- containting it
|
-- containting it
|
||||||
-- - `NoSuchThing` if the file does not exist
|
-- - `NoSuchThing` if the file does not exist
|
||||||
readFile :: Path b -> IO ByteString
|
readFile :: Path b -> IO L.ByteString
|
||||||
readFile (MkPath fp) =
|
readFile path = do
|
||||||
bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd -> do
|
stream <- readFileStream path
|
||||||
stat <- PF.getFdStatus fd
|
toLazyByteString <$> S.fold FL.mconcat (fmap byteString stream)
|
||||||
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
|
-- | Open the given file as a filestream. Once the filestream is
|
||||||
-- Builders under the hood.
|
-- exits, the filehandle is cleaned up.
|
||||||
--
|
|
||||||
-- Safety/reliability concerns:
|
|
||||||
--
|
|
||||||
-- * the whole file is read into memory!
|
|
||||||
--
|
--
|
||||||
-- Throws:
|
-- Throws:
|
||||||
--
|
--
|
||||||
@ -909,23 +905,13 @@ readFile (MkPath fp) =
|
|||||||
-- - `PermissionDenied` if we cannot read the file or the directory
|
-- - `PermissionDenied` if we cannot read the file or the directory
|
||||||
-- containting it
|
-- containting it
|
||||||
-- - `NoSuchThing` if the file does not exist
|
-- - `NoSuchThing` if the file does not exist
|
||||||
readFileEOF :: Path b -> IO L.ByteString
|
readFileStream :: Path b
|
||||||
readFileEOF (MkPath fp) =
|
-> IO (SerialT IO ByteString)
|
||||||
bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd ->
|
readFileStream (MkPath fp) = do
|
||||||
allocaBytes (fromIntegral bufSize) $ \buf -> read' fd buf mempty
|
fd <- openFd fp SPI.ReadOnly [] Nothing
|
||||||
where
|
handle <- SPI.fdToHandle fd
|
||||||
bufSize :: CSize
|
let stream = (S.unfold (SU.finally SIO.hClose FH.readChunks) handle) >>= arrayToByteString
|
||||||
bufSize = 8192
|
pure stream
|
||||||
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)
|
|
||||||
(return ())
|
|
||||||
read' fd buf (builder <> byteString readBS)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
57
hpath-io/src/Streamly/ByteString.hs
Normal file
57
hpath-io/src/Streamly/ByteString.hs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
|
module Streamly.ByteString where
|
||||||
|
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Data.ByteString hiding (length)
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import Data.ByteString.Unsafe
|
||||||
|
import Data.Word (Word8)
|
||||||
|
import Foreign.ForeignPtr
|
||||||
|
import Foreign.ForeignPtr.Unsafe
|
||||||
|
import Foreign.Ptr (castPtr, minusPtr, plusPtr)
|
||||||
|
import Prelude hiding (length)
|
||||||
|
import Streamly
|
||||||
|
import Streamly.Internal.Memory.Array.Types
|
||||||
|
import Streamly.Memory.Array
|
||||||
|
import qualified Streamly.Prelude as S
|
||||||
|
|
||||||
|
toByteString ::
|
||||||
|
forall m. (MonadIO m, MonadAsync m)
|
||||||
|
=> SerialT m (Array Word8)
|
||||||
|
-> m ByteString
|
||||||
|
toByteString stream =
|
||||||
|
let xs = S.mapM arrayToByteString stream
|
||||||
|
ys = S.foldlM' (\a b -> pure $ a <> b) mempty xs
|
||||||
|
in ys
|
||||||
|
|
||||||
|
arrayToByteString :: (MonadIO m) => Array Word8 -> m ByteString
|
||||||
|
arrayToByteString arr
|
||||||
|
| length arr == 0 = return mempty
|
||||||
|
arrayToByteString Array {..} =
|
||||||
|
liftIO $
|
||||||
|
withForeignPtr aStart $ \ptr ->
|
||||||
|
unsafePackCStringFinalizer ptr aLen (return ())
|
||||||
|
where
|
||||||
|
aLen =
|
||||||
|
let p = unsafeForeignPtrToPtr aStart
|
||||||
|
in aEnd `minusPtr` p
|
||||||
|
|
||||||
|
byteStringToArray :: (MonadIO m) => ByteString -> m (Array Word8)
|
||||||
|
byteStringToArray bs =
|
||||||
|
liftIO $
|
||||||
|
unsafeUseAsCStringLen
|
||||||
|
bs
|
||||||
|
(\(ptr, _) -> do
|
||||||
|
let endPtr pr = (castPtr pr `plusPtr` (BS.length bs))
|
||||||
|
fptr <- newForeignPtr_ (castPtr ptr)
|
||||||
|
return $ Array {aStart = fptr, aEnd = endPtr ptr, aBound = endPtr ptr})
|
||||||
|
|
||||||
|
fromByteString ::
|
||||||
|
forall m. (MonadIO m)
|
||||||
|
=> ByteString
|
||||||
|
-> m (Array Word8)
|
||||||
|
fromByteString bs = byteStringToArray bs
|
@ -1,85 +0,0 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
|
|
||||||
module HPath.IO.ReadFileEOFSpec where
|
|
||||||
|
|
||||||
|
|
||||||
import Test.Hspec
|
|
||||||
import System.IO.Error
|
|
||||||
(
|
|
||||||
ioeGetErrorType
|
|
||||||
)
|
|
||||||
import GHC.IO.Exception
|
|
||||||
(
|
|
||||||
IOErrorType(..)
|
|
||||||
)
|
|
||||||
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)
|
|
@ -27,7 +27,6 @@ import Data.IORef
|
|||||||
, IORef
|
, IORef
|
||||||
)
|
)
|
||||||
import HPath.IO
|
import HPath.IO
|
||||||
import HPath.IO.Errors
|
|
||||||
import Prelude hiding (appendFile, readFile, writeFile)
|
import Prelude hiding (appendFile, readFile, writeFile)
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
(
|
(
|
||||||
@ -281,10 +280,5 @@ allDirectoryContents' ip =
|
|||||||
|
|
||||||
readFile' :: ByteString -> IO ByteString
|
readFile' :: ByteString -> IO ByteString
|
||||||
{-# NOINLINE readFile' #-}
|
{-# NOINLINE readFile' #-}
|
||||||
readFile' p = withTmpDir p readFile
|
readFile' p = withTmpDir p (fmap L.toStrict . readFile)
|
||||||
|
|
||||||
|
|
||||||
readFileEOF' :: ByteString -> IO L.ByteString
|
|
||||||
{-# NOINLINE readFileEOF' #-}
|
|
||||||
readFileEOF' p = withTmpDir p readFileEOF
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user