@@ -24,6 +24,10 @@ module Mismi.S3.Commands (
2424 , uploadOrFail
2525 , uploadWithMode
2626 , uploadWithModeOrFail
27+ , uploadRecursive
28+ , uploadRecursiveOrFail
29+ , uploadRecursiveWithMode
30+ , uploadRecursiveWithModeOrFail
2731 , multipartUpload
2832 , uploadSingle
2933 , write
@@ -70,6 +74,7 @@ import Control.Exception (ioError)
7074import qualified Control.Exception as CE
7175import Control.Lens ((.~) , (^.) , to , view )
7276import Control.Monad.Catch (throwM , onException )
77+ import Control.Monad.Extra (concatMapM )
7378import Control.Monad.Trans.Class (lift )
7479import Control.Monad.Trans.Resource (ResourceT , allocate , runResourceT )
7580import Control.Monad.Reader (ask )
@@ -114,10 +119,10 @@ import P
114119import System.IO (IO , IOMode (.. ), SeekMode (.. ))
115120import System.IO (hFileSize , hSetFileSize , withFile )
116121import System.IO.Error (IOError )
117- import System.Directory (createDirectoryIfMissing , doesFileExist )
122+ import System.Directory (createDirectoryIfMissing , doesFileExist , getDirectoryContents )
118123import System.FilePath (FilePath , (</>) , takeDirectory )
119124import System.Posix.IO (OpenMode (.. ), openFd , closeFd , fdSeek , defaultFileFlags )
120- import System.Posix.Files (getFileStatus , isDirectory )
125+ import System.Posix.Files (getFileStatus , isDirectory , isRegularFile )
121126import qualified "unix-bytestring" System.Posix.IO.ByteString as UBS
122127
123128import System.Timeout.Lifted (timeout )
@@ -365,6 +370,14 @@ upload :: FilePath -> Address -> EitherT UploadError AWS ()
365370upload =
366371 uploadWithMode Fail
367372
373+ uploadRecursive :: FilePath -> Address -> EitherT UploadError AWS ()
374+ uploadRecursive =
375+ uploadRecursiveWithMode Fail
376+
377+ uploadRecursiveOrFail :: FilePath -> Address -> AWS ()
378+ uploadRecursiveOrFail f a =
379+ eitherT hoistUploadError pure $ uploadRecursive f a
380+
368381uploadOrFail :: FilePath -> Address -> AWS ()
369382uploadOrFail f a =
370383 eitherT hoistUploadError pure $ upload f a
@@ -373,13 +386,19 @@ uploadWithModeOrFail :: WriteMode -> FilePath -> Address -> AWS ()
373386uploadWithModeOrFail w f a =
374387 eitherT hoistUploadError pure $ uploadWithMode w f a
375388
389+ uploadRecursiveWithModeOrFail :: WriteMode -> FilePath -> Address -> AWS ()
390+ uploadRecursiveWithModeOrFail w f a =
391+ eitherT hoistUploadError pure $ uploadRecursiveWithMode w f a
392+
376393hoistUploadError :: UploadError -> AWS ()
377394hoistUploadError e =
378395 case e of
379396 UploadSourceMissing f ->
380397 throwM $ SourceFileMissing f
381398 UploadDestinationExists a ->
382399 throwM $ DestinationAlreadyExists a
400+ UploadSourceNotDirectory f ->
401+ throwM $ SourceNotDirectory f
383402 MultipartUploadError (WorkerError a) ->
384403 throwM $ a
385404 MultipartUploadError (BlowUpError a) ->
@@ -455,6 +474,45 @@ multipartUploadWorker e mpu file a (o, c, i) =
455474 pure $! Right $! PartResponse i m
456475
457476
477+ uploadRecursiveWithMode :: WriteMode -> FilePath -> Address -> EitherT UploadError AWS ()
478+ uploadRecursiveWithMode m src (Address buck ky) = do
479+ es <- tryIO $ getFileStatus src
480+ case es of
481+ Left _ -> left $ UploadSourceMissing src
482+ Right st -> unless (isDirectory st) . left $ UploadSourceNotDirectory src
483+ files <- liftIO $ listRecursivelyLocal src
484+ let prefixLen = L. length (src </> " a" ) - 1
485+ outputAddrs = fmap (\ fp -> Address buck (ky // Key (T. pack $ L. drop prefixLen fp))) files
486+ mapM_ (uncurry (uploadWithMode m)) $ L. zip files outputAddrs
487+
488+ -- | Like `listRecursively` but for the local filesystem.
489+ listRecursivelyLocal :: MonadIO m => FilePath -> m [FilePath ]
490+ listRecursivelyLocal topdir = do
491+ entries <- liftIO $ listDirectory topdir
492+ (dirs, files) <- liftIO . partitionDirsFiles $ fmap (topdir </> ) entries
493+ others <- concatMapM listRecursivelyLocal dirs
494+ pure $ files <> others
495+
496+
497+ -- Not available with ghc 7.10 so copy it here.
498+ listDirectory :: FilePath -> IO [FilePath ]
499+ listDirectory path =
500+ filter f <$> getDirectoryContents path
501+ where
502+ f filename =
503+ filename /= " ." && filename /= " .."
504+
505+ partitionDirsFiles :: MonadIO m => [FilePath ] -> m ([FilePath ], [FilePath ])
506+ partitionDirsFiles =
507+ pworker ([] , [] )
508+ where
509+ pworker (dirs, files) [] = pure (dirs, files)
510+ pworker (dirs, files) (x: xs) = do
511+ xstat <- liftIO $ getFileStatus x
512+ pworker
513+ (if isDirectory xstat then x : dirs else dirs, if isRegularFile xstat then x : files else files)
514+ xs
515+
458516write :: Address -> Text -> AWS WriteResult
459517write =
460518 writeWithMode Fail
@@ -645,9 +703,6 @@ downloadRecursiveWithMode mode src dest = do
645703 addrs <- lift $ listRecursively src
646704 mapM_ drWorker addrs
647705 where
648- tryIO :: MonadIO m => IO a -> m (Either IOError a )
649- tryIO = liftIO . CE. try
650-
651706 drWorker :: Address -> EitherT DownloadError AWS ()
652707 drWorker addr = do
653708 fpdest <- hoistMaybe (DownloadInvariant addr src) $
@@ -750,3 +805,6 @@ worker input output mode env f = runEitherT . runAWST env SyncAws $ do
750805 (liftCopy $ copyWithMode Overwrite f out)
751806 (ifM (lift $ exists out) (right () ) cp)
752807 mode
808+
809+ tryIO :: MonadIO m => IO a -> m (Either IOError a )
810+ tryIO = liftIO . CE. try
0 commit comments