packages feed

arch-hs-0.16.2: sync/Submit.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}

module Submit (submit, genCSV) where

import Control.Monad (unless)
import Data.Algorithm.Diff (getGroupedDiff)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as LBS
import Data.List (sortOn)
import Data.Maybe (fromJust)
import qualified Data.Set as Set
import qualified Data.Text as T
import Distribution.ArchHs.Exception
import Distribution.ArchHs.Internal.Prelude
import Distribution.ArchHs.Name (ghcProvidedPackages)
import Distribution.ArchHs.PP
import Distribution.ArchHs.Types
import Distribution.ArchHs.Utils
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode, statusIsSuccessful, statusMessage)
import Submit.CSV
import Utils

-- | Generate Distro CSV from Extra DB
genCSV :: Members [ExtraEnv, HackageEnv, WithMyErr, Embed IO] r => Sem r DistroCSV
genCSV = do
  extra <- ask
  linked <- linkedHaskellPackages
  let providedByGHC = ghcProvidedPackages extra
  pure $
    sortOn
      (^. _1)
      [ (unPackageName hackageName, version, prefix <> tweakedName)
        | (archLinuxName, version, hackageName) <- linked,
          let tweakedName =
                if Set.member hackageName providedByGHC
                  then "ghc"
                  else unArchLinuxName archLinuxName
              prefix = "https://archlinux.org/packages/extra/x86_64/"
      ]

-- | Check and submit distro CSV to Hackage
submit :: Members [HackageEnv, ExtraEnv, WithMyErr, Reader Manager, Embed IO] r => Maybe String -> FilePath -> Bool -> Sem r ()
submit token output upload = do
  csv <- genCSV
  let v = renderDistroCSV csv
  embed $
    unless (null output) $ do
      printInfo $ "Write file" <> colon <+> pretty output
      writeFile output v
  check csv
  manager <- ask
  when ((not . null) token && upload) $ do
    status <- interceptHttpException $ do
      printInfo "Uploading..."
      initialRequest <- parseRequest "https://hackage.haskell.org/distro/Arch/packages"
      let req =
            initialRequest
              { method = "PUT",
                requestBody = RequestBodyBS . BS.pack $ v,
                requestHeaders =
                  [ ("Authorization", "X-ApiKey " <> BS.pack (fromJust token)),
                    ("Content-Type", "text/csv")
                  ]
              }
      result <- httpLbs req manager
      let status = responseStatus result
      printInfo $ "Status" <> colon <+> viaShow status
      printInfo "ResponseBody:"
      printInfo . pretty . decodeUtf8 . LBS.toStrict $ responseBody result
      pure status
    unless (statusIsSuccessful status) $
      throw . HackageUploadFailed (statusCode status) . BS.unpack $ statusMessage status

-- | Download Distro CSV from Hackage and print differences from @extra@
check :: Members [HackageEnv, WithMyErr, Reader Manager, Embed IO] r => DistroCSV -> Sem r ()
check extra = do
  printInfo "Checking generated csv file..."

  req <- interceptHttpException $ parseRequest "https://hackage.haskell.org/distro/Arch/packages.csv"
  printInfo "Downloading csv from hackage..."
  manager <- ask
  result <- interceptHttpException $ httpLbs req manager
  let bs = responseBody result
      hackage = parseDistroCSV . T.unpack . decodeUtf8 . LBS.toStrict $ bs

  let diff = getGroupedDiff hackage extra
      diffOld = filterFirstDiff diff >>= (ppDiffColored . mapDiff (fmap ppRecord))
      diffNew = filterSecondDiff diff >>= (ppDiffColored . mapDiff (fmap ppRecord))
      ppRecord (name, version, url) = "(" <> name <> ", " <> version <> ", " <> url <> ")"
      j g x = if null x then "[]" else g x
  embed . putDoc . annMagneta $ "Diff" <> colon <> line
  embed . putDoc . indent 2 $
    if noDiff diff
      then "[]"
      else
        j cat diffOld
          <> splitLine
          <> j cat diffNew
          <> line

  embed . putDoc $
    "Found"
      <+> pretty (length hackage)
      <+> "packages with submitted distribution information in hackage, and"
      <+> pretty (length extra)
      <+> "haskell packages in"
      <+> ppExtra
      <> dot
      <> line