packages feed

hackage-repo-tool-0.1.0.0: src/Hackage/Security/Utility/Layout.hs

-- | Layout of the local repository as managed by this tool
--
-- The local repository follows a RepoLayout exactly, but adds some additional
-- files. In addition, we also manage a directory of keys (although this will
-- eventually need to be replaced with a proper key management system).
module Hackage.Security.Utility.Layout (
    -- * File system locations
    RepoLoc(..)
  , KeysLoc(..)
    -- * Additional paths in the repository
  , repoLayoutIndexDir
    -- * Layout of the keys directory
  , KeyRoot
  , KeyPath
  , KeysLayout(..)
  , defaultKeysLayout
  , keysLayoutKey
    -- * Layout-parametrized version of TargetPath
  , TargetPath'(..)
  , prettyTargetPath'
  , applyTargetPath'
    -- * Utility
  , anchorIndexPath
  , anchorRepoPath
  , anchorKeyPath
  , anchorTargetPath'
  ) where

import Distribution.Package

import Hackage.Security.Client
import Hackage.Security.Util.Path
import Hackage.Security.Util.Pretty
import Hackage.Security.Util.Some

{-------------------------------------------------------------------------------
  File system locations
-------------------------------------------------------------------------------}

newtype RepoLoc = RepoLoc { repoLocPath :: AbsolutePath }
  deriving Eq

newtype KeysLoc = KeysLoc { keysLocPath :: AbsolutePath }
  deriving Eq

{-------------------------------------------------------------------------------
  Additional paths specifically to the kind of repository this tool manages
-------------------------------------------------------------------------------}

-- | Directory containing the unpacked index
--
-- Since the layout of the tarball may not match the layout of the index,
-- we create a local directory with the unpacked contents of the index.
repoLayoutIndexDir :: RepoLayout -> RepoPath
repoLayoutIndexDir _ = rootPath Rooted $ fragment' "index"

{-------------------------------------------------------------------------------
  Key layout
-------------------------------------------------------------------------------}

-- | The key directory
data KeyRoot
type KeyPath = Path (Rooted KeyRoot)

instance IsRoot KeyRoot where showRoot _ = "<keys>"

-- | Layout of the keys directory
--
-- Specifies the directories containing the keys (relative to the keys loc),
-- as well as the filename for individual keys.
data KeysLayout = KeysLayout {
      keysLayoutRoot      :: KeyPath
    , keysLayoutTarget    :: KeyPath
    , keysLayoutTimestamp :: KeyPath
    , keysLayoutSnapshot  :: KeyPath
    , keysLayoutMirrors   :: KeyPath
    , keysLayoutKeyFile   :: Some Key -> UnrootedPath
    }

defaultKeysLayout :: KeysLayout
defaultKeysLayout = KeysLayout {
      keysLayoutRoot      = rp $ fragment' "root"
    , keysLayoutTarget    = rp $ fragment' "target"
    , keysLayoutTimestamp = rp $ fragment' "timestamp"
    , keysLayoutSnapshot  = rp $ fragment' "snapshot"
    , keysLayoutMirrors   = rp $ fragment' "mirrors"
    , keysLayoutKeyFile   = \key -> let kId = keyIdString (someKeyId key)
                                    in fragment' kId <.> "private"
    }
  where
    rp :: UnrootedPath -> KeyPath
    rp = rootPath Rooted

keysLayoutKey :: (KeysLayout -> KeyPath) -> Some Key -> KeysLayout -> KeyPath
keysLayoutKey dir key keysLayout@KeysLayout{..} =
   dir keysLayout </> keysLayoutKeyFile key

{-------------------------------------------------------------------------------
  TargetPath'
-------------------------------------------------------------------------------}

-- | This is a variation on 'TargetPath' parameterized by layout
data TargetPath' =
    InRep    (RepoLayout  -> RepoPath)
  | InIdx    (IndexLayout -> IndexPath)
  | InRepPkg (RepoLayout  -> PackageIdentifier -> RepoPath)  PackageIdentifier
  | InIdxPkg (IndexLayout -> PackageIdentifier -> IndexPath) PackageIdentifier

prettyTargetPath' :: RepoLayout -> TargetPath' -> String
prettyTargetPath' repoLayout = pretty . applyTargetPath' repoLayout

-- | Apply the layout
applyTargetPath' :: RepoLayout -> TargetPath' -> TargetPath
applyTargetPath' repoLayout targetPath =
    case targetPath of
      InRep    file       -> TargetPathRepo  $ file repoLayout
      InIdx    file       -> TargetPathIndex $ file indexLayout
      InRepPkg file pkgId -> TargetPathRepo  $ file repoLayout  pkgId
      InIdxPkg file pkgId -> TargetPathIndex $ file indexLayout pkgId
  where
    indexLayout = repoIndexLayout repoLayout

{-------------------------------------------------------------------------------
  Utility
-------------------------------------------------------------------------------}

-- | Anchor a tarball path to the repo (see 'repoLayoutIndex')
anchorIndexPath :: RepoLayout -> RepoLoc -> (IndexLayout -> IndexPath) -> AbsolutePath
anchorIndexPath repoLayout repoLoc file =
        anchorRepoPath repoLayout repoLoc repoLayoutIndexDir
    </> unrootPath' (file $ repoIndexLayout repoLayout)

anchorRepoPath :: RepoLayout -> RepoLoc -> (RepoLayout -> RepoPath) -> AbsolutePath
anchorRepoPath repoLayout (RepoLoc repoLoc) file =
    anchorRepoPathLocally repoLoc $ file repoLayout

anchorKeyPath :: KeysLayout -> KeysLoc -> (KeysLayout -> KeyPath) -> AbsolutePath
anchorKeyPath keysLayout (KeysLoc keysLoc) dir =
    keysLoc </> unrootPath' (dir keysLayout)

anchorTargetPath' :: RepoLayout -> RepoLoc -> TargetPath' -> AbsolutePath
anchorTargetPath' repoLayout repoLoc = go
  where
    go :: TargetPath' -> AbsolutePath
    go (InRep    file)       = anchorRepoPath  repoLayout repoLoc file
    go (InIdx    file)       = anchorIndexPath repoLayout repoLoc file
    go (InRepPkg file pkgId) = anchorRepoPath  repoLayout repoLoc (`file` pkgId)
    go (InIdxPkg file pkgId) = anchorIndexPath repoLayout repoLoc (`file` pkgId)