-- | Information about files -- -- Intended to be double imported -- -- > import Hackage.Security.TUF.FileMap (FileMap) -- > import qualified Hackage.Security.TUF.FileMap as FileMap module Hackage.Security.TUF.FileMap ( FileMap -- opaque , TargetPath(..) -- * Standard accessors , empty , lookup , (!) , insert , fromList -- * Convenience accessors , lookupM -- * Comparing file maps , FileChange(..) , fileMapChanges ) where import Prelude hiding (lookup) import Control.Arrow (second) import Data.Map (Map) import qualified Data.Map as Map import Hackage.Security.JSON import Hackage.Security.TUF.FileInfo import Hackage.Security.TUF.Paths import Hackage.Security.Util.Path import Hackage.Security.Util.Pretty {------------------------------------------------------------------------------- Datatypes -------------------------------------------------------------------------------} -- | Mapping from paths to file info -- -- File maps are used in target files; the paths are relative to the location -- of the target files containing the file map. newtype FileMap = FileMap { fileMap :: Map TargetPath FileInfo } deriving (Show) -- | Entries in 'FileMap' either talk about the repository or the index data TargetPath = TargetPathRepo RepoPath | TargetPathIndex IndexPath deriving (Show, Eq, Ord) instance Pretty TargetPath where pretty (TargetPathRepo path) = pretty path pretty (TargetPathIndex path) = pretty path {------------------------------------------------------------------------------- Standard accessors -------------------------------------------------------------------------------} empty :: FileMap empty = FileMap Map.empty lookup :: TargetPath -> FileMap -> Maybe FileInfo lookup fp = Map.lookup fp . fileMap (!) :: FileMap -> TargetPath -> FileInfo fm ! fp = fileMap fm Map.! fp insert :: TargetPath -> FileInfo -> FileMap -> FileMap insert fp nfo = FileMap . Map.insert fp nfo . fileMap fromList :: [(TargetPath, FileInfo)] -> FileMap fromList = FileMap . Map.fromList {------------------------------------------------------------------------------- Convenience accessors -------------------------------------------------------------------------------} lookupM :: Monad m => FileMap -> TargetPath -> m FileInfo lookupM m fp = case lookup fp m of Nothing -> fail $ "No entry for " ++ pretty fp ++ " in filemap" Just nfo -> return nfo {------------------------------------------------------------------------------- Comparing filemaps -------------------------------------------------------------------------------} data FileChange = -- | File got added or modified; we record the new file info FileChanged FileInfo -- | File got deleted | FileDeleted deriving (Show) fileMapChanges :: FileMap -- ^ Old -> FileMap -- ^ New -> Map TargetPath FileChange fileMapChanges (FileMap a) (FileMap b) = Map.fromList $ go (Map.toList a) (Map.toList b) where -- Assumes the old and new lists are sorted alphabetically -- (Map.toList guarantees this) go :: [(TargetPath, FileInfo)] -> [(TargetPath, FileInfo)] -> [(TargetPath, FileChange)] go [] new = map (second FileChanged) new go old [] = map (second (const FileDeleted)) old go old@((fp, nfo):old') new@((fp', nfo'):new') | fp < fp' = (fp , FileDeleted ) : go old' new | fp > fp' = (fp', FileChanged nfo') : go old new' | knownFileInfoEqual nfo nfo' = (fp , FileChanged nfo') : go old' new' | otherwise = go old' new' {------------------------------------------------------------------------------- JSON -------------------------------------------------------------------------------} instance Monad m => ToJSON m FileMap where toJSON (FileMap metaFiles) = toJSON metaFiles instance ReportSchemaErrors m => FromJSON m FileMap where fromJSON enc = FileMap <$> fromJSON enc instance Monad m => ToObjectKey m TargetPath where toObjectKey = return . pretty instance ReportSchemaErrors m => FromObjectKey m TargetPath where fromObjectKey ('<':'r':'e':'p':'o':'>':'/':path) = return . Just . TargetPathRepo . rootPath . fromUnrootedFilePath $ path fromObjectKey ('<':'i':'n':'d':'e':'x':'>':'/':path) = return . Just . TargetPathIndex . rootPath . fromUnrootedFilePath $ path fromObjectKey _str = return Nothing