packages feed

tar-conduit 0.2.3.1 → 0.2.4

raw patch · 7 files changed

+175/−67 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Data.Conduit.Tar: extractTarballLenient :: FilePath -> Maybe FilePath -> IO [(FileInfo, [SomeException])]
+ Data.Conduit.Tar: restoreFileWithErrors :: (MonadResource m) => Bool -> FileInfo -> ConduitM ByteString (IO (FileInfo, [SomeException])) m ()
+ Data.Conduit.Tar.Types: UnsupportedType :: !FileType -> TarException
- Data.Conduit.Tar.Types: newtype CGid :: *
+ Data.Conduit.Tar.Types: newtype CGid
- Data.Conduit.Tar.Types: newtype CUid :: *
+ Data.Conduit.Tar.Types: newtype CUid

Files

ChangeLog.md view
@@ -1,3 +1,7 @@+## 0.2.4 - 2018-08-28+ * Use forward slashes on Windows too, see [issue #21](https://github.com/snoyberg/tar-conduit/issues/21)+ * Addition of `extractTarballLenient`+ ## 0.2.3.1 - 2018-06-06  * Fixed drops associated payload for unsupported headers ((https://github.com/snoyberg/tar-conduit/issues/17)) ([PR 18](https://github.com/snoyberg/tar-conduit/pull/18))  * Dropped support for GHC 7/Stack LTS-2, LTS-3, LTS-6
src/Data/Conduit/Tar.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE BangPatterns        #-} {-# LANGUAGE CPP                 #-} {-# LANGUAGE OverloadedStrings   #-}@@ -14,6 +15,7 @@     , untarWithFinalizers     , restoreFile     , restoreFileInto+    , restoreFileWithErrors     -- ** Operate on Chunks     , untarChunks     , withEntry@@ -29,12 +31,13 @@     , createTarball     , writeTarball     , extractTarball+    , extractTarballLenient       -- * Types     , module Data.Conduit.Tar.Types     ) where  import           Conduit                  as C-import           Control.Exception        (assert)+import           Control.Exception        (assert, SomeException) import           Control.Monad            (unless, void) import           Data.Bits import           Data.ByteString          (ByteString)@@ -399,6 +402,19 @@     liftIO finilizers  +-- | Same as `untarWithFinalizers`, but will also produce a list of any exceptions that might have+-- occured during restoration process.+--+-- @since 0.2.4+untarWithExceptions ::+       (MonadThrow m, MonadIO m)+    => (FileInfo -> ConduitM ByteString (IO (FileInfo, [SomeException])) m ())+    -> ConduitM ByteString c m [(FileInfo, [SomeException])]+untarWithExceptions inner = do+    finalizers <- untar inner .| C.foldMapC (fmap pure)+    filter (not . null . snd) <$> liftIO finalizers++ -------------------------------------------------------------------------------- -- Create a tar file ----------------------------------------------------------- --------------------------------------------------------------------------------@@ -829,8 +845,10 @@     runConduitRes $ yieldMany dirs .| void tarFilePath .| sinkHandle tarHandle  +-- always use forward slash, see+-- https://github.com/snoyberg/tar-conduit/issues/21 pathSeparatorS :: ByteString-pathSeparatorS = S8.singleton pathSeparator+pathSeparatorS = "/" -- S8.singleton pathSeparator   fileInfoFromHeader :: Header -> FileInfo@@ -863,9 +881,55 @@     runConduitRes $ sourceFileBS tarfp .| untarWithFinalizers (restoreFileInto cd)  +prependDirectory :: FilePath -> FileInfo -> FileInfo+prependDirectory cd fi =+    fi {filePath = encodeFilePath (cd </> makeRelative "/" (getFileInfoPath fi))}++ -- | Restore all files into a folder. Absolute file paths will be turned into -- relative to the supplied folder. restoreFileInto :: MonadResource m =>                    FilePath -> FileInfo -> ConduitM ByteString (IO ()) m ()-restoreFileInto cd fi =-    restoreFile fi {filePath = encodeFilePath (cd </> makeRelative "/" (getFileInfoPath fi))}+restoreFileInto cd = restoreFile . prependDirectory cd++-- | Restore all files into a folder. Absolute file paths will be turned into relative to the+-- supplied folder. Yields a list with exceptions instead of throwing them.+restoreFileIntoLenient :: MonadResource m =>+    FilePath -> FileInfo -> ConduitM ByteString (IO (FileInfo, [SomeException])) m ()+restoreFileIntoLenient cd = restoreFileWithErrors True . prependDirectory cd+++-- | Same as `extractTarball`, but ignores possible extraction errors. It can still throw a+-- `TarException` if the tarball is corrupt or malformed.+--+-- @since 0.2.4+extractTarballLenient :: FilePath -- ^ Filename for the tarball+                   -> Maybe FilePath -- ^ Folder where tarball should be extract+                   -- to. Default is the current path+                   -> IO [(FileInfo, [SomeException])]+extractTarballLenient tarfp mcd = do+    cd <- maybe getCurrentDirectory return mcd+    createDirectoryIfMissing True cd+    runConduitRes $+        sourceFileBS tarfp .| untarWithExceptions (restoreFileIntoLenient cd)++++-- | Restore files onto the file system. Produces actions that will set the modification time on the+-- directories, which can be executed after the pipeline has finished and all files have been+-- written to disk.+restoreFile :: (MonadResource m) =>+               FileInfo -> ConduitM S8.ByteString (IO ()) m ()+restoreFile fi = restoreFileWithErrors False fi .| mapC void+++-- | Restore files onto the file system, much in the same way `restoreFile` does it, except with+-- ability to ignore restoring problematic files and report errors that occured as a list of+-- exceptions, which will be returned as a list when finilizer executed. If a list is empty, it+-- means, that no errors occured and a file only had a finilizer associated with it.+restoreFileWithErrors ::+       (MonadResource m)+    => Bool+    -> FileInfo+    -> ConduitM S8.ByteString (IO (FileInfo, [SomeException])) m ()+restoreFileWithErrors = restoreFileInternal
src/Data/Conduit/Tar/Types.hs view
@@ -136,6 +136,7 @@     | InvalidHeader     !FileOffset     | BadChecksum       !FileOffset     | FileTypeError     !FileOffset !Char !String+    | UnsupportedType   !FileType     deriving (Show, Typeable) instance Exception TarException 
src/Data/Conduit/Tar/Unix.hs view
@@ -4,14 +4,15 @@ {-# LANGUAGE ScopedTypeVariables #-} module Data.Conduit.Tar.Unix     ( getFileInfo-    , restoreFile+    , restoreFileInternal     ) where -import           Conduit+import           Conduit                       hiding (throwM) import           Control.Exception.Safe-import           Control.Monad                 (void, when)+import           Control.Monad                 (void, when, unless) import           Data.Bits import qualified Data.ByteString.Char8         as S8+import           Data.Either import           Data.Conduit.Tar.Types import           Foreign.C.Types               (CTime (..)) import qualified System.Directory              as Dir@@ -53,41 +54,52 @@         , fileModTime   = Posix.modificationTime fs         } ---- | Restore files onto the file system. Produces actions that will set the modification time on the--- directories, which can be executed after the pipeline has finished and all files have been--- written to disk.-restoreFile :: (MonadResource m) =>-               FileInfo -> ConduitM S8.ByteString (IO ()) m ()-restoreFile FileInfo {..} = do+-- | See 'Data.Conduit.Tar.restoreFileWithErrors' for documentation+restoreFileInternal ::+       (MonadResource m)+    => Bool+    -> FileInfo+    -> ConduitM S8.ByteString (IO (FileInfo, [SomeException])) m ()+restoreFileInternal lenient fi@FileInfo {..} = do     let fpStr = decodeFilePath filePath+        tryAnyCond action = if lenient then tryAny action else fmap Right action         restorePermissions = do-            void $ tryAny $ Posix.setOwnerAndGroup fpStr fileUserId fileGroupId-            void $ tryAny $ Posix.setFileMode fpStr fileMode+            eExc1 <- tryAnyCond $ Posix.setOwnerAndGroup fpStr fileUserId fileGroupId+            eExc2 <- tryAnyCond $ Posix.setFileMode fpStr fileMode+            return $! fst $ partitionEithers [eExc1, eExc2]+        -- | Catch all exceptions, but only if lenient is set to True     case fileType of         FTDirectory -> do-            liftIO $ do+            excs <- liftIO $ do                 Dir.createDirectoryIfMissing False fpStr                 restorePermissions-            yield $-                (Dir.doesDirectoryExist fpStr >>=-                 (`when` Posix.setFileTimes fpStr fileModTime fileModTime))-        FTSymbolicLink link ->-            liftIO $ do+            yield $ do+                eExc <- tryAnyCond (Dir.doesDirectoryExist fpStr >>=+                                    (`when` Posix.setFileTimes fpStr fileModTime fileModTime))+                return (fi, either ((excs ++) . pure) (const excs) eExc)+        FTSymbolicLink link -> do+            excs <- liftIO $ do                 -- Try to unlink any existing file/symlink-                _ <- tryAny $ Posix.removeLink fpStr+                void $ tryAny $ Posix.removeLink fpStr                 Posix.createSymbolicLink (decodeFilePath link) fpStr-                _ <- tryAny $ Posix.setSymbolicLinkOwnerAndGroup fpStr fileUserId fileGroupId-                -- Try best effort in setting symbolic link modification time.+                eExc1 <- tryAnyCond $ Posix.setSymbolicLinkOwnerAndGroup fpStr fileUserId fileGroupId #if MIN_VERSION_unix(2,7,0)+                -- Try best effort in setting symbolic link modification time.                 let CTime epochInt32 = fileModTime                     unixModTime = fromInteger (fromIntegral epochInt32)-                void $ tryAny $ Posix.setSymbolicLinkTimesHiRes fpStr unixModTime unixModTime+                eExc2 <- tryAny $ Posix.setSymbolicLinkTimesHiRes fpStr unixModTime unixModTime #endif+                return $ fst $ partitionEithers [eExc1, eExc2]+            unless (null excs) $ yield (return (fi, excs))         FTNormal -> do             sinkFile fpStr-            liftIO $ do-                restorePermissions-                Posix.setFileTimes fpStr fileModTime fileModTime-        ty -> error $ "Unsupported tar entry type: " ++ show ty+            excs <- liftIO $ do+                excs <- restorePermissions+                eExc <- tryAnyCond $ Posix.setFileTimes fpStr fileModTime fileModTime+                return (either ((excs ++) . pure) (const excs) eExc)+            unless (null excs) $ yield (return (fi, excs))+        ty -> do+            let exc = UnsupportedType ty+            unless lenient $ liftIO $ throwM exc+            yield $ return (fi, [toException exc]) 
src/Data/Conduit/Tar/Windows.hs view
@@ -2,14 +2,16 @@ {-# LANGUAGE RecordWildCards   #-} module Data.Conduit.Tar.Windows     ( getFileInfo-    , restoreFile+    , restoreFileInternal     ) where  import           Conduit-import           Control.Monad            (when)+import           Control.Monad            (when, unless)+import           Control.Exception.Safe   (tryAny, SomeException, toException) import           Data.Bits import qualified Data.ByteString.Char8    as S8 import           Data.Conduit.Tar.Types+import           Data.Either              (partitionEithers) import           Data.Time.Clock.POSIX import           Foreign.C.Types          (CTime (..)) import qualified System.Directory         as Dir@@ -38,24 +40,38 @@         , fileModTime   = Posix.modificationTime fs         } --- | Restore files onto the file system. Produces actions that will set the modification time on the--- directories, which can be executed after the pipeline has finished and all files have been--- written to disk.-restoreFile :: (MonadResource m) =>-               FileInfo -> ConduitM S8.ByteString (IO ()) m ()-restoreFile FileInfo {..} = do+++-- | See 'Data.Conduit.Tar.restoreFileWithErrors' for documentation+restoreFileInternal ::+       (MonadResource m)+    => Bool+    -> FileInfo+    -> ConduitM S8.ByteString (IO (FileInfo, [SomeException])) m ()+restoreFileInternal lenient fi@FileInfo {..} = do     let fpStr = decodeFilePath filePath+        tryAnyCond action = if lenient then tryAny action else fmap Right action         CTime modTimeEpoch = fileModTime         modTime = posixSecondsToUTCTime (fromIntegral modTimeEpoch)+        restoreTimeAndMode = do+            eExc1 <- tryAnyCond $ Posix.setFileMode fpStr fileMode+            eExc2 <- tryAnyCond $ Dir.setModificationTime fpStr modTime+            return $! fst $ partitionEithers [eExc1, eExc2]     case fileType of         FTDirectory -> do-            liftIO $ Dir.createDirectoryIfMissing False fpStr-            yield $-                (Dir.doesDirectoryExist fpStr >>=-                 (`when` Dir.setModificationTime fpStr modTime))-        FTNormal -> sinkFile fpStr-        ty -> error $ "Unsupported tar entry type: " ++ show ty-    liftIO $ do-        Dir.setModificationTime fpStr modTime-        Posix.setFileMode fpStr fileMode+            excs <- liftIO $ do+                Dir.createDirectoryIfMissing False fpStr+                restoreTimeAndMode+            yield $ do+                eExc <- tryAnyCond (Dir.doesDirectoryExist fpStr >>=+                                    (`when` Dir.setModificationTime fpStr modTime))+                return (fi, either ((excs ++) . pure) (const excs) eExc)+        FTNormal -> do+            sinkFile fpStr+            excs <- liftIO $ restoreTimeAndMode+            unless (null excs) $ yield $ return (fi, excs)+        ty -> do+            let exc = UnsupportedType ty+            unless lenient $ liftIO $ throwM exc+            yield $ return (fi, [toException exc]) 
tar-conduit.cabal view
@@ -1,5 +1,5 @@ name:                tar-conduit-version:             0.2.3.1+version:             0.2.4 synopsis:            Extract and create tar files using conduit for streaming description:         Please see README.md. This is just filler to avoid warnings. homepage:            https://github.com/snoyberg/tar-conduit#readme@@ -21,21 +21,20 @@                      , bytestring                      , conduit                      , conduit-combinators >= 1.0.8.1+                     , directory                      , filepath+                     , safe-exceptions                      , text   default-language:    Haskell2010   ghc-options:         -Wall   if os(windows)       other-modules: Data.Conduit.Tar.Windows-      build-depends: directory-                   , time+      build-depends: time                    , unix-compat       cpp-options:   -DWINDOWS   else       other-modules: Data.Conduit.Tar.Unix-      build-depends: directory-                   , safe-exceptions-                   , unix+      build-depends: unix  test-suite tests   type:                exitcode-stdio-1.0
tests/Spec.hs view
@@ -13,10 +13,12 @@ import           Data.Conduit.List import           Data.Conduit.Tar import           Data.Int+import           Data.List             (sortOn) import           Data.Monoid import           Prelude               as P import           System.Directory-import           System.FilePath+import qualified System.FilePath as Host+import qualified System.FilePath.Posix as Posix -- always use forward slashes import           System.IO import           System.IO             (BufferMode (LineBuffering),                                         hSetBuffering, stdout)@@ -58,8 +60,8 @@                     finally                         (setCurrentDirectory outDir >> createTarball fpOut testPaths)                         (setCurrentDirectory curDir)-                    tb1 <- readTarball fpIn-                    tb2 <- readTarball fpOut+                    tb1 <- readTarballSorted fpIn+                    tb2 <- readTarballSorted fpOut                     P.length tb1 `shouldBe` P.length tb2                     zipWithM_ shouldBe (fmap fst tb2) (fmap fst tb1)                     zipWithM_ shouldBe (fmap snd tb2) (fmap snd tb1)@@ -111,7 +113,7 @@ instance Arbitrary GnuTarFile where     arbitrary = do         filePathLen <- (`mod` 4090) <$> arbitrary-        filePath <- ("test-" <>) <$> asciiGen filePathLen+        filePath <- ("test-" <>) . S8.filter (/= ('\\')) <$> asciiGen filePathLen         NonNegative fileUserId64 <- arbitrary         let fileUserId = fromIntegral (fileUserId64 :: Int64)         NonNegative fileGroupId64 <- arbitrary@@ -171,7 +173,7 @@         emptyFileInfoExpectation defFileInfo     it "long file name <255" $ do         emptyFileInfoExpectation $-            defFileInfo {filePath = S8.pack (P.replicate 99 'f' </> P.replicate 99 'o')}+            defFileInfo {filePath = S8.pack (P.replicate 99 'f' Posix.</> P.replicate 99 'o')}   gnutarSpec :: Spec@@ -180,19 +182,25 @@         emptyFileInfoExpectation $             defFileInfo             { filePath =-                  S8.pack (P.replicate 100 'f' </> P.replicate 100 'o' </> P.replicate 99 'b')+                  S8.pack (P.replicate 100 'f' Posix.</>+                           P.replicate 100 'o' Posix.</>+                           P.replicate 99 'b')             }     it "LongLink - multiple files with long file names" $ do         fileInfoExpectation             [ ( defFileInfo                 { filePath =-                      S8.pack (P.replicate 100 'f' </> P.replicate 100 'o' </> P.replicate 99 'b')+                      S8.pack (P.replicate 100 'f' Posix.</>+                               P.replicate 100 'o' Posix.</>+                               P.replicate 99 'b')                 , fileSize = 10                 }               , "1234567890")             , ( defFileInfo                 { filePath =-                      S8.pack (P.replicate 1000 'g' </> P.replicate 1000 'o' </> P.replicate 99 'b')+                      S8.pack (P.replicate 1000 'g' Posix.</>+                               P.replicate 1000 'o' Posix.</>+                               P.replicate 99 'b')                 , fileSize = 11                 }               , "abcxdefghij")@@ -221,9 +229,9 @@ withTempTarFiles base =     bracket         (do tmpDir <- getTemporaryDirectory-            (fp1, h1) <- openBinaryTempFile tmpDir (addExtension base ".tar")-            let outPath = dropExtension fp1 ++ ".out"-            return (fp1, h1, outPath, addExtension outPath ".tar")+            (fp1, h1) <- openBinaryTempFile tmpDir (Host.addExtension base ".tar")+            let outPath = Host.dropExtension fp1 ++ ".out"+            return (fp1, h1, outPath, Host.addExtension outPath ".tar")         )         (\(fp, h, dirOut, fpOut) -> do              hClose h@@ -232,10 +240,14 @@              doesFileExist fpOut >>= (`when` removeFile fpOut)         ) --readTarball+-- | Collects all of the files and direcotries from the tarball. Then all of them get sorted, since+-- apparently Windows has no guaranteed order the files within a directory will be listed in upon a+-- tarball creation.+readTarballSorted   :: FilePath -> IO [(FileInfo, Maybe ByteString)]-readTarball fp = runConduitRes $ sourceFileBS fp .| untar grabBoth .| sinkList+readTarballSorted fp =+  sortOn (filePath . fst) <$>+  (runConduitRes $ sourceFileBS fp .| untar grabBoth .| sinkList)  readGzipTarball   :: FilePath