packages feed

hackage-server-0.4: Distribution/Server/Features/PackageCandidates/Backup.hs

module Distribution.Server.Features.PackageCandidates.Backup where

import Distribution.Server.Framework.BackupRestore
import Distribution.Server.Framework.BackupDump
import Distribution.Server.Features.PackageCandidates.State
import Distribution.Server.Features.PackageCandidates.Types
import Distribution.Server.Features.Core.Backup as CoreBackup
import qualified Distribution.Server.Packages.PackageIndex as PackageIndex
import Distribution.Package (PackageId, packageId)
import Text.CSV (CSV)
import Data.Version (Version(Version), showVersion)
import Data.Map (Map)
import qualified Data.Map as Map

{-------------------------------------------------------------------------------
  Restore backup
-------------------------------------------------------------------------------}

data PartialCandidate = PartialCandidate {
    partialWarnings :: [String]
  , partialPublic   :: Bool
  }

type PartialCandidates = Map PackageId PartialCandidate

restoreCandidates :: RestoreBackup CandidatePackages
restoreCandidates = updateCandidates Map.empty Map.empty

-- We keep the partial packages separate from the rest of the candidate info
-- so that we can reuse more of CoreBackup
updateCandidates :: PartialIndex -> PartialCandidates -> RestoreBackup CandidatePackages
updateCandidates packageMap candidateMap = RestoreBackup {
    restoreEntry = \entry -> do
      packageMap'   <- doPackageImport   packageMap   entry
      candidateMap' <- doCandidateImport candidateMap entry
      return (updateCandidates packageMap' candidateMap')
  , restoreFinalize = do
      let combined = combineMaps packageMap candidateMap
      results <- mapM mkCandidate (Map.toList combined)
      return $ CandidatePackages (PackageIndex.fromList results)
  }
  where
    mkCandidate :: (PackageId, (Maybe PartialPkg, Maybe PartialCandidate)) -> Restore CandPkgInfo
    mkCandidate (pkgId, (Just partialPkg, Just partialCand)) = do
      pkg <- CoreBackup.partialToFullPkg (pkgId, partialPkg)
      return CandPkgInfo { candPkgInfo  = pkg
                         , candWarnings = partialWarnings partialCand
                         , candPublic   = partialPublic   partialCand
                         }
    mkCandidate (pkgId, (Nothing, Just _)) =
      fail $ show pkgId ++ ": candidate.csv without corresponding package"
    mkCandidate (pkgId, (Just _, Nothing)) =
      fail $ show pkgId ++ ": missing candidate.csv"
    mkCandidate _ =
      fail "mkCandidate: the impossible happened"

combineMaps :: Ord k => Map k a -> Map k b -> Map k (Maybe a, Maybe b)
combineMaps map1 map2 =
  let map1' = Map.map (\x -> (Just x, Nothing)) map1
      map2' = Map.map (\y -> (Nothing, Just y)) map2
  in Map.unionWith (\e1 e2 -> (fst e1, snd e2)) map1' map2'

doCandidateImport :: PartialCandidates -> BackupEntry -> Restore PartialCandidates
doCandidateImport candidates (BackupByteString ["package", pkgStr, "candidate.csv"] bs) = do
  pkgId <- CoreBackup.parsePackageId pkgStr
  csv <- importCSV "candidate.csv" bs
  partial <- case csv of
    [_version, ["public", public], "warnings" : warnings] ->
      return PartialCandidate { partialWarnings = warnings
                              , partialPublic   = read public
                              }
    _ ->
      fail "candidate.csv has an invalid format"
  return (Map.insert pkgId partial candidates)
doCandidateImport candidates _ =
  return candidates

{-------------------------------------------------------------------------------
  Create backup
-------------------------------------------------------------------------------}

backupCandidates :: CandidatePackages -> [BackupEntry]
backupCandidates st = concatMap backupCandidate candidates
  where
    candidates :: [CandPkgInfo]
    candidates = PackageIndex.allPackages (candidateList st)

backupCandidate :: CandPkgInfo -> [BackupEntry]
backupCandidate candidate =
    csvToBackup (CoreBackup.pkgPath (packageId candidate) "candidate.csv")
                (backupExtraInfo candidate)
  : CoreBackup.infoToAllEntries (candPkgInfo candidate)

-- | Backup the information CandPkgInfo adds on top of PkgInfo
backupExtraInfo :: CandPkgInfo -> CSV
backupExtraInfo candidate = [
    [showVersion versionCSV]
  , ["public", show (candPublic candidate)]
  , "warnings" : candWarnings candidate
  ]
  where
    versionCSV = Version [0,1] ["unstable"]