module Pier.Core.Download ( askDownload , Download(..) , downloadRules , DownloadLocation(..) ) where import Control.Exception (bracketOnError) import Control.Monad (unless) import Development.Shake import Development.Shake.Classes import Development.Shake.FilePath import GHC.Generics import Network.HTTP.Client import Network.HTTP.Client.TLS import Network.HTTP.Types.Status import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as L import qualified System.Directory as Directory import Pier.Core.Artifact import Pier.Core.Directory import Pier.Core.Persistent import Pier.Core.Run -- | Downloads @downloadUrlPrefix / downloadName@ to -- @downloadFilePrefix / downloadName@. -- Everything is stored in `~/.pier/downloads`. data Download = Download { downloadUrlPrefix :: String , downloadName :: FilePath , downloadFilePrefix :: FilePath } deriving (Typeable, Eq, Generic) instance Show Download where show d = "Download " ++ show (downloadName d) ++ " from " ++ show (downloadUrlPrefix d) ++ " into " ++ show (downloadFilePrefix d) instance Hashable Download instance Binary Download instance NFData Download type instance RuleResult Download = Artifact askDownload :: Download -> Action Artifact askDownload = askPersistent -- TODO: make this its own rule type? downloadRules :: DownloadLocation -> Rules () downloadRules loc = do manager <- liftIO $ newManager tlsManagerSettings addPersistent $ \d -> do -- Download to a shared location under $HOME/.pier, if it doesn't -- already exist (atomically); then make an artifact that symlinks to it. downloadsDir <- liftIO $ pierDownloadsDir loc let result = downloadsDir downloadFilePrefix d downloadName d exists <- liftIO $ Directory.doesFileExist result unless exists $ do putNormal $ "Downloading " ++ downloadName d -- TODO: fix the race liftIO $ bracketOnError (createPierTempFile $ takeFileName $ downloadName d) Directory.removeFile $ \tmp -> do let url = downloadUrlPrefix d ++ "/" ++ downloadName d req <- parseRequest url resp <- httpLbs req manager unless (statusIsSuccessful . responseStatus $ resp) $ error $ "Unable to download " ++ show url ++ "\nStatus: " ++ showStatus (responseStatus resp) liftIO . L.writeFile tmp . responseBody $ resp createParentIfMissing result Directory.renameFile tmp result return $ externalFile result where showStatus s = show (statusCode s) ++ " " ++ BC.unpack (statusMessage s) pierDownloadsDir :: DownloadLocation -> IO FilePath pierDownloadsDir DownloadToHome = do home <- Directory.getHomeDirectory return $ home ".pier/downloads" pierDownloadsDir DownloadLocal = return $ pierFile "downloads" data DownloadLocation = DownloadToHome | DownloadLocal