factored out downloadConduit from download
useful when an API provides a Request to download
This commit is contained in:
parent
b82ecf7076
commit
4af55c42bf
1 changed files with 68 additions and 57 deletions
125
Utility/Url.hs
125
Utility/Url.hs
|
@ -29,6 +29,7 @@ module Utility.Url (
|
||||||
assumeUrlExists,
|
assumeUrlExists,
|
||||||
download,
|
download,
|
||||||
downloadQuiet,
|
downloadQuiet,
|
||||||
|
downloadConduit,
|
||||||
sinkResponseFile,
|
sinkResponseFile,
|
||||||
downloadPartial,
|
downloadPartial,
|
||||||
parseURIRelaxed,
|
parseURIRelaxed,
|
||||||
|
@ -335,7 +336,8 @@ download' noerror meterupdate url file uo =
|
||||||
case (urlDownloader uo, parseUrlRequest (show u)) of
|
case (urlDownloader uo, parseUrlRequest (show u)) of
|
||||||
(DownloadWithConduit (DownloadWithCurlRestricted r), Just req) -> catchJust
|
(DownloadWithConduit (DownloadWithCurlRestricted r), Just req) -> catchJust
|
||||||
(matchStatusCodeException (== found302))
|
(matchStatusCodeException (== found302))
|
||||||
(downloadconduit req)
|
((downloadConduit meterupdate req file uo >> return True)
|
||||||
|
`catchNonAsync` (dlfailed . show))
|
||||||
(followredir r)
|
(followredir r)
|
||||||
(DownloadWithConduit (DownloadWithCurlRestricted r), Nothing)
|
(DownloadWithConduit (DownloadWithCurlRestricted r), Nothing)
|
||||||
| isfileurl u -> downloadfile u
|
| isfileurl u -> downloadfile u
|
||||||
|
@ -354,58 +356,6 @@ download' noerror meterupdate url file uo =
|
||||||
|
|
||||||
ftpport = 21
|
ftpport = 21
|
||||||
|
|
||||||
downloadconduit req = catchMaybeIO (getFileSize file) >>= \case
|
|
||||||
Just sz | sz > 0 -> resumeconduit req' sz
|
|
||||||
_ -> runResourceT $ do
|
|
||||||
liftIO $ debugM "url" (show req')
|
|
||||||
resp <- http req' (httpManager uo)
|
|
||||||
if responseStatus resp == ok200
|
|
||||||
then store zeroBytesProcessed WriteMode resp
|
|
||||||
else showrespfailure resp
|
|
||||||
where
|
|
||||||
req' = applyRequest uo $ req
|
|
||||||
-- Override http-client's default decompression of gzip
|
|
||||||
-- compressed files. We want the unmodified file content.
|
|
||||||
{ requestHeaders = (hAcceptEncoding, "identity") :
|
|
||||||
filter ((/= hAcceptEncoding) . fst)
|
|
||||||
(requestHeaders req)
|
|
||||||
, decompress = const False
|
|
||||||
}
|
|
||||||
|
|
||||||
alreadydownloaded sz s h = s == requestedRangeNotSatisfiable416
|
|
||||||
&& case lookup hContentRange h of
|
|
||||||
-- This could be improved by fixing
|
|
||||||
-- https://github.com/aristidb/http-types/issues/87
|
|
||||||
Just crh -> crh == B8.fromString ("bytes */" ++ show sz)
|
|
||||||
-- Some http servers send no Content-Range header when
|
|
||||||
-- the range extends beyond the end of the file.
|
|
||||||
-- There is no way to distinguish between the file
|
|
||||||
-- being the same size on the http server, vs
|
|
||||||
-- it being shorter than the file we already have.
|
|
||||||
-- So assume we have the whole content of the file
|
|
||||||
-- already, the same as wget and curl do.
|
|
||||||
Nothing -> True
|
|
||||||
|
|
||||||
-- Resume download from where a previous download was interrupted,
|
|
||||||
-- when supported by the http server. The server may also opt to
|
|
||||||
-- send the whole file rather than resuming.
|
|
||||||
resumeconduit req sz = catchJust
|
|
||||||
(matchStatusCodeHeadersException (alreadydownloaded sz))
|
|
||||||
dl
|
|
||||||
(const $ return True)
|
|
||||||
where
|
|
||||||
dl = runResourceT $ do
|
|
||||||
let req' = req { requestHeaders = resumeFromHeader sz : requestHeaders req }
|
|
||||||
liftIO $ debugM "url" (show req')
|
|
||||||
resp <- http req' (httpManager uo)
|
|
||||||
if responseStatus resp == partialContent206
|
|
||||||
then store (BytesProcessed sz) AppendMode resp
|
|
||||||
else if responseStatus resp == ok200
|
|
||||||
then store zeroBytesProcessed WriteMode resp
|
|
||||||
else showrespfailure resp
|
|
||||||
|
|
||||||
showrespfailure = liftIO . dlfailed . B8.toString
|
|
||||||
. statusMessage . responseStatus
|
|
||||||
showhttpexception he = do
|
showhttpexception he = do
|
||||||
let msg = case he of
|
let msg = case he of
|
||||||
HttpExceptionRequest _ (StatusCodeException r _) ->
|
HttpExceptionRequest _ (StatusCodeException r _) ->
|
||||||
|
@ -417,6 +367,7 @@ download' noerror meterupdate url file uo =
|
||||||
HttpExceptionRequest _ other -> show other
|
HttpExceptionRequest _ other -> show other
|
||||||
_ -> show he
|
_ -> show he
|
||||||
dlfailed msg
|
dlfailed msg
|
||||||
|
|
||||||
dlfailed msg
|
dlfailed msg
|
||||||
| noerror = return False
|
| noerror = return False
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
|
@ -424,10 +375,6 @@ download' noerror meterupdate url file uo =
|
||||||
hFlush stderr
|
hFlush stderr
|
||||||
return False
|
return False
|
||||||
|
|
||||||
store initialp mode resp = do
|
|
||||||
sinkResponseFile meterupdate initialp file mode resp
|
|
||||||
return True
|
|
||||||
|
|
||||||
basecurlparams = curlParams uo
|
basecurlparams = curlParams uo
|
||||||
[ if noerror
|
[ if noerror
|
||||||
then Param "-S"
|
then Param "-S"
|
||||||
|
@ -453,6 +400,8 @@ download' noerror meterupdate url file uo =
|
||||||
L.writeFile file
|
L.writeFile file
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
-- Conduit does not support ftp, so will throw an exception on a
|
||||||
|
-- redirect to a ftp url; fall back to curl.
|
||||||
followredir r ex@(HttpExceptionRequest _ (StatusCodeException resp _)) =
|
followredir r ex@(HttpExceptionRequest _ (StatusCodeException resp _)) =
|
||||||
case headMaybe $ map decodeBS $ getResponseHeader hLocation resp of
|
case headMaybe $ map decodeBS $ getResponseHeader hLocation resp of
|
||||||
Just url' -> case parseURIRelaxed url' of
|
Just url' -> case parseURIRelaxed url' of
|
||||||
|
@ -463,6 +412,68 @@ download' noerror meterupdate url file uo =
|
||||||
Nothing -> throwIO ex
|
Nothing -> throwIO ex
|
||||||
followredir _ ex = throwIO ex
|
followredir _ ex = throwIO ex
|
||||||
|
|
||||||
|
{- Download a perhaps large file using conduit, with auto-resume
|
||||||
|
- of incomplete downloads.
|
||||||
|
-
|
||||||
|
- Does not catch exceptions.
|
||||||
|
-}
|
||||||
|
downloadConduit :: MeterUpdate -> Request -> FilePath -> UrlOptions -> IO ()
|
||||||
|
downloadConduit meterupdate req file uo =
|
||||||
|
catchMaybeIO (getFileSize file) >>= \case
|
||||||
|
Just sz | sz > 0 -> resumedownload sz
|
||||||
|
_ -> runResourceT $ do
|
||||||
|
liftIO $ debugM "url" (show req')
|
||||||
|
resp <- http req' (httpManager uo)
|
||||||
|
if responseStatus resp == ok200
|
||||||
|
then store zeroBytesProcessed WriteMode resp
|
||||||
|
else respfailure resp
|
||||||
|
where
|
||||||
|
req' = applyRequest uo $ req
|
||||||
|
-- Override http-client's default decompression of gzip
|
||||||
|
-- compressed files. We want the unmodified file content.
|
||||||
|
{ requestHeaders = (hAcceptEncoding, "identity") :
|
||||||
|
filter ((/= hAcceptEncoding) . fst)
|
||||||
|
(requestHeaders req)
|
||||||
|
, decompress = const False
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Resume download from where a previous download was interrupted,
|
||||||
|
-- when supported by the http server. The server may also opt to
|
||||||
|
-- send the whole file rather than resuming.
|
||||||
|
resumedownload sz = catchJust
|
||||||
|
(matchStatusCodeHeadersException (alreadydownloaded sz))
|
||||||
|
dl
|
||||||
|
(const noop)
|
||||||
|
where
|
||||||
|
dl = runResourceT $ do
|
||||||
|
let req'' = req' { requestHeaders = resumeFromHeader sz : requestHeaders req }
|
||||||
|
liftIO $ debugM "url" (show req'')
|
||||||
|
resp <- http req'' (httpManager uo)
|
||||||
|
if responseStatus resp == partialContent206
|
||||||
|
then store (BytesProcessed sz) AppendMode resp
|
||||||
|
else if responseStatus resp == ok200
|
||||||
|
then store zeroBytesProcessed WriteMode resp
|
||||||
|
else respfailure resp
|
||||||
|
|
||||||
|
alreadydownloaded sz s h = s == requestedRangeNotSatisfiable416
|
||||||
|
&& case lookup hContentRange h of
|
||||||
|
-- This could be improved by fixing
|
||||||
|
-- https://github.com/aristidb/http-types/issues/87
|
||||||
|
Just crh -> crh == B8.fromString ("bytes */" ++ show sz)
|
||||||
|
-- Some http servers send no Content-Range header when
|
||||||
|
-- the range extends beyond the end of the file.
|
||||||
|
-- There is no way to distinguish between the file
|
||||||
|
-- being the same size on the http server, vs
|
||||||
|
-- it being shorter than the file we already have.
|
||||||
|
-- So assume we have the whole content of the file
|
||||||
|
-- already, the same as wget and curl do.
|
||||||
|
Nothing -> True
|
||||||
|
|
||||||
|
store initialp mode resp =
|
||||||
|
sinkResponseFile meterupdate initialp file mode resp
|
||||||
|
|
||||||
|
respfailure = giveup . B8.toString . statusMessage . responseStatus
|
||||||
|
|
||||||
{- Sinks a Response's body to a file. The file can either be opened in
|
{- Sinks a Response's body to a file. The file can either be opened in
|
||||||
- WriteMode or AppendMode. Updates the meter as data is received.
|
- WriteMode or AppendMode. Updates the meter as data is received.
|
||||||
-
|
-
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue