@@ -17,39 +17,31 @@ import Prelude ()
1717import Distribution.Server.Prelude
1818
1919import qualified Data.Aeson as Aeson
20+ import Data.Aeson ((.=) )
21+ import qualified Data.Aeson.Key as Key
2022import qualified Data.ByteString.Lazy.Char8 as BS (toStrict )
23+ import qualified Data.Map.Strict as Map
2124import qualified Data.Text as T
2225import qualified Data.Vector as Vector
2326
2427import Distribution.License (licenseToSPDX )
2528import Distribution.Package (PackageIdentifier (.. ),
26- PackageName , packageName ,
2729 packageVersion )
2830import qualified Distribution.Parsec as Parsec
2931import qualified Distribution.PackageDescription.Parsec as PkgDescr
32+ import Distribution.Text (display )
3033import qualified Distribution.Types.GenericPackageDescription as PkgDescr
3134import qualified Distribution.Types.PackageDescription as PkgDescr
32- import Distribution.Version (nullVersion )
35+ import qualified Distribution.Pretty as Pretty
36+ import Distribution.SPDX.License (License )
37+ import Distribution.Version (nullVersion , Version )
3338
34- import Distribution.Server.Framework ((</>) )
35- import qualified Distribution.Server.Framework as Framework
36- import Distribution.Server.Features.Core (CoreFeature (.. ),
37- CoreResource (.. ),
38- isPackageChangeAny )
39+ import qualified Distribution.Server.Framework as Framework
40+ import Distribution.Server.Features.Core (CoreFeature (.. ),
41+ CoreResource (.. ))
3942import qualified Distribution.Server.Features.PreferredVersions as Preferred
40- import Distribution.Server.Packages.Types (CabalFileText (.. ), pkgMetadataRevisions )
41- import Distribution.Server.Framework.BackupRestore (RestoreBackup (.. ))
42-
43- import Distribution.Server.Features.PackageInfoJSON.State (PackageBasicDescription (.. ),
44- PackageBasicDescriptionDTO (.. ),
45- PackageVersions (.. ),
46- PackageInfoState (.. ),
47- GetPackageInfo (.. ),
48- ReplacePackageInfo (.. ),
49- GetVersionsFor (.. ),
50- SetVersionsFor (.. ),
51- initialPackageInfoState
52- )
43+ import Distribution.Server.Packages.Types (CabalFileText (.. ), pkgMetadataRevisions )
44+
5345import Distribution.Utils.ShortText (fromShortText )
5446import Data.Foldable (toList )
5547import Data.Traversable (for )
@@ -58,6 +50,69 @@ import Data.Time (UTCTime)
5850import Distribution.Server.Users.Types (UserName (.. ), UserInfo (.. ))
5951import Distribution.Server.Features.Users (UserFeature (lookupUserInfo ))
6052
53+ data PackageBasicDescription = PackageBasicDescription
54+ { pbd_license :: ! License
55+ , pbd_copyright :: ! T. Text
56+ , pbd_synopsis :: ! T. Text
57+ , pbd_description :: ! T. Text
58+ , pbd_author :: ! T. Text
59+ , pbd_homepage :: ! T. Text
60+ , pbd_metadata_revision :: ! Int
61+ , pbd_uploaded_at :: ! UTCTime
62+ } deriving (Eq , Show )
63+
64+
65+
66+ -- | Data type used in the `/package/:packagename` JSON endpoint
67+ data PackageBasicDescriptionDTO = PackageBasicDescriptionDTO
68+ { license :: ! License
69+ , copyright :: ! T. Text
70+ , synopsis :: ! T. Text
71+ , description :: ! T. Text
72+ , author :: ! T. Text
73+ , homepage :: ! T. Text
74+ , metadata_revision :: ! Int
75+ , uploaded_at :: ! UTCTime
76+ , uploader :: ! UserName
77+ } deriving (Eq , Show )
78+
79+ instance Aeson. ToJSON PackageBasicDescriptionDTO where
80+ toJSON PackageBasicDescriptionDTO {.. } =
81+ Aeson. object
82+ [ Key. fromString " license" .= Pretty. prettyShow license
83+ , Key. fromString " copyright" .= copyright
84+ , Key. fromString " synopsis" .= synopsis
85+ , Key. fromString " description" .= description
86+ , Key. fromString " author" .= author
87+ , Key. fromString " homepage" .= homepage
88+ , Key. fromString " metadata_revision" .= metadata_revision
89+ , Key. fromString " uploaded_at" .= uploaded_at
90+ , Key. fromString " uploader" .= uploader
91+ ]
92+
93+
94+ -- | An index of versions for one Hackage package
95+ -- and their preferred/deprecated status
96+ newtype PackageVersions = PackageVersions {
97+ unPackageVersions :: [(Version , Preferred. VersionStatus )]
98+ } deriving (Eq , Show )
99+
100+ -- | This encoding of @PackageVersions@ is used in the
101+ -- `/package/$package` endpoint (when the URI doesn't specify)
102+ -- a version. Any change here is an API change.
103+ instance Aeson. ToJSON PackageVersions where
104+ toJSON (PackageVersions p) =
105+ Aeson. toJSON
106+ $ Map. mapKeys display
107+ $ fmap encodeStatus
108+ $ Map. fromList p
109+ where
110+ encodeStatus = \ case
111+ Preferred. NormalVersion -> " normal"
112+ Preferred. DeprecatedVersion -> " deprecated"
113+ Preferred. UnpreferredVersion -> " unpreferred"
114+
115+
61116
62117data PackageInfoJSONFeature = PackageInfoJSONFeature {
63118 packageInfoJSONFeatureInterface :: Framework. HackageFeature
@@ -77,14 +132,10 @@ data PackageInfoJSONResource = PackageInfoJSONResource {
77132-- | Initializing our feature involves adding JSON variants to the
78133-- endpoints that serve basic information about a package-version,
79134-- and a packages version deprecation status.
80- -- Additionally we set up caching for these endpoints,
81- -- and attach a package change hook that invalidates the cache
82- -- line for a package when it changes
83135initPackageInfoJSONFeature
84136 :: Framework. ServerEnv
85137 -> IO (CoreFeature -> Preferred. VersionsFeature -> UserFeature -> IO PackageInfoJSONFeature )
86- initPackageInfoJSONFeature env = do
87- packageInfoState <- packageInfoStateComponent False (Framework. serverStateDir env)
138+ initPackageInfoJSONFeature _env = do
88139 return $ \ core preferred userFeature -> do
89140
90141 let coreR = coreResource core
@@ -98,36 +149,24 @@ initPackageInfoJSONFeature env = do
98149 Framework. resourceDesc = [(Framework. GET , info)]
99150 , Framework. resourceGet =
100151 [(" json" , servePackageBasicDescription coreR userFeature
101- preferred packageInfoState )]
152+ preferred)]
102153 }
103154 , (Framework. extendResource (coreCabalFileRev coreR)) {
104155 Framework. resourceDesc = [(Framework. GET , vInfo)]
105156 , Framework. resourceGet =
106157 [(" json" , servePackageBasicDescription coreR userFeature
107- preferred packageInfoState )]
158+ preferred)]
108159 }
109160 ]
110161
111- -- When a package is modified in any way, delet all its
112- -- PackageInfoState cache lines.
113- -- They will be recalculated next time the endpoint
114- -- is hit
115- postInit = Framework. registerHookJust
116- (packageChangeHook core)
117- isPackageChangeAny $ \ (pkgid, _) -> do
118-
119- Framework. updateState packageInfoState $
120- SetVersionsFor (packageName pkgid) Nothing
121-
122162 return $ PackageInfoJSONFeature {
123163 packageInfoJSONFeatureInterface =
124164 (Framework. emptyHackageFeature " package-info-json" )
125165 { Framework. featureDesc = " Provide JSON endpoints for basic package descriptions"
126166 , Framework. featureResources = jsonResources
127167 , Framework. featureCaches = []
128- , Framework. featurePostInit = postInit
129- , Framework. featureState =
130- [Framework. abstractAcidStateComponent packageInfoState]
168+ , Framework. featurePostInit = pure ()
169+ , Framework. featureState = []
131170 }
132171 }
133172
@@ -184,28 +223,27 @@ servePackageBasicDescription
184223 :: CoreResource
185224 -> UserFeature
186225 -> Preferred. VersionsFeature
187- -> Framework. StateComponent Framework. AcidState PackageInfoState
188226 -> Framework. DynamicPath
189227 -- ^ URI specifying a package and version `e.g. lens or lens-4.11`
190228 -> Framework. ServerPartE Framework. Response
191- servePackageBasicDescription resource userFeature preferred packageInfoState dpath = do
229+ servePackageBasicDescription resource userFeature preferred dpath = do
192230
193231 let metadataRev :: Maybe Int = lookup " revision" dpath >>= Framework. fromReqURI
194232
195233 pkgid@ (PackageIdentifier name version) <- packageInPath resource dpath
196234 guardValidPackageName resource name
197235
198236 if version /= nullVersion
199- then lookupOrInsertDescr pkgid metadataRev
200- else lookupOrInsertVersions name
237+ then fetchDescr pkgid metadataRev
238+ else Framework. toResponse . Aeson. toJSON <$> getVersionListing name
201239
202240 where
203241
204- lookupOrInsertDescr
242+ fetchDescr
205243 :: PackageIdentifier
206244 -> Maybe Int
207245 -> Framework. ServerPartE Framework. Response
208- lookupOrInsertDescr pkgid metadataRev = do
246+ fetchDescr pkgid metadataRev = do
209247 guardValidPackageId resource pkgid
210248 pkg <- lookupPackageId resource pkgid
211249
@@ -235,55 +273,10 @@ servePackageBasicDescription resource userFeature preferred packageInfoState dpa
235273 let packageInfoDTO = basicDescriptionToDTO uploader d
236274 return packageInfoDTO
237275
238- lookupOrInsertVersions
239- :: PackageName
240- -> Framework. ServerPartE Framework. Response
241- lookupOrInsertVersions pkgname = do
242- cachedVersions <- Framework. queryState packageInfoState $
243- GetVersionsFor pkgname
244- vers :: PackageVersions <- case cachedVersions of
245- Just vs -> return vs
246- Nothing -> do
247- vs <- getVersionListing pkgname
248- Framework. updateState packageInfoState $
249- SetVersionsFor pkgname (Just vs)
250- return vs
251- return $ Framework. toResponse $ Aeson. toJSON vers
252-
253276 getVersionListing name = do
254277 pkgs <- lookupPackageName resource name
255278 prefInfo <- Preferred. queryGetPreferredInfo preferred name
256279 return
257280 . PackageVersions
258281 . Preferred. classifyVersions prefInfo
259282 $ fmap packageVersion pkgs
260-
261- -- | Our backup doesn't produce any entries, and backup restore
262- -- returns an empty state. Our responses are cheap enough to
263- -- compute that we would rather regenerate them by need than
264- -- deal with the complexity persisting backups in
265- -- yet-another-format
266- packageInfoStateComponent
267- :: Bool
268- -> FilePath
269- -> IO (Framework. StateComponent Framework. AcidState PackageInfoState )
270- packageInfoStateComponent freshDB stateDir = do
271- st <- Framework. openLocalStateFrom
272- (stateDir </> " db" </> " PackageInfoJSON" )
273- (initialPackageInfoState freshDB)
274- return Framework. StateComponent {
275- stateDesc = " Preferred package versions"
276- , stateHandle = st
277- , getState = Framework. query st GetPackageInfo
278- , putState = Framework. update st . ReplacePackageInfo
279- , resetState = packageInfoStateComponent True
280- , backupState = \ _ -> return []
281- , restoreState = nullRestore (initialPackageInfoState True )
282- }
283- where
284-
285- nullRestore :: PackageInfoState -> RestoreBackup PackageInfoState
286- nullRestore st = RestoreBackup {
287- restoreEntry = \ _ -> nullRestore <$> pure (initialPackageInfoState True )
288- , restoreFinalize = return st
289- }
0 commit comments