ftp-client 0.4.0.0 → 0.4.0.1
raw patch · 2 files changed
+52/−7 lines, 2 filesdep +containersdep ~ftp-clientPVP: minor bump suggested
API additions: PVP suggests at least a minor version bump
Dependencies added: containers
Dependency ranges changed: ftp-client
API changes (from Hackage documentation)
+ Network.FTP.Client: Mlsd :: String -> FTPCommand
+ Network.FTP.Client: MlsdResponse :: String -> Map String String -> MlsdResponse
+ Network.FTP.Client: [mrFacts] :: MlsdResponse -> Map String String
+ Network.FTP.Client: [mrFilename] :: MlsdResponse -> String
+ Network.FTP.Client: data MlsdResponse
+ Network.FTP.Client: instance GHC.Show.Show Network.FTP.Client.MlsdResponse
+ Network.FTP.Client: mlsd :: (MonadIO m, MonadMask m) => Handle -> String -> m [MlsdResponse]
+ Network.FTP.Client: parseMlsdLine :: ByteString -> MlsdResponse
Files
- ftp-client.cabal +4/−3
- src/Network/FTP/Client.hs +48/−4
ftp-client.cabal view
@@ -1,5 +1,5 @@ name: ftp-client-version: 0.4.0.0+version: 0.4.0.1 cabal-version: >=1.10 build-type: Simple license: PublicDomain@@ -28,7 +28,8 @@ attoparsec >=0.10 && <0.14, connection ==0.2.*, transformers >=0.5.2.0 && <0.6,- exceptions >=0.8.3 && <0.9+ exceptions >=0.8.3 && <0.9,+ containers >=0.5.7.1 && <0.6 default-language: Haskell2010 default-extensions: OverloadedStrings hs-source-dirs: src@@ -38,7 +39,7 @@ main-is: Spec.hs build-depends: base >=4.9.1.0 && <4.10,- ftp-client >=0.4.0.0 && <0.5+ ftp-client >=0.4.0.1 && <0.5 default-language: Haskell2010 hs-source-dirs: test ghc-options: -threaded -rtsopts -with-rtsopts=-N
src/Network/FTP/Client.hs view
@@ -25,10 +25,12 @@ retr, list, stor,+ mlsd, -- * Types FTPCommand(..), FTPResponse(..), ResponseStatus(..),+ MlsdResponse(..), RTypeCode(..), PortActivity(..), ProtType(..),@@ -44,7 +46,8 @@ getMultiLineResp, sendCommandLine, createSendDataCommand,- createTLSSendDataCommand+ createTLSSendDataCommand,+ parseMlsdLine ) where import qualified Data.ByteString.Char8 as C@@ -66,7 +69,12 @@ import Data.ByteString.Lazy.Internal (defaultChunkSize) import Data.Functor ((<$>)) import Control.Applicative ((<*>))+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Control.Arrow +import Debug.Trace+ debugging :: Bool debugging = False @@ -144,6 +152,7 @@ | Rmd String | Pbsz Int | Prot ProtType+ | Mlsd String | Cwd String | Cdup | Ccc@@ -184,6 +193,7 @@ serializeCommand (Pbsz buf) = "PBSZ " <> show buf serializeCommand (Prot P) = "PROT P" serializeCommand (Prot C) = "PROT C"+serializeCommand (Mlsd path) = "MLSD " <> path serializeCommand (Cwd dir) = "CWD " <> dir serializeCommand Cdup = "CDUP" serializeCommand Ccc = "CCC"@@ -409,7 +419,7 @@ getAllLineResp :: (MonadIO m, MonadCatch m) => Handle -> m ByteString getAllLineResp h = getAllLineResp' h [] where- getAllLineResp' h ret = (do+ getAllLineResp' h ret = ( do line <- liftIO $ getLineResp h getAllLineResp' h (ret <> [line])) `M.catchIOError` (\_ -> return $ C.intercalate "\n" ret)@@ -418,7 +428,7 @@ recvAll :: (MonadIO m, MonadCatch m) => Handle -> m ByteString recvAll h = recvAll' "" where- recvAll' bs = (do+ recvAll' bs = ( do chunk <- liftIO $ recv h defaultChunkSize recvAll' $ bs <> chunk) `M.catchIOError` (\_ -> return bs)@@ -638,7 +648,7 @@ retr h path = withDataCommandSecurity h Passive [RType TI, Retr path] recvAll list :: (MonadIO m, MonadMask m) => Handle -> [String] -> m ByteString-list h args = withDataCommandSecurity h Passive [RType TA, List args] recvAll+list h args = withDataCommandSecurity h Passive [RType TA, List args] getAllLineResp stor :: (MonadIO m, MonadMask m)@@ -650,3 +660,37 @@ stor h loc dat rtype = withDataCommandSecurity h Passive [RType rtype, Stor loc] $ sendType rtype dat++data MlsdResponse = MlsdResponse {+ mrFilename :: String,+ mrFacts :: Map String String+} deriving (Show)++splitApart :: Char -> ByteString -> (ByteString, ByteString)+splitApart on s =+ let (x0, x1) = C.break (== on) s+ in (x0, C.drop 1 x1)++parseMlsdLine :: ByteString -> MlsdResponse+parseMlsdLine line =+ let (factLine, filename) = splitApart ' ' line+ bFacts = splitApart '=' <$> C.split ';' factLine+ facts+ = Map.fromList+ $ filter (not . null . fst)+ $ join (***) C.unpack <$> bFacts+ in MlsdResponse (C.unpack filename) facts++getMlsdResponse :: (MonadIO m, MonadCatch m) => Handle -> m [MlsdResponse]+getMlsdResponse h = getMlsdResponse' h []+ where+ getMlsdResponse' h ret = ( do+ line <- liftIO $ getLineResp h+ getMlsdResponse' h $+ if C.null line+ then ret+ else (parseMlsdLine line):ret+ ) `M.catchIOError` (\_ -> return ret)++mlsd :: (MonadIO m, MonadMask m) => Handle -> String -> m [MlsdResponse]+mlsd h path = withDataCommandSecurity h Passive [RType TA, Mlsd path] getMlsdResponse