{-# LANGUAGE ConstraintKinds #-}

module Test.Sandwich.WebDriver.Internal.Binaries.Selenium (
  obtainSelenium
  , downloadSeleniumIfNecessary
  , SeleniumToUse(..)
  ) where

import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
import Control.Monad.Logger
import Control.Monad.Reader
import qualified Data.List as L
import Data.String.Interpolate
import qualified Data.Text as T
import GHC.Stack
import Test.Sandwich
import Test.Sandwich.Contexts.Files
import Test.Sandwich.Contexts.Nix
import Test.Sandwich.WebDriver.Internal.Binaries.Common
import Test.Sandwich.WebDriver.Internal.Binaries.Selenium.Types
import Test.Sandwich.WebDriver.Internal.Util
import UnliftIO.Directory


type Constraints m = (
  HasCallStack
  , MonadLogger m
  , MonadUnliftIO m
  )

-- * Obtaining binaries

defaultSeleniumJarUrl :: String
defaultSeleniumJarUrl :: String
defaultSeleniumJarUrl = String
"https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar"

-- | Manually obtain a Selenium server JAR file, according to the 'SeleniumToUse' policy,
-- storing it under the provided 'FilePath' if necessary and returning the exact path.
obtainSelenium :: (
  MonadReader context m, HasBaseContext context
  , MonadUnliftIO m, MonadLogger m
  )
  -- | How to obtain Selenium
  => SeleniumToUse
  -> m FilePath
obtainSelenium :: forall context (m :: * -> *).
(MonadReader context m, HasBaseContext context, MonadUnliftIO m,
 MonadLogger m) =>
SeleniumToUse -> m String
obtainSelenium (DownloadSeleniumFrom String
toolsDir String
url) = do
  let path :: String
path = [i|#{toolsDir}/selenium-server-standalone.jar|]
  String -> m Bool
forall (m :: * -> *). MonadIO m => String -> m Bool
doesFileExist String
path m Bool -> (Bool -> m ()) -> m ()
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Bool
True -> do
      Text -> m ()
forall (m :: * -> *). (HasCallStack, MonadLogger m) => Text -> m ()
debug [i|Selenium already existed at #{path}|]
    Bool
False -> do
      Text -> m ()
forall (m :: * -> *). (HasCallStack, MonadLogger m) => Text -> m ()
debug [i|Downloading Selenium from #{url} to #{path}|]
      String -> String -> m ()
forall (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
String -> String -> m ()
curlDownloadToPath String
url String
path
  String -> m String
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return String
path
obtainSelenium (DownloadSeleniumDefault String
toolsDir) = do
  let path :: String
path = [i|#{toolsDir}/selenium-server-standalone-3.141.59.jar|]
  String -> m Bool
forall (m :: * -> *). MonadIO m => String -> m Bool
doesFileExist String
path m Bool -> (Bool -> m ()) -> m ()
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Bool
True -> do
      Text -> m ()
forall (m :: * -> *). (HasCallStack, MonadLogger m) => Text -> m ()
debug [i|Selenium already existed at #{path}|]
    Bool
False -> do
      Text -> m ()
forall (m :: * -> *). (HasCallStack, MonadLogger m) => Text -> m ()
debug [i|Downloading Selenium from #{defaultSeleniumJarUrl} to #{path}|]
      String -> String -> m ()
forall (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
String -> String -> m ()
curlDownloadToPath String
defaultSeleniumJarUrl String
path
  String -> m String
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return String
path
obtainSelenium (UseSeleniumAt String
path) = IO Bool -> m Bool
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO Bool
forall (m :: * -> *). MonadIO m => String -> m Bool
doesFileExist String
path) m Bool -> (Bool -> m String) -> m String
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
  Bool
False -> String -> m String
forall (m :: * -> *) a. (HasCallStack, MonadIO m) => String -> m a
expectationFailure [i|Path '#{path}' didn't exist|]
  Bool
True -> do
    Text -> m ()
forall (m :: * -> *). (HasCallStack, MonadLogger m) => Text -> m ()
debug [i|Found Selenium at #{path}|]
    String -> m String
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return String
path
obtainSelenium (UseSeleniumFromNixpkgs NixContext
nc) = do
  Text -> m ()
forall (m :: * -> *). (HasCallStack, MonadLogger m) => Text -> m ()
debug [i|Building selenium-server-standalone with Nix...|]
  ret <- NixContext -> [Text] -> m String
forall context (m :: * -> *).
(HasBaseContextMonad context m, MonadUnliftIO m, MonadLogger m) =>
NixContext -> [Text] -> m String
buildNixSymlinkJoin' NixContext
nc [Text
"selenium-server-standalone"] m String -> (String -> m String) -> m String
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
    IO String -> m String
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO String -> m String)
-> (String -> IO String) -> String -> m String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> IO Bool) -> String -> IO String
findFirstFile (Bool -> IO Bool
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> IO Bool) -> (String -> Bool) -> String -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String
".jar" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`L.isSuffixOf`))
  debug [i|Got Selenium: #{ret}|]
  return ret

-- * Lower level helpers

downloadSeleniumIfNecessary :: Constraints m => FilePath -> m (Either T.Text FilePath)
downloadSeleniumIfNecessary :: forall (m :: * -> *).
Constraints m =>
String -> m (Either Text String)
downloadSeleniumIfNecessary String
toolsDir = m String -> m (Either Text String)
forall (m :: * -> *) a. MonadUnliftIO m => m a -> m (Either Text a)
leftOnException' (m String -> m (Either Text String))
-> m String -> m (Either Text String)
forall a b. (a -> b) -> a -> b
$ do
  let seleniumPath :: String
seleniumPath = [i|#{toolsDir}/selenium-server.jar|]
  IO Bool -> m Bool
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO Bool
forall (m :: * -> *). MonadIO m => String -> m Bool
doesFileExist String
seleniumPath) m Bool -> (Bool -> m ()) -> m ()
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Bool -> m () -> m ()) -> m () -> Bool -> m ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String -> m ()
forall (m :: * -> *). Constraints m => String -> m ()
downloadSelenium String
seleniumPath)
  String -> m String
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return String
seleniumPath
  where
    downloadSelenium :: Constraints m => FilePath -> m ()
    downloadSelenium :: forall (m :: * -> *). Constraints m => String -> m ()
downloadSelenium String
seleniumPath = m () -> m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
      Text -> m ()
forall (m :: * -> *). (HasCallStack, MonadLogger m) => Text -> m ()
info [i|Downloading selenium-server.jar to #{seleniumPath}|]
      String -> String -> m ()
forall (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
String -> String -> m ()
curlDownloadToPath String
defaultSeleniumJarUrl String
seleniumPath