hpodder-1.1.6: Download.hs
{- hpodder component
Copyright (C) 2006-2007 John Goerzen <jgoerzen@complete.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-}
{- |
Module : Download
Copyright : Copyright (C) 2006-2007 John Goerzen
License : GNU GPL, version 2 or above
Maintainer : John Goerzen <jgoerzen@complete.org>
Stability : provisional
Portability: portable
Written by John Goerzen, jgoerzen\@complete.org
-}
module Download(startGetURL, finishGetURL, checkDownloadSize, Result(..),
DownloadTok(..), getdlfname) where
import System.Cmd.Utils
import System.Posix.Process
import Config
import System.Log.Logger
import Text.Printf
import System.Exit
import System.Directory
import System.Posix.Files
import System.Posix.Process
import System.Posix.Types
import System.Posix.IO
import Data.Hash.MD5
import Control.Exception(evaluate)
data Result = Success | Failure
deriving (Eq, Show, Read)
data DownloadTok =
DownloadTok {tokpid :: ProcessID,
tokurl :: String,
tokpath :: FilePath,
tokstartsize :: Maybe FileOffset}
deriving (Eq, Show, Ord)
d = debugM "download"
i = infoM "download"
curl = "curl"
curlopts = ["-A", "hpodder v1.0.0; Haskell; GHC", -- Set User-Agent
"-s", -- Silent mode
"-S", -- Still show error messages
"-L", -- Follow redirects
"-y", "60", "-Y", "1", -- Timeouts
"--retry", "2", -- Retry twice
"--globoff", -- Disable globbing on URLs (#79)
"-f" -- Fail on server errors
]
getCurlConfig :: IO String
getCurlConfig =
do ad <- getAppDir
return $ ad ++ "/curlrc"
getsize fp = catch (getFileStatus fp >>= (return . Just . fileSize))
(\_ -> return Nothing)
{- | Begin the download process on the given URL.
Once it has finished, pass the returned token to finishGetURL. -}
startGetURL :: String -- ^ URL to download
-> FilePath -- ^ Directory into which to put downloaded file
-> Bool -- ^ Whether to allow resuming
-> IO DownloadTok -- ^ Result including path to which the file is being downloaded
startGetURL url dirbase allowresume =
do curlrc <- getCurlConfig
havecurlrc <- doesFileExist curlrc
let curlrcopts = (if havecurlrc then ["-K", curlrc] else [])
++ (if allowresume then ["-C", "-"] else [])
let fp = dirbase ++ "/" ++ getdlfname url
startsize <- getsize fp
case startsize of
Just x -> d $ printf "Resuming download of %s at %s" fp (show x)
Nothing -> d $ printf "Beginning download of %s" fp
msgfd <- openFd (fp ++ ".msg") WriteOnly (Just 0o600)
(defaultFileFlags {trunc = True})
msgfd2 <- dup msgfd
pid <- pOpen3Raw Nothing (Just msgfd) (Just msgfd2)
curl (curlopts ++ curlrcopts ++ [url, "-o", fp])
(return ())
closeFd msgfd
closeFd msgfd2
return $ DownloadTok pid url fp startsize
getdlfname url = md5s (Str url)
{- | Checks to see how much has been downloaded on the given file. Also works
after download is complete to get the final size. Returns Nothing if the
file doesn't exist. -}
checkDownloadSize :: DownloadTok -> IO (Maybe FileOffset)
checkDownloadSize dltok = getsize (tokpath dltok)
finishGetURL :: DownloadTok -> ProcessStatus -> IO Result
finishGetURL dltok ec =
do newsize <- getsize (tokpath dltok)
let r = case ec of
Exited ExitSuccess -> Success
Exited (ExitFailure i) -> Failure
Terminated _ -> Failure
Stopped _ -> Failure
if r == Success
then do d $ "curl returned successfully; new size is " ++
(show newsize)
if (tokstartsize dltok /= Nothing) &&
(newsize == tokstartsize dltok)
-- compensate for resumes that failed
then do i $ "Attempt to resume download failed; will re-try download on next run"
removeFile (tokpath dltok)
--getURL url fp
return Failure
else if newsize == Nothing
-- Sometimes Curl returns success but doesn't
-- actually download anything
then return Failure
else return r
else do d $ "curl returned error; new size is " ++ (show newsize)
return r