packages feed

hackage-server-0.4: Distribution/Server/Features/RecentPackages.hs

{-# LANGUAGE DoRec, RankNTypes, NamedFieldPuns, RecordWildCards #-}
module Distribution.Server.Features.RecentPackages (
    RecentPackagesFeature(..),
    RecentPackagesResource(..),
    initRecentPackagesFeature,
  ) where

import Distribution.Server.Framework

import Distribution.Server.Features.Core
import Distribution.Server.Features.Users
import Distribution.Server.Features.PackageContents (PackageContentsFeature(..))

import Distribution.Server.Packages.Types
import Distribution.Server.Packages.Render

import qualified Distribution.Server.Packages.PackageIndex as PackageIndex
import qualified Distribution.Server.Framework.ResponseContentTypes as Resource

import Data.Time.Clock (getCurrentTime)
import Data.List (sortBy)
import Data.Ord (comparing)


-- the goal is to have the HTML modules import /this/ one, not the other way around
import qualified Distribution.Server.Pages.Recent as Pages

data RecentPackagesFeature = RecentPackagesFeature {
    recentPackagesFeatureInterface :: HackageFeature,
    recentPackagesResource :: RecentPackagesResource,

    -- necessary information for the representation of a package resource
    packageRender :: PkgInfo -> IO PackageRender
    -- other informational hooks: perhaps a simplified CondTree so a browser script can dynamically change the package page based on flags
}

instance IsHackageFeature RecentPackagesFeature where
    getFeatureInterface = recentPackagesFeatureInterface

data RecentPackagesResource = RecentPackagesResource {
    -- replace with log resource
    recentPackages :: Resource
}

initRecentPackagesFeature :: ServerEnv
                          -> UserFeature
                          -> CoreFeature
                          -> PackageContentsFeature
                          -> IO RecentPackagesFeature
initRecentPackagesFeature env@ServerEnv{serverCacheDelay, serverVerbosity = verbosity}
                          user
                          core@CoreFeature{packageChangeHook}
                          packageContents = do
    loginfo verbosity "Initialising recentPackages feature, start"

    -- recent caches. in lieu of an ActionLog
    -- TODO: perhaps a hook, recentUpdated :: HookList ([PkgInfo] -> IO ())
    rec let (feature, updateRecentCache) =
              recentPackagesFeature env user core packageContents
                                    cacheRecent

        cacheRecent <- newAsyncCacheNF updateRecentCache
                         defaultAsyncCachePolicy {
                           asyncCacheName = "recent uploads (html,rss)",
                           asyncCacheUpdateDelay  = serverCacheDelay,
                           asyncCacheSyncInit     = False,
                           asyncCacheLogVerbosity = verbosity
                         }

    registerHookJust packageChangeHook isPackageChangeAny $ \_ ->
      prodAsyncCache cacheRecent

    loginfo verbosity "Initialising recentPackages feature, end"
    return feature


recentPackagesFeature :: ServerEnv
                      -> UserFeature
                      -> CoreFeature
                      -> PackageContentsFeature
                      -> AsyncCache (Response, Response)
                      -> (RecentPackagesFeature, IO (Response, Response))

recentPackagesFeature env
                      UserFeature{..}
                      CoreFeature{..}
                      PackageContentsFeature{packageChangeLog}
                      cacheRecent
  = (RecentPackagesFeature{..}, updateRecentCache)
  where
    recentPackagesFeatureInterface = (emptyHackageFeature "recentPackages") {
        featureResources = map ($recentPackagesResource) [recentPackages]
      , featureState     = []
      , featureCaches    = [
            CacheComponent {
              cacheDesc       = "recents packages page (html, rss)",
              getCacheMemSize = memSize <$> readAsyncCache cacheRecent
            }
          ]
      , featurePostInit = syncAsyncCache cacheRecent
      }

    recentPackagesResource = RecentPackagesResource {
        recentPackages = (resourceAt "/recent.:format") {
            resourceGet = [
                ("html", const $ liftM fst $ readAsyncCache cacheRecent)
              , ("rss",  const $ liftM snd $ readAsyncCache cacheRecent)
              ]
          }
      }

    packageRender pkg = do
      users <- queryGetUserDb
      changeLog <- packageChangeLog pkg
      let showChangeLogLink = case changeLog of Right _ -> True ; _ -> False
      doPackageRender users pkg showChangeLogLink

    updateRecentCache = do
        -- TODO: move the html version to the HTML feature
        pkgIndex <- queryGetPackageIndex
        users <- queryGetUserDb
        now   <- getCurrentTime
        let recentChanges = reverse $ sortBy (comparing pkgUploadTime) (PackageIndex.allPackages pkgIndex)
            xmlRepresentation = toResponse $ Resource.XHtml $ Pages.recentPage users recentChanges
            rssRepresentation = toResponse $ Pages.recentFeed users (serverBaseURI env) now recentChanges
        return (xmlRepresentation, rssRepresentation)

{-
data SimpleCondTree = SimpleCondNode [Dependency] [(Condition ConfVar, SimpleCondTree, SimpleCondTree)]
                    | SimpleCondLeaf
    deriving (Show, Eq)

doMakeCondTree :: GenericPackageDescription -> [(String, SimpleCondTree)]
doMakeCondTree desc = map (\lib -> ("library", makeCondTree lib)) (maybeToList $ condLibrary desc)
                   ++ map (\(exec, tree) -> (exec, makeCondTree tree)) (condExecutables desc)
  where
    makeCondTree (CondNode _ deps comps) = case deps of
        [] -> SimpleCondLeaf
        _  -> SimpleCondNode deps $ map makeCondComponents comps
    makeCondComponents (cond, tree, mtree) = (cond, makeCondTree tree, maybe SimpleCondLeaf makeCondTree mtree)
-}