{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} module Catalog where import Control.Monad.Error (MonadError, throwError) import Control.Monad.RWS (MonadIO, liftIO, MonadReader, asks) import Data.Aeson ((.:)) import qualified Data.Aeson as Json import qualified Data.Binary as Binary import qualified Data.ByteString.Lazy as LBS import qualified Data.Time.Clock as Time import Data.Version (showVersion) import Network.HTTP import qualified Network.HTTP.Client as Client import qualified Network.HTTP.Client.MultipartFormData as Multi import System.Directory (createDirectoryIfMissing, doesFileExist) import System.FilePath ((), dropFileName) import qualified Elm.Docs as Docs import qualified Elm.Package.Description as Desc import qualified Elm.Package.Name as N import qualified Elm.Package.Paths as P import qualified Elm.Package.Version as V import qualified Manager import qualified Paths_elm_package as This import qualified Utils.Http as Http catalog :: (MonadIO m, MonadReader Manager.Environment m, MonadError String m) => String -> [(String,String)] -> m String catalog path vars = do domain <- asks Manager.catalog return $ domain ++ "/" ++ path ++ "?" ++ urlEncodeVars (version : vars) where version = ("elm-package-version", showVersion This.version) versions :: N.Name -> Manager.Manager (Maybe [V.Version]) versions name = do url <- catalog "versions" [("name", N.toString name)] Http.send url $ \request manager -> do response <- Client.httpLbs request manager return $ Binary.decode $ Client.responseBody response allPackages :: (MonadIO m, MonadReader Manager.Environment m, MonadError String m) => Maybe Time.UTCTime -> m (Maybe [(N.Name, [V.Version])]) allPackages maybeTime = do url <- catalog "all-packages" vars Http.send url $ \request manager -> do response <- Client.httpLbs request manager case Json.eitherDecode (Client.responseBody response) of Left _ -> return Nothing Right summaries -> return $ Just $ map (\(PackageSummary s) -> s) summaries where vars = case maybeTime of Nothing -> [] Just time -> [("since", show time)] newtype PackageSummary = PackageSummary (N.Name, [V.Version]) instance Json.FromJSON PackageSummary where parseJSON (Json.Object obj) = do name <- obj .: "name" versions <- obj .: "versions" return (PackageSummary (name, versions)) parseJSON _ = fail "package summary must be an object" register :: N.Name -> V.Version -> Manager.Manager () register name version = do url <- catalog "register" vars Http.send url $ \request manager -> do request' <- Multi.formDataBody files request let request'' = request' { Client.responseTimeout = Nothing } Client.httpLbs request'' manager return () where vars = [ ("name", N.toString name) , ("version", V.toString version) ] files = [ Multi.partFileSource "documentation" P.documentation , Multi.partFileSource "description" P.description , Multi.partFileSource "readme" "README.md" ] description :: (MonadIO m, MonadReader Manager.Environment m, MonadError String m) => N.Name -> V.Version -> m Desc.Description description name version = getJson "description" P.description name version documentation :: (MonadIO m, MonadReader Manager.Environment m, MonadError String m) => N.Name -> V.Version -> m [Docs.Documentation] documentation name version = getJson "documentation" "documentation.json" name version getJson :: (MonadIO m, MonadReader Manager.Environment m, MonadError String m, Json.FromJSON a) => String -> FilePath -> N.Name -> V.Version -> m a getJson metadata metadataPath name version = do cacheDir <- asks Manager.cacheDirectory let fullMetadataPath = cacheDir N.toFilePath name V.toString version metadataPath exists <- liftIO (doesFileExist fullMetadataPath) content <- case exists of True -> liftIO (LBS.readFile fullMetadataPath) False -> do url <- catalog metadata [("name", N.toString name), ("version", V.toString version)] Http.send url $ \request manager -> do response <- Client.httpLbs request manager createDirectoryIfMissing True (dropFileName fullMetadataPath) LBS.writeFile fullMetadataPath (Client.responseBody response) return (Client.responseBody response) case Json.eitherDecode content of Right value -> return value Left err -> throwError $ "Unable to get " ++ metadataPath ++ " for " ++ N.toString name ++ " " ++ V.toString version ++ "\n" ++ err