{- |
   Module      :  Distribution.Hackage.DB.Path
   License     :  BSD3
   Maintainer  :  simons@cryp.to
   Stability   :  provisional
   Portability :  portable

   Find the location of the local Hackage database that is maintained by running
   @cabal update@.
 -}

module Distribution.Hackage.DB.Path where

import Distribution.Hackage.DB.Errors

import Control.Exception
import System.Directory
import System.Environment (lookupEnv)
import System.FilePath

-- |
-- Determines the /state/ directory (which e.g. holds the hackage tarball)
-- cabal-install uses via the following logic:
--
-- 1. If the @CABAL_DIR@ environment variable is set, its content is used as the
--    cabal state directory
-- 2. If @~/.cabal@ (see 'getAppUserDataDirectory') exists, use that.
-- 3. Otherwise, use @${XDG_CACHE_HOME}/cabal@ (see @'getXdgDirectory' 'XdgCache'@)
--    which is the new directory cabal-install can use starting with
--    version @3.10.*@.
--
-- This logic is mostly equivalent to what upstream cabal-install is
-- [doing](https://github.com/haskell/cabal/blob/0ed12188525335ac9759dc957d49979ab09382a1/cabal-install/src/Distribution/Client/Config.hs#L594-L610)
-- with the following exception:
-- The state directory can freely be configured to use a different location
-- in the cabal-install configuration file. hackage-db doesn't parse this
-- configuration file, so differing state directories are ignored.
cabalStateDir :: IO FilePath
cabalStateDir :: IO FilePath
cabalStateDir = do
  Maybe FilePath
envCabal <- FilePath -> IO (Maybe FilePath)
lookupEnv FilePath
"CABAL_DIR"
  FilePath
dotCabal <- FilePath -> IO FilePath
getAppUserDataDirectory FilePath
"cabal"
  Bool
dotCabalExists <- FilePath -> IO Bool
doesDirectoryExist FilePath
dotCabal
  case Maybe FilePath
envCabal of
    Just FilePath
dir -> forall (f :: * -> *) a. Applicative f => a -> f a
pure FilePath
dir
    Maybe FilePath
Nothing ->
      if Bool
dotCabalExists
      then forall (f :: * -> *) a. Applicative f => a -> f a
pure FilePath
dotCabal
      else XdgDirectory -> FilePath -> IO FilePath
getXdgDirectory XdgDirectory
XdgCache FilePath
"cabal"

cabalTarballDir :: String -> IO FilePath
cabalTarballDir :: FilePath -> IO FilePath
cabalTarballDir FilePath
repo = do
  FilePath
csd <- IO FilePath
cabalStateDir
  forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [FilePath] -> FilePath
joinPath [FilePath
csd, FilePath
"packages", FilePath
repo]

hackageTarballDir :: IO FilePath
hackageTarballDir :: IO FilePath
hackageTarballDir = FilePath -> IO FilePath
cabalTarballDir FilePath
"hackage.haskell.org"

-- | Determine the default path of the Hackage database, which typically
-- resides in @$HOME\/.cabal\/packages\/hackage.haskell.org\/@.
-- Running the command @cabal update@ or @cabal v2-update@ will keep the index
-- up-to-date.
--
-- See 'cabalStateDir' on how @hackage-db@ searches for the cabal state directory.
hackageTarball :: IO FilePath
hackageTarball :: IO FilePath
hackageTarball = do
  FilePath
htd <- IO FilePath
hackageTarballDir
  let idx00 :: FilePath
idx00 = FilePath
htd FilePath -> FilePath -> FilePath
</> FilePath
"00-index.tar"
      idx01 :: FilePath
idx01 = FilePath
htd FilePath -> FilePath -> FilePath
</> FilePath
"01-index.tar"
  -- Using 'msum' here would be nice, but unfortunetaly there was no reliable
  -- MonadPlus instance for IO in pre 8.x versions of GHC. So we use the ugly
  -- code for sake of portability.
  Bool
have01 <- FilePath -> IO Bool
doesFileExist FilePath
idx01
  if Bool
have01 then forall (m :: * -> *) a. Monad m => a -> m a
return FilePath
idx01 else do
    Bool
have00 <- FilePath -> IO Bool
doesFileExist FilePath
idx00
    if Bool
have00 then forall (m :: * -> *) a. Monad m => a -> m a
return FilePath
idx00 else
      forall e a. Exception e => e -> IO a
throwIO NoHackageTarballFound
NoHackageTarballFound