66module Stackage.Package.Hackage
77 ( Hackage
88 , HasUpdates (.. )
9+ , SdistUnavailable (.. )
910 , withHackage
1011 , refreshIndex
1112 , withSdist
1213 , sdistLocations
1314 ) where
1415
15- import Control.Exception (bracket )
16+ import Control.Exception (Exception ( .. ), bracket , throwIO )
1617import Control.Monad (when )
1718import GHC.Stack (HasCallStack )
1819import Data.Time (UTCTime )
@@ -33,10 +34,11 @@ import Hackage.Security.Client.Formats (Format(..))
3334import Hackage.Security.Client.Repository.Cache
3435 (Cache (.. ), getCachedIndex )
3536import qualified Hackage.Security.Client.Repository.Remote as Remote
37+ import Hackage.Security.Util.Checked (tryChecked )
3638import Hackage.Security.Util.Path (fromFilePath , makeAbsolute , toFilePath )
37- import Hackage.Security.Util.Pretty (pretty )
39+ import Hackage.Security.Util.Pretty (Pretty ( .. ) )
3840
39- import Stackage.Package.HttpLib (withHttpLib )
41+ import Stackage.Package.HttpLib (unexpectedResponseStatus , withHttpLib )
4042
4143-- | A bootstrapped connection to Hackage together with the local cache backing
4244-- it.
@@ -45,6 +47,22 @@ data Hackage = Hackage
4547 , hackageCache :: Cache
4648 }
4749
50+ -- | A package whose source tarball no mirror would serve.
51+ --
52+ -- This can happen for moderated packages. The index is append-only, so they're
53+ -- still there... just not downloadable.
54+ data SdistUnavailable = SdistUnavailable
55+ { unavailablePackage :: PackageIdentifier
56+ , unavailableStatus :: Int -- ^ What the last mirror tried answered with
57+ } deriving (Show )
58+
59+ instance Pretty SdistUnavailable where
60+ pretty (SdistUnavailable pkgId code) =
61+ " No mirror would serve " ++ display pkgId ++ " : status " ++ show code
62+
63+ instance Exception SdistUnavailable where
64+ displayException = pretty
65+
4866-- | Primary server first, then out-of-band mirrors.
4967--
5068-- Hackage's own @mirrors.json@ currently lists only defunct mirrors, so the
@@ -118,14 +136,25 @@ refreshIndex hackage now = do
118136--
119137-- The tarball lands under the cache root because @hackage-security@ moves it
120138-- there from a temporary file of its own with a plain rename.
121- withSdist :: Hackage -> PackageIdentifier -> (FilePath -> IO a ) -> IO a
139+ withSdist
140+ :: Hackage
141+ -> PackageIdentifier
142+ -> (FilePath -> IO a )
143+ -> IO (Either SdistUnavailable a )
122144withSdist hackage pkgId action = do
123145 let tmpDir = toFilePath (cacheRoot (hackageCache hackage)) </> " sdists"
124146 createDirectoryIfMissing True tmpDir
125147 bracket (newTempFile tmpDir) removeFile $ \ dest -> do
126- uncheckClientErrors $
148+ fetched <-
149+ uncheckClientErrors $
150+ tryChecked $
127151 downloadPackage' (hackageRepository hackage) pkgId dest
128- action dest
152+ case fetched of
153+ Right () -> Right <$> action dest
154+ Left err ->
155+ case unexpectedResponseStatus err of
156+ Just code -> return (Left (SdistUnavailable pkgId code))
157+ Nothing -> throwIO err
129158 where
130159 newTempFile dir = do
131160 (path, h) <- openBinaryTempFile dir (display pkgId ++ " .tar.gz" )
0 commit comments