{-# LANGUAGE FlexibleInstances, OverloadedStrings, PackageImports, ScopedTypeVariables, TemplateHaskell #-} {-# OPTIONS -Wall -fno-warn-orphans #-} module Debian.Repo.State.PackageIndex ( binaryPackagesFromSources , sourcePackagesFromSources ) where import Control.Monad.Trans (MonadIO(liftIO)) import Data.List (intercalate) import Data.List as List (map, partition) import Data.Maybe (catMaybes) import qualified Data.Text as T (Text, unpack) import Debian.Arch (Arch, Arch(..), prettyArch) import Debian.Control (ControlFunctions(stripWS), formatParagraph) import qualified Debian.Control.Text as B (Control'(Control), ControlFunctions(lookupP), ControlFunctions(parseControlFromHandle), Field, Field'(Field), fieldValue, Paragraph) import Debian.Pretty (ppDisplay) import qualified Debian.Relation.Text as B (ParseRelations(..), Relations) import Debian.Release (ReleaseName(..), releaseName', sectionName') import Debian.Repo.EnvPath (EnvRoot(rootPath)) import Debian.Repo.Internal.Repos (MonadRepos) import Debian.Repo.PackageID (makeBinaryPackageID, makeSourcePackageID) import Debian.Repo.PackageIndex (BinaryPackage, BinaryPackage(..), PackageIndex(..), PackageIndex(packageIndexArch, packageIndexComponent), packageIndexPath, SourceControl(..), SourceFileSpec(SourceFileSpec), SourcePackage(..), SourcePackage(sourcePackageID)) --import Debian.Repo.Prelude (symbol) import Debian.Repo.Release (Release(releaseName)) import Debian.Repo.Repo (Repo(repoKey, repoReleaseInfo), RepoKey, repoKeyURI) import Debian.Repo.Slice (binarySlices, Slice(sliceRepoKey, sliceSource), SliceList(slices), sourceSlices) import Debian.Repo.State.Repository (foldRepository) import Debian.Sources (DebSource(sourceDist, sourceType), SourceType(Deb, DebSrc)) import Debian.URI (URI(uriScheme), uriToString') import Debian.Version (parseDebianVersion) import Network.URI (escapeURIString, URI(uriAuthority, uriPath), URIAuth(uriPort, uriRegName, uriUserInfo)) import qualified System.IO as IO (hClose, IOMode(ReadMode), openBinaryFile) --import System.IO.Unsafe (unsafeInterleaveIO) --import System.Process.Progress (qBracket, quieter) -- |Return a list of the index files that contain the packages of a -- slice. sliceIndexes :: MonadRepos m => Arch -> Slice -> m [(RepoKey, Release, PackageIndex)] sliceIndexes arch slice = foldRepository f f (sliceRepoKey slice) where f repo = case (sourceDist (sliceSource slice)) of Left exact -> error $ "Can't handle exact path in sources.list: " ++ exact Right (release, sections) -> return $ map (makeIndex repo release) sections makeIndex repo release section = (repoKey repo, findReleaseInfo repo release, PackageIndex { packageIndexComponent = section , packageIndexArch = case (sourceType (sliceSource slice)) of DebSrc -> Source Deb -> arch }) findReleaseInfo repo release = case filter ((==) release . releaseName) (repoReleaseInfo repo) of [x] -> x [] -> error $ ("sliceIndexes: Invalid release name: " ++ releaseName' release ++ "\n You may need to remove ~/.autobuilder/repoCache." ++ "\n Available: " ++ (show . map releaseName . repoReleaseInfo $ repo)) ++ "\n repoKey: " ++ show (repoKey repo) ++ "\n repoReleaseInfo: " ++ show (repoReleaseInfo repo) ++ "\n slice: " ++ show slice xs -> error $ "Internal error 5 - multiple releases named " ++ releaseName' release ++ "\n" ++ show xs data UpdateError = Changed ReleaseName FilePath SliceList SliceList | Missing ReleaseName FilePath | Flushed instance Show UpdateError where show (Changed r p l1 l2) = unwords ["Changed", show r, show p, ppDisplay l1, ppDisplay l2] show (Missing r p) = unwords ["Missing", show r, show p] show Flushed = "Flushed" sourcePackagesFromSources :: MonadRepos m => EnvRoot -> Arch -> SliceList -> m [SourcePackage] sourcePackagesFromSources root arch sources = do indexes <- mapM (sliceIndexes arch) (slices . sourceSlices $ sources) >>= return . concat mapM (\ (repo, rel, index) -> sourcePackagesOfIndex root arch repo rel index) indexes >>= return . concat -- FIXME: assuming the index is part of the cache sourcePackagesOfIndex :: MonadRepos m => EnvRoot -> Arch -> RepoKey -> Release -> PackageIndex -> m [SourcePackage] sourcePackagesOfIndex root arch repo release index = -- quieter 2 $ qBracket ($(symbol 'sourcePackagesOfIndex) ++ " " ++ path) $ do -- unsafeInterleaveIO makes the package index file reads -- asynchronous, not sure what the performance implications -- are. Anyway, this is now only called on demand, so the -- unsafeInterleaveIO is probably moot. paragraphs <- liftIO $ {-unsafeInterleaveIO-} (readParagraphs path) let packages = List.map (toSourcePackage index) paragraphs return packages where path = rootPath root ++ suff suff = indexCacheFile arch repo release index toSourcePackage :: PackageIndex -> B.Paragraph -> SourcePackage toSourcePackage index package = case (B.fieldValue "Directory" package, B.fieldValue "Files" package, B.fieldValue "Package" package, maybe Nothing (Just . parseDebianVersion . T.unpack) (B.fieldValue "Version" package)) of (Just directory, Just files, Just name, Just version) -> case (parseSourcesFileList files, parseSourceParagraph package) of (Right files', Right para) -> SourcePackage { sourcePackageID = makeSourcePackageID (T.unpack name) version , sourceParagraph = package , sourceControl = para , sourceDirectory = T.unpack directory , sourcePackageFiles = files' } (Left messages, _) -> error $ "Invalid file list: " ++ show messages (_, Left messages) -> error $ "Error in source paragraph\n package=" ++ show package ++ "\n index=" ++ show index ++ "\n messages:\n " ++ intercalate "\n " messages x -> error $ "Missing info in source package control information in " ++ show index ++ " -> " ++ show x ++ " :\n" ++ T.unpack (formatParagraph package) where -- Parse the list of files in a paragraph of a Sources index. parseSourcesFileList :: T.Text -> Either [String] [SourceFileSpec] parseSourcesFileList text = merge . catMaybes . List.map parseSourcesFiles . lines . T.unpack $ text parseSourcesFiles line = case words line of [md5sum, size, name] -> Just (Right (SourceFileSpec md5sum (read size) name)) [] -> Nothing _ -> Just (Left ("Invalid line in Files list: '" ++ show line ++ "'")) merge x = case partition (either (const True) (const False)) x of (a, []) -> Left . catMaybes . List.map (either Just (const Nothing )) $ a (_, a) -> Right . catMaybes . List.map (either (const Nothing) Just) $ a parseSourceParagraph :: B.Paragraph -> Either [String] SourceControl parseSourceParagraph p = -- Look up the required fields case (B.fieldValue "Package" p, B.fieldValue "Maintainer" p) of (Just source', Just maintainer') -> -- The optional fields can be parsed as pure values Right (SourceControl { source = source' , maintainer = maintainer' , uploaders = maybe [] (: []) $ B.fieldValue "Uploaders" p , packageSection = fmap stripWS $ B.fieldValue "Section" p , packagePriority = fmap stripWS $ B.fieldValue "Priority" p , buildDepends = maybe [] (: []) $ B.fieldValue "Build-Depends" p , buildDependsIndep = maybe [] (: []) $ B.fieldValue "Build-Depends-Indep" p , buildConflicts = maybe [] (: []) $ B.fieldValue "Build-Conflicts" p , buildConflictsIndep = maybe [] (: []) $ B.fieldValue "Build-Conflicts-Indep" p , standardsVersion = fmap stripWS $ B.fieldValue "Standards-Version" p , homepage = fmap stripWS $ B.fieldValue "Homepage" p }) _x -> Left ["parseSourceParagraph - One or more required fields (Package, Maintainer, Standards-Version) missing: " ++ show p] binaryPackagesFromSources :: MonadRepos m => EnvRoot -> Arch -> SliceList -> m [BinaryPackage] binaryPackagesFromSources root arch sources = do indexes <- mapM (sliceIndexes arch) (slices . binarySlices $ sources) >>= return . concat mapM (\ (repo, rel, index) -> binaryPackagesOfIndex root arch repo rel index) indexes >>= return . concat -- FIXME: assuming the index is part of the cache binaryPackagesOfIndex :: MonadRepos m => EnvRoot -> Arch -> RepoKey -> Release -> PackageIndex -> m [BinaryPackage] binaryPackagesOfIndex root arch repo release index = -- quieter 2 $ qBracket ($(symbol 'binaryPackagesOfIndex) ++ ": " ++ path) $ do paragraphs <- liftIO $ {-unsafeInterleaveIO-} (readParagraphs path) let packages = List.map (toBinaryPackage release index) paragraphs return packages where suff = indexCacheFile arch repo release index path = rootPath root ++ suff toBinaryPackage :: Release -> PackageIndex -> B.Paragraph -> BinaryPackage toBinaryPackage release index p = case (B.fieldValue "Package" p, B.fieldValue "Version" p) of (Just name, Just version) -> BinaryPackage { packageID = makeBinaryPackageID (T.unpack name) (parseDebianVersion (T.unpack version)) , packageInfo = p , pDepends = tryParseRel $ B.lookupP "Depends" p , pPreDepends = tryParseRel $ B.lookupP "Pre-Depends" p , pConflicts = tryParseRel $ B.lookupP "Conflicts" p , pReplaces = tryParseRel $ B.lookupP "Replaces" p , pProvides = tryParseRel $ B.lookupP "Provides" p } _ -> error ("Invalid data in source index:\n " ++ packageIndexPath release index) tryParseRel :: Maybe B.Field -> B.Relations tryParseRel (Just (B.Field (_, relStr))) = either (error . show) id (B.parseRelations relStr) tryParseRel _ = [] readParagraphs :: FilePath -> IO [B.Paragraph] readParagraphs path = do --IO.hPutStrLn IO.stderr ("OSImage.paragraphsFromFile " ++ path) -- Debugging output h <- IO.openBinaryFile path IO.ReadMode B.Control paragraphs <- B.parseControlFromHandle path h >>= return . (either (error . show) id) IO.hClose h --IO.hPutStrLn IO.stderr ("OSImage.paragraphsFromFile " ++ path ++ " done.") -- Debugging output return paragraphs indexCacheFile :: Arch -> RepoKey -> Release -> PackageIndex -> FilePath indexCacheFile arch repo release index = case (arch, packageIndexArch index) of (Binary _ _, Source) -> indexPrefix repo release index ++ "_source_Sources" (Binary _ _, indexArch@(Binary _ _)) -> indexPrefix repo release index ++ "_binary-" ++ show (prettyArch indexArch) ++ "_Packages" (x, _) -> error $ "Invalid build architecture: " ++ show x indexPrefix :: RepoKey -> Release -> PackageIndex -> FilePath indexPrefix repo release index = (escapeURIString (/= '@') ("/var/lib/apt/lists/" ++ uriText +?+ "dists_") ++ releaseName' distro ++ "_" ++ (sectionName' $ section)) where section = packageIndexComponent index uri = repoKeyURI repo distro = releaseName $ release scheme = uriScheme uri auth = uriAuthority uri path = uriPath uri userpass = maybe "" uriUserInfo auth reg = maybeOfString $ maybe "" uriRegName auth port = maybe "" uriPort auth (user, pass) = break (== ':') userpass user' = maybeOfString user pass' = maybeOfString pass uriText = prefix scheme user' pass' reg port path -- If user is given and password is not, the user name is -- added to the file name. Otherwise it is not. Really. prefix "http:" (Just user'') Nothing (Just host) port' path' = user'' ++ host ++ port' ++ escape path' prefix "http:" _ _ (Just host) port' path' = host ++ port' ++ escape path' prefix "ftp:" _ _ (Just host) _ path' = host ++ escape path' prefix "file:" Nothing Nothing Nothing "" path' = escape path' prefix "ssh:" (Just user'') Nothing (Just host) port' path' = user'' ++ host ++ port' ++ escape path' prefix "ssh" _ _ (Just host) port' path' = host ++ port' ++ escape path' prefix _ _ _ _ _ _ = error ("invalid repo URI: " ++ (uriToString' . repoKeyURI $ repo)) maybeOfString "" = Nothing maybeOfString s = Just s escape s = intercalate "_" (wordsBy (== '/') s) wordsBy :: Eq a => (a -> Bool) -> [a] -> [[a]] wordsBy p s = case (break p s) of (s', []) -> [s'] (h, t) -> h : wordsBy p (drop 1 t) (+?+) :: String -> String -> String (+?+) a ('_' : b) = a +?+ b (+?+) "" b = b (+?+) a b = case last a of '_' -> (init a) +?+ b _ -> a ++ "_" ++ b