packages feed

hpath-io 0.10.0 → 0.10.1

raw patch · 9 files changed

+201/−182 lines, 9 filesdep +safe-exceptionsdep +transformersPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: safe-exceptions, transformers

API changes (from Hackage documentation)

- HPath.IO: readFileEOF :: Path b -> IO ByteString
- HPath.IO.Errors: canOpenDirectory :: Path b -> IO Bool
- HPath.IO.Errors: doesDirectoryExist :: Path b -> IO Bool
- HPath.IO.Errors: doesFileExist :: Path b -> IO Bool
- HPath.IO.Errors: isWritable :: Path b -> IO Bool
+ HPath.IO: canOpenDirectory :: Path b -> IO Bool
+ HPath.IO: doesDirectoryExist :: Path b -> IO Bool
+ HPath.IO: doesExist :: Path b -> IO Bool
+ HPath.IO: doesFileExist :: Path b -> IO Bool
+ HPath.IO: isWritable :: Path b -> IO Bool
+ HPath.IO: readFileStream :: Path b -> IO (SerialT IO ByteString)

Files

CHANGELOG.md view
@@ -1,5 +1,17 @@ # Revision history for hpath-io -## 0.9.3 -- YYYY-mm-dd+## 0.10.1 -- 2020-01-13 -* First version. Released on an unsuspecting world.+* Move file check functions to HPath.IO+* Add 'doesExist'+* Exception handling of `doesExist`, `doesFileExist`, `doesDirectoryExist` has changed: only eNOENT is catched+* Exception handling of `isWritable` has changed: just a wrapper around `access` now+* switch exception handling to `safe-exceptions`+* Redo file reading API (readFileEOF dropped and now using streamly under the hood, added `readFileStream`)+++## 0.10.0 -- 2020-01-04++* First version. Split from 'hpath', contains only the IO parts.+* Now uses streamly for 'copyFile'+* Fixed tmpdir in hspec
README.md view
@@ -4,6 +4,8 @@  High-level IO operations on files/directories, utilizing type-safe Paths. +This package is part of the HPath suite, also check out [hpath](https://hackage.haskell.org/package/hpath) and [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath).+ ## Motivation  The motivation came during development of
hpath-io.cabal view
@@ -1,5 +1,5 @@ name:                hpath-io-version:             0.10.0+version:             0.10.1 synopsis:            High-level IO operations on files/directories description:         High-level IO operations on files/directories, utilizing type-safe Paths -- bug-reports:@@ -32,17 +32,20 @@                      System.Posix.FD   c-sources:         cbits/dirutils.c -  -- other-modules:+  other-modules:     Streamly.ByteString   -- other-extensions:   build-depends:     base >= 4.8 && <5                    , IfElse                    , bytestring >= 0.10.0.0                    , hpath >= 0.10 && < 0.11                    , hpath-filepath >= 0.10 && < 0.11+                   , safe-exceptions >= 0.1                    , streamly >= 0.7                    , unix >= 2.5                    , unix-bytestring                    , utf8-string+  if !impl(ghc>=7.11)+    build-depends:     transformers   hs-source-dirs:      src   default-language:    Haskell2010 @@ -73,7 +76,6 @@                         HPath.IO.GetFileTypeSpec                         HPath.IO.MoveFileOverwriteSpec                         HPath.IO.MoveFileSpec-                        HPath.IO.ReadFileEOFSpec                         HPath.IO.ReadFileSpec                         HPath.IO.RecreateSymlinkOverwriteSpec                         HPath.IO.RecreateSymlinkSpec
src/HPath/IO.hs view
@@ -36,6 +36,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}  module HPath.IO   (@@ -66,13 +67,19 @@   , moveFile   -- * File reading   , readFile-  , readFileEOF+  , readFileStream   -- * File writing   , writeFile   , appendFile   -- * File permissions   , newFilePerms   , newDirPerms+  -- * File checks+  , doesExist+  , doesFileExist+  , doesDirectoryExist+  , isWritable+  , canOpenDirectory   -- * Directory reading   , getDirsFiles   -- * Filetype operations@@ -88,7 +95,7 @@   (     (<$>)   )-import Control.Exception+import Control.Exception.Safe   (     IOException   , bracket@@ -175,8 +182,14 @@ import HPath.Internal import HPath.IO.Errors 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.Internal.Data.Unfold as SU import qualified Streamly.Internal.FileSystem.Handle as IFH+import qualified Streamly.Internal.Memory.ArrayStream as AS import qualified Streamly.Prelude as S import qualified System.IO as SIO import System.IO.Error@@ -191,7 +204,9 @@ import System.Posix.Directory.ByteString   (     createDirectory+  , closeDirStream   , getWorkingDirectory+  , openDirStream   , removeDirectory   ) import System.Posix.Directory.Traversals@@ -201,6 +216,7 @@ import System.Posix.Files.ByteString   (     createSymbolicLink+  , fileAccess   , fileMode   , getFdStatus   , groupExecuteMode@@ -446,7 +462,9 @@     case cm of       Strict -> return ()       Overwrite -> do-        writable <- toAbs newsym >>= isWritable+        writable <- toAbs newsym >>= (\p -> do+                                        e <- doesExist p+                                        if e then isWritable p else pure False)         isfile   <- doesFileExist newsym         isdir    <- doesDirectoryExist newsym         when (writable && isfile) (deleteFile newsym)@@ -830,7 +848,10 @@                 easyDelete from     Overwrite -> do       ft <- getFileType from-      writable <- toAbs to >>= isWritable+      writable <- toAbs to >>= (\p -> do+                                      e <- doesFileExist p+                                      if e then isWritable p else pure False)+       case ft of         RegularFile -> do           exists <- doesFileExist to@@ -853,19 +874,14 @@     --------------------  --- |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--- or file type. File must exist.------ Note: the size of the file is determined in advance, as to only--- have one allocation.+-- or file type. File must exist. Uses Builders under the hood+-- (hence lazy ByteString). -- -- 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!+--    * the whole file is read into memory, this doesn't read lazily -- -- Throws: --@@ -873,21 +889,15 @@ --     - `PermissionDenied` if we cannot read the file or the directory --        containting it --     - `NoSuchThing` if the file does not exist-readFile :: Path b -> IO ByteString-readFile (MkPath 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)+readFile :: Path b -> IO L.ByteString+readFile path = do+  stream <- readFileStream path+  toLazyByteString <$> S.fold FL.mconcat (fmap byteString stream)  --- |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!++-- | Open the given file as a filestream. Once the filestream is+-- exits, the filehandle is cleaned up. -- -- Throws: --@@ -895,23 +905,13 @@ --     - `PermissionDenied` if we cannot read the file or the directory --        containting it --     - `NoSuchThing` if the file does not exist-readFileEOF :: Path b -> IO L.ByteString-readFileEOF (MkPath 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)-                                                 (return ())-            read' fd buf (builder <> byteString readBS)+readFileStream :: Path b+               -> IO (SerialT IO ByteString)+readFileStream (MkPath fp) = do+  fd <- openFd fp SPI.ReadOnly [] Nothing+  handle <- SPI.fdToHandle fd+  let stream = (S.unfold (SU.finally SIO.hClose FH.readChunks) handle) >>= arrayToByteString+  pure stream   @@ -977,6 +977,72 @@     `unionFileModes` groupReadMode     `unionFileModes` otherExecuteMode     `unionFileModes` otherReadMode+++++    -------------------+    --[ File checks ]--+    -------------------+++-- |Checks if the given file exists.+-- Does not follow symlinks.+--+-- Only eNOENT is catched (and returns False).+doesExist :: Path b -> IO Bool+doesExist (MkPath bs) =+  catchErrno [eNOENT] (do+      _  <- PF.getSymbolicLinkStatus bs+      return $ True)+    $ return False+++-- |Checks if the given file exists and is not a directory.+-- Does not follow symlinks.+--+-- Only eNOENT is catched (and returns False).+doesFileExist :: Path b -> IO Bool+doesFileExist (MkPath bs) =+  catchErrno [eNOENT] (do+      fs  <- PF.getSymbolicLinkStatus bs+      return $ not . PF.isDirectory $ fs)+    $ return False+++-- |Checks if the given file exists and is a directory.+-- Does not follow symlinks.+--+-- Only eNOENT is catched (and returns False).+doesDirectoryExist :: Path b -> IO Bool+doesDirectoryExist (MkPath bs) =+  catchErrno [eNOENT] (do+      fs  <- PF.getSymbolicLinkStatus bs+      return $ PF.isDirectory fs)+    $ return False+++-- |Checks whether a file or folder is writable.+--+-- Only eACCES, eROFS, eTXTBSY, ePERM are catched (and return False).+--+-- Throws:+--+--     - `NoSuchThing` if the file does not exist+isWritable :: Path b -> IO Bool+isWritable (MkPath bs) = fileAccess bs False True False+++-- |Checks whether the directory at the given path exists and can be+-- opened. This invokes `openDirStream` which follows symlinks.+canOpenDirectory :: Path b -> IO Bool+canOpenDirectory (MkPath bs) =+  handleIOError (\_ -> return False) $ do+    bracket (openDirStream bs)+            closeDirStream+            (\_ -> return ())+    return True+   
src/HPath/IO.hs-boot view
@@ -6,3 +6,11 @@ canonicalizePath :: Path b -> IO (Path Abs)  toAbs :: Path b -> IO (Path Abs)++doesFileExist :: Path b -> IO Bool++doesDirectoryExist :: Path b -> IO Bool++isWritable :: Path b -> IO Bool++canOpenDirectory :: Path b -> IO Bool
src/HPath/IO/Errors.hs view
@@ -33,10 +33,6 @@   , throwSameFile   , sameFile   , throwDestinationInSource-  , doesFileExist-  , doesDirectoryExist-  , isWritable-  , canOpenDirectory    -- * Error handling functions   , catchErrno@@ -52,7 +48,7 @@   (     (<$>)   )-import Control.Exception+import Control.Exception.Safe hiding (handleIOError) import Control.Monad   (     forM@@ -92,11 +88,14 @@   (     canonicalizePath   , toAbs+  , doesFileExist+  , doesDirectoryExist+  , isWritable+  , canOpenDirectory   ) import System.IO.Error   (     alreadyExistsErrorType-  , catchIOError   , ioeGetErrorType   , mkIOError   )@@ -240,42 +239,6 @@               $ PF.getFileStatus sbs   when (elem sid dids)        (throwIO $ DestinationInSource dbs sbs)----- |Checks if the given file exists and is not a directory.--- Does not follow symlinks.-doesFileExist :: Path b -> IO Bool-doesFileExist (MkPath bs) =-  handleIOError (\_ -> return False) $ do-    fs  <- PF.getSymbolicLinkStatus bs-    return $ not . PF.isDirectory $ fs----- |Checks if the given file exists and is a directory.--- Does not follow symlinks.-doesDirectoryExist :: Path b -> IO Bool-doesDirectoryExist (MkPath bs) =-  handleIOError (\_ -> return False) $ do-    fs  <- PF.getSymbolicLinkStatus bs-    return $ PF.isDirectory fs----- |Checks whether a file or folder is writable.-isWritable :: Path b -> IO Bool-isWritable (MkPath bs) =-  handleIOError (\_ -> return False) $-    fileAccess bs False True False----- |Checks whether the directory at the given path exists and can be--- opened. This invokes `openDirStream` which follows symlinks.-canOpenDirectory :: Path b -> IO Bool-canOpenDirectory (MkPath bs) =-  handleIOError (\_ -> return False) $ do-    bracket (PFD.openDirStream bs)-            PFD.closeDirStream-            (\_ -> return ())-    return True   
+ src/Streamly/ByteString.hs view
@@ -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
− test/HPath/IO/ReadFileEOFSpec.hs
@@ -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)
test/Utils.hs view
@@ -27,7 +27,6 @@   , IORef   ) import HPath.IO-import HPath.IO.Errors import Prelude hiding (appendFile, readFile, writeFile) import Data.Maybe   (@@ -281,10 +280,5 @@  readFile' :: ByteString -> IO ByteString {-# NOINLINE readFile' #-}-readFile' p = withTmpDir p readFile---readFileEOF' :: ByteString -> IO L.ByteString-{-# NOINLINE readFileEOF' #-}-readFileEOF' p = withTmpDir p readFileEOF+readFile' p = withTmpDir p (fmap L.toStrict . readFile)