Untested Hackage views

This commit is contained in:
Michael Snoyman 2014-04-13 08:48:58 +03:00
parent 8296c4ad57
commit 4f122f6282
5 changed files with 180 additions and 26 deletions

View File

@ -5,7 +5,7 @@ module Application
, makeFoundation , makeFoundation
) where ) where
import Import import Import hiding (catch)
import Settings import Settings
import Yesod.Default.Config import Yesod.Default.Config
import Yesod.Default.Main import Yesod.Default.Main
@ -22,8 +22,14 @@ import System.Log.FastLogger (newStdoutLoggerSet, defaultBufSize, flushLogStr)
import Network.Wai.Logger (clockDateCacher) import Network.Wai.Logger (clockDateCacher)
import Yesod.Core.Types (loggerSet, Logger (Logger)) import Yesod.Core.Types (loggerSet, Logger (Logger))
import qualified System.Random.MWC as MWC import qualified System.Random.MWC as MWC
import Data.BlobStore (fileStore) import Data.BlobStore (fileStore, storeWrite)
import Data.Hackage import Data.Hackage
import Data.Hackage.Views
import Data.Conduit.Lazy (MonadActive, monadActive)
import Control.Monad.Catch (MonadCatch (..))
import Database.Persist.Sql (SqlPersistT (..))
import Control.Monad.Trans.Resource.Internal (ResourceT (..))
import Control.Monad.Reader (MonadReader (..))
-- Import all relevant handler modules here. -- Import all relevant handler modules here.
-- Don't forget to add new modules to your cabal file! -- Don't forget to add new modules to your cabal file!
@ -35,6 +41,8 @@ import Handler.UploadStackage
import Handler.StackageHome import Handler.StackageHome
import Handler.StackageIndex import Handler.StackageIndex
import Handler.StackageSdist import Handler.StackageSdist
import Handler.HackageViewIndex
import Handler.HackageViewSdist
-- This line actually creates our YesodDispatch instance. It is the second half -- This line actually creates our YesodDispatch instance. It is the second half
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the -- of the call to mkYesodData which occurs in Foundation.hs. Please see the
@ -113,8 +121,8 @@ makeFoundation conf = do
-- Start the cabal file loader -- Start the cabal file loader
void $ forkIO $ forever $ flip runLoggingT (messageLoggerSource foundation logger) $ do void $ forkIO $ forever $ flip runLoggingT (messageLoggerSource foundation logger) $ do
when development $ liftIO $ threadDelay $ 5 * 60 * 1000000 when development $ liftIO $ threadDelay $ 5 * 60 * 1000000
eres <- tryAny $ flip runReaderT foundation $ loadCabalFiles eres <- tryAny $ flip runReaderT foundation $ do
$ \name version mmtime -> loadCabalFiles $ \name version mmtime ->
runResourceT $ flip (Database.Persist.runPool dbconf) p $ do runResourceT $ flip (Database.Persist.runPool dbconf) p $ do
mx <- getBy $ UniqueUploaded name version mx <- getBy $ UniqueUploaded name version
case mx of case mx of
@ -122,6 +130,17 @@ makeFoundation conf = do
Nothing -> do Nothing -> do
mtime <- lift $ lift mmtime mtime <- lift $ lift mmtime
forM_ mtime $ void . insertBy . Uploaded name version forM_ mtime $ void . insertBy . Uploaded name version
let views =
[ ("pvp", viewPVP)
, ("no-bounds", viewNoBounds)
, ("unchanged", viewUnchanged)
]
forM_ views $ \(name, func) ->
runResourceT $ flip (Database.Persist.runPool dbconf) p $ createView
name
func
(selectSource [] [])
(storeWrite $ HackageViewIndex name)
case eres of case eres of
Left e -> $logError $ tshow e Left e -> $logError $ tshow e
Right () -> return () Right () -> return ()
@ -129,6 +148,19 @@ makeFoundation conf = do
return foundation return foundation
instance MonadActive m => MonadActive (SqlPersistT m) where -- FIXME orphan upstream
monadActive = lift monadActive
deriving instance MonadCatch m => MonadCatch (SqlPersistT m)
instance MonadCatch m => MonadCatch (ResourceT m) where
catch (ResourceT m) c = ResourceT $ \r -> m r `catch` \e -> unResourceT (c e) r
mask a = ResourceT $ \e -> mask $ \u -> unResourceT (a $ q u) e
where q u (ResourceT b) = ResourceT (u . b)
uninterruptibleMask a =
ResourceT $ \e -> uninterruptibleMask $ \u -> unResourceT (a $ q u) e
where q u (ResourceT b) = ResourceT (u . b)
instance MonadReader env m => MonadReader env (SqlPersistT m) where
ask = lift ask
-- for yesod devel -- for yesod devel
getApplicationDev :: IO (Int, Application) getApplicationDev :: IO (Int, Application)
getApplicationDev = getApplicationDev =

View File

@ -1,9 +1,10 @@
module Data.Hackage module Data.Hackage
( loadCabalFiles ( loadCabalFiles
, sourceHackageSdist , sourceHackageSdist
, createView
) where ) where
import ClassyPrelude.Yesod import ClassyPrelude.Yesod hiding (get)
import Types import Types
import Data.BlobStore import Data.BlobStore
import Data.Conduit.Lazy (MonadActive (..), lazyConsume) import Data.Conduit.Lazy (MonadActive (..), lazyConsume)
@ -12,7 +13,7 @@ import qualified Codec.Archive.Tar as Tar
import Control.Monad.Reader (MonadReader, ask) import Control.Monad.Reader (MonadReader, ask)
import Control.Monad.Trans.Resource (release) import Control.Monad.Trans.Resource (release)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Conduit.Zlib (ungzip) import Data.Conduit.Zlib (ungzip, gzip)
import Text.XML.Cursor (($//), (&/), content, fromDocument, element, followingSibling) import Text.XML.Cursor (($//), (&/), content, fromDocument, element, followingSibling)
import Text.HTML.DOM (sinkDoc) import Text.HTML.DOM (sinkDoc)
import System.IO.Temp (withSystemTempFile, withSystemTempDirectory) import System.IO.Temp (withSystemTempFile, withSystemTempDirectory)
@ -20,8 +21,12 @@ import System.IO (IOMode (ReadMode), openBinaryFile)
import Control.Monad.Catch (MonadCatch) import Control.Monad.Catch (MonadCatch)
import Model (Uploaded (Uploaded)) import Model (Uploaded (Uploaded))
import Filesystem (createTree) import Filesystem (createTree)
import Distribution.PackageDescription.Parse (showPackageDescription, parsePackageDescription, ParseResult (ParseOk)) import Distribution.PackageDescription.Parse (parsePackageDescription, ParseResult (ParseOk))
import Distribution.PackageDescription (GenericPackageDescription, PackageDescription) import Distribution.PackageDescription.PrettyPrint (showGenericPackageDescription)
import Distribution.PackageDescription (GenericPackageDescription, PackageDescription, packageDescription)
import Control.Exception (throw)
import Control.Monad.State (modify, put, get)
import Control.Concurrent.Lifted (fork)
loadCabalFiles :: ( MonadActive m loadCabalFiles :: ( MonadActive m
, MonadBaseControl IO m , MonadBaseControl IO m
@ -46,14 +51,10 @@ loadCabalFiles addUpload = do
liftIO $ hClose handleOut liftIO $ hClose handleOut
withBinaryFile tempIndex ReadMode $ \handleIn -> do withBinaryFile tempIndex ReadMode $ \handleIn -> do
bss <- lazyConsume $ sourceHandle handleIn $= ungzip bss <- lazyConsume $ sourceHandle handleIn $= ungzip
loop $ Tar.read $ fromChunks bss tarSource (Tar.read $ fromChunks bss) $$ parMapMC 32 go =$ sinkNull -- FIXME parMapM_C
where where
withBinaryFile fp mode = bracket (liftIO $ openBinaryFile fp mode) (liftIO . hClose) withBinaryFile fp mode = bracket (liftIO $ openBinaryFile fp mode) (liftIO . hClose)
loop (Tar.Next entry entries) = go entry >> loop entries
loop Tar.Done = return ()
loop (Tar.Fail e) = throwM e
go entry = do go entry = do
case Tar.entryContent entry of case Tar.entryContent entry of
Tar.NormalFile lbs _ Tar.NormalFile lbs _
@ -66,6 +67,10 @@ loadCabalFiles addUpload = do
setUploadDate name version addUpload setUploadDate name version addUpload
_ -> return () _ -> return ()
tarSource Tar.Done = return ()
tarSource (Tar.Fail e) = throwM e
tarSource (Tar.Next e es) = yield e >> tarSource es
setUploadDate :: ( MonadBaseControl IO m setUploadDate :: ( MonadBaseControl IO m
, MonadThrow m , MonadThrow m
, MonadIO m , MonadIO m
@ -148,17 +153,62 @@ sourceHackageSdist name version = do
then storeRead key then storeRead key
else return Nothing else return Nothing
sourceHackageViewSdist viewName name version = do
let key = HackageViewSdist viewName name version
msrc1 <- storeRead key
case msrc1 of
Just src -> return $ Just src
Nothing -> do
mcabalSrc <- storeRead $ HackageViewCabal viewName name version
case mcabalSrc of
Nothing -> return Nothing
Just cabalSrc -> do
cabalLBS <- cabalSrc $$ sinkLazy
msrc <- storeRead $ HackageSdist name version
case msrc of
Nothing -> return Nothing
Just src -> do
lbs <- fromChunks <$> lazyConsume src
let lbs' = Tar.write $ replaceCabal cabalLBS $ Tar.read lbs
sourceLazy lbs' $$ storeWrite key
storeRead key
where
cabalName = unpack $ concat
[ toPathPiece name
, "-"
, toPathPiece version
, "/"
, toPathPiece name
, ".cabal"
]
replaceCabal _ Tar.Done = []
replaceCabal _ (Tar.Fail e) = throw e -- עבירה גוררת עבירה
replaceCabal lbs (Tar.Next e es) = replaceCabal' lbs e : replaceCabal lbs es
replaceCabal' lbs e
| Tar.entryPath e == cabalName = e { Tar.entryContent = Tar.NormalFile lbs (olength64 lbs) }
| otherwise = e
createView :: ( MonadResource m createView :: ( MonadResource m
, MonadCatch m , MonadCatch m
, MonadReader env m , MonadReader env m
, HasBlobStore env StoreKey , HasBlobStore env StoreKey
, MonadBaseControl IO m
, MonadLogger m
) )
=> (PackageName -> Version -> UTCTime -> GenericPackageDescription -> m PackageDescription) => HackageView
-> (PackageName -> Version -> UTCTime -> GenericPackageDescription -> m GenericPackageDescription)
-> Source m (Entity Uploaded) -> Source m (Entity Uploaded)
-> Sink ByteString m () -> Sink ByteString m ()
-> m () -> m ()
createView modifyCabal src sink = withSystemTempDirectory "createview" $ \dir -> do createView viewName modifyCabal src sink = withSystemTempDirectory "createview" $ \dir -> do
rels <- src $$ mapMC (\(Entity _ (Uploaded name version time)) -> do $logDebug $ "Creating view: " ++ tshow viewName
rels <- src $$ parMapMC 32 (uploadedConduit dir) =$ foldC
entries <- liftIO $ Tar.pack dir (map fpToString $ setToList rels)
sourceLazy (Tar.write entries) $$ gzip =$ sink
where
uploadedConduit dir (Entity _ (Uploaded name version time)) = do
let relfp = fpFromText (toPathPiece name) let relfp = fpFromText (toPathPiece name)
</> fpFromText (toPathPiece version) </> fpFromText (toPathPiece version)
</> fpFromText (concat </> fpFromText (concat
@ -176,18 +226,59 @@ createView modifyCabal src sink = withSystemTempDirectory "createview" $ \dir ->
case parsePackageDescription $ unpack $ decodeUtf8 orig of case parsePackageDescription $ unpack $ decodeUtf8 orig of
ParseOk _ gpd -> do ParseOk _ gpd -> do
gpd' <- modifyCabal name version time gpd gpd' <- modifyCabal name version time gpd
return $ encodeUtf8 $ pack $ showPackageDescription gpd' return $ encodeUtf8 $ pack $ showGenericPackageDescription gpd'
_ -> return orig _ -> return orig
sourceLazy new $$ storeWrite (HackageViewCabal viewName name version)
let fp = fpFromString dir </> relfp let fp = fpFromString dir </> relfp
liftIO $ createTree $ directory fp liftIO $ createTree $ directory fp
writeFile fp new writeFile fp new
return $ asSet $ singletonSet relfp return $ asSet $ singletonSet relfp
) =$ foldC
entries <- liftIO $ Tar.pack dir (map fpToString $ setToList rels)
sourceLazy (Tar.write entries) $$ sink
viewNoBounds :: Monad m -- FIXME put in conduit-combinators
=> packageName -> version -> time parMapMC :: (MonadIO m, MonadBaseControl IO m)
-> GenericPackageDescription => Int
-> m GenericPackageDescription -> (i -> m o)
viewNoBounds gpd = undefined -> Conduit i m o
parMapMC threads f = evalStateC 0 $ do
incoming <- liftIO $ newTBQueueIO $ threads * 8
outgoing <- liftIO newTChanIO
lift $ lift $ replicateM_ threads (addWorker incoming outgoing)
awaitForever $ \x -> do
cnt <- get
ys <- atomically $ do
writeTBQueue incoming (Just x)
readWholeTChan outgoing
put $ cnt + 1 - length ys
yieldMany ys
atomically $ writeTBQueue incoming Nothing
let loop = do
togo <- get
when (togo > 0) $ do
y <- atomically $ readTChan outgoing
put $ togo - 1
yield y
loop
where
addWorker incoming outgoing =
fork loop
where
loop = join $ atomically $ do
mx <- readTBQueue incoming
case mx of
Nothing -> do
writeTBQueue incoming Nothing
return $ return ()
Just x -> return $ do
y <- f x
atomically $ writeTChan outgoing y
loop
readWholeTChan chan =
go id
where
go front = do
mx <- tryReadTChan chan
case mx of
Nothing -> return $ front []
Just x -> go $ front . (x:)

View File

@ -7,11 +7,13 @@ import Database.Persist.Sql (PersistFieldSql)
import qualified Data.Text as T import qualified Data.Text as T
newtype PackageName = PackageName { unPackageName :: Text } newtype PackageName = PackageName { unPackageName :: Text }
deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup, PersistField, PersistFieldSql) deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup, PersistField, PersistFieldSql, IsString)
newtype Version = Version { unVersion :: Text } newtype Version = Version { unVersion :: Text }
deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup, PersistField, PersistFieldSql) deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup, PersistField, PersistFieldSql)
newtype PackageSetIdent = PackageSetIdent { unPackageSetIdent :: Text } newtype PackageSetIdent = PackageSetIdent { unPackageSetIdent :: Text }
deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup, PersistField, PersistFieldSql) deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup, PersistField, PersistFieldSql)
newtype HackageView = HackageView { unHackageView :: Text }
deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup, PersistField, PersistFieldSql, IsString)
data PackageNameVersion = PackageNameVersion !PackageName !Version data PackageNameVersion = PackageNameVersion !PackageName !Version
deriving (Show, Read, Typeable, Eq, Ord) deriving (Show, Read, Typeable, Eq, Ord)
@ -29,6 +31,9 @@ data StoreKey = HackageCabal !PackageName !Version
| HackageSdist !PackageName !Version | HackageSdist !PackageName !Version
| CabalIndex !PackageSetIdent | CabalIndex !PackageSetIdent
| CustomSdist !PackageSetIdent !PackageName !Version | CustomSdist !PackageSetIdent !PackageName !Version
| HackageViewCabal !HackageView !PackageName !Version
| HackageViewSdist !HackageView !PackageName !Version
| HackageViewIndex !HackageView
instance ToPath StoreKey where instance ToPath StoreKey where
toPath (HackageCabal name version) = ["hackage", toPathPiece name, toPathPiece version ++ ".cabal"] toPath (HackageCabal name version) = ["hackage", toPathPiece name, toPathPiece version ++ ".cabal"]
@ -40,6 +45,23 @@ instance ToPath StoreKey where
, toPathPiece name , toPathPiece name
, toPathPiece version ++ ".tar.gz" , toPathPiece version ++ ".tar.gz"
] ]
toPath (HackageViewCabal viewName name version) =
[ "hackage-view"
, toPathPiece viewName
, toPathPiece name
, toPathPiece version ++ ".cabal"
]
toPath (HackageViewSdist viewName name version) =
[ "hackage-view"
, toPathPiece viewName
, toPathPiece name
, toPathPiece version ++ ".tar.gz"
]
toPath (HackageViewIndex viewName) =
[ "hackage-view"
, toPathPiece viewName
, "00-index.tar.gz"
]
newtype HackageRoot = HackageRoot { unHackageRoot :: Text } newtype HackageRoot = HackageRoot { unHackageRoot :: Text }
deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup) deriving (Show, Read, Typeable, Eq, Ord, Hashable, PathPiece, ToMarkup)

View File

@ -12,3 +12,5 @@
/stackage/#PackageSetIdent StackageHomeR GET /stackage/#PackageSetIdent StackageHomeR GET
/stackage/#PackageSetIdent/00-index.tar.gz StackageIndexR GET /stackage/#PackageSetIdent/00-index.tar.gz StackageIndexR GET
/stackage/#PackageSetIdent/package/#PackageNameVersion StackageSdistR GET /stackage/#PackageSetIdent/package/#PackageNameVersion StackageSdistR GET
/hackage-view/#HackageView/00-index.tar.gz HackageViewIndexR GET
/hackage-view/#HackageView/package/#PackageNameVersion HackageViewSdistR GET

View File

@ -22,6 +22,7 @@ library
Data.Slug Data.Slug
Data.BlobStore Data.BlobStore
Data.Hackage Data.Hackage
Data.Hackage.Views
Types Types
Handler.Home Handler.Home
Handler.Profile Handler.Profile
@ -31,6 +32,8 @@ library
Handler.StackageHome Handler.StackageHome
Handler.StackageIndex Handler.StackageIndex
Handler.StackageSdist Handler.StackageSdist
Handler.HackageViewIndex
Handler.HackageViewSdist
if flag(dev) || flag(library-only) if flag(dev) || flag(library-only)
cpp-options: -DDEVELOPMENT cpp-options: -DDEVELOPMENT
@ -57,6 +60,8 @@ library
RankNTypes RankNTypes
FunctionalDependencies FunctionalDependencies
PatternGuards PatternGuards
StandaloneDeriving
UndecidableInstances
build-depends: base >= 4 && < 5 build-depends: base >= 4 && < 5
, yesod >= 1.2.5 && < 1.3 , yesod >= 1.2.5 && < 1.3
@ -107,6 +112,8 @@ library
, xml-conduit , xml-conduit
, html-conduit , html-conduit
, Cabal , Cabal
, lifted-base
, mono-traversable
executable stackage-server executable stackage-server
if flag(library-only) if flag(library-only)