Files
p2p-rendering-computation/haskell/lib/P2Prc.hs

325 lines
7.4 KiB
Haskell

{-# LANGUAGE OverloadedStrings #-}
module P2Prc ( runP2Prc )
where
import System.Exit ( ExitCode(ExitFailure) )
import System.Process
( readProcessWithExitCode
, proc
, createProcess
, terminateProcess
, ProcessHandle
)
import Control.Concurrent ( threadDelay )
import qualified Data.Text as T
import qualified Data.ByteString.Lazy.Char8 as LBC8
import Data.Aeson
import Environment ( cleanEnvironment )
import Error (IOEitherError, Error(..), assignError)
import JSON
-- URGENT TASKS
--
-- TODO: setup the project as a haskell library
-- TODO: splitting code on different files and directories
-- TODO: lock cabal index
-- TODO: add Haddock documentation
--
-- TODO: P2PRC setup
-- p2prc runtime packaging
-- Perhaps create internal script to run P2PRC from nix flake
-- "nix flake run ..."
-- simplify packaging
-- check version P2PRC: only run if version if above a certain value
-- setup p2prc command
-- check if p2prc command is available in environment first
-- otherwise check folder above
--
-- TODO: publish haskell library
--
-- TODO: add use case examples (extra-source_files)
-- Module: API
runP2Prc :: IO ()
runP2Prc = do
-- important but not urgent yet
--
-- TODO: Change Standard Library
-- TODO: add GDTA syntax to data types
-- TODO: need monad transformers to refactor the code
--
-- TODO: parse IO arguments;
-- TODO: create DSL from the standard input
--
-- TODO: setup nix flake package
-- TODO: add quickcheck testing (quickchecking-dynamic)
eitherP2prcAPI <- getP2prcAPI
case eitherP2prcAPI of
(Right p2prcAPI) -> do
let
( MkP2prAPI
{ startServer = startServer
, execInitConfig = execInitConfig
, execListServers = execListServers
, execMapPort = execMapPort
}
) = p2prcAPI
configValue <- execInitConfig
-- TODO: get name of host server from config json
print configValue
putStrLn "\n\n\n"
eitherStartProcessHandle <- startServer
case eitherStartProcessHandle of
(Right startProcessHandle) -> do
let sleepNSecs i = threadDelay (i * 1000000)
sleepNSecs 5
outputStr <- execListServers
print outputStr
mapPortOut <- execMapPort $ MkMapPortRequest 3333 "domain"
case mapPortOut of
(Right v) -> print v
(Left e) -> print e
-- TODO: work on looping function
--
-- Loop (Run replica of haskell program on different $NODES)
-- - Start server
-- - wait 4 seconds
-- - Identify new node running p2prc with SSH external port exposed
-- - SSH into machine
-- - Use simple File transfer to setup files
-- - Run server
-- - Use remote machine p2prc cmd to map a port using --mp
-- - Return back the exposed public IP and port number back to stdout
terminateProcess startProcessHandle
(Left err) -> print err
(Left err) -> print err
data P2prAPI = MkP2prAPI
{ startServer :: IOEitherError ProcessHandle
, execInitConfig :: IOEitherError P2prcConfig
, execListServers :: IOEitherError IPAdressTable
, execMapPort :: MapPortRequest -> IOEitherError MapPortResponse
}
data MapPortRequest
= MkMapPortRequest Int String
getP2prcAPI :: IOEitherError P2prAPI
getP2prcAPI = do
cleanEnvironment
eitherP2prcCmd <- getP2PrcCmd
pure $ case eitherP2prcCmd of
(Right p2prcCmd) -> let
execProcP2PrcParser ::
FromJSON a =>
[CLIOpt] -> StdInput -> IOEitherError a
execProcP2PrcParser = eitherExecProcessParser p2prcCmd
-- TODO: GHC question, why does it scope down instead staying generic
execProcP2Prc = eitherExecProcess p2prcCmd
in do
Right $ MkP2prAPI
{ startServer = spawnProcP2Prc p2prcCmd [ MkOptAtomic "--s" ]
, execListServers =
execProcP2PrcParser [ MkOptAtomic "--ls" ] MkEmptyStdInput
, execMapPort =
\ (MkMapPortRequest portNumber _) ->
execProcP2PrcParser
[ MkOptTuple
( "--mp"
, show portNumber
)
-- , MkOptTuple -- TODO: add domain parameter
-- ( "--dm"
-- , domainName
-- )
]
MkEmptyStdInput
, execInitConfig = do
confInitRes <- execProcP2Prc [ MkOptAtomic "--dc" ] MkEmptyStdInput
case confInitRes of
(Right _) -> do
-- TODO: get config file name dynamically
-- TODO: change values before loading file
let fname = "/home/xecarlox/Desktop/p2p-rendering-computation/haskell/config.json" :: FilePath
-- TODO: read config check if file exists
configContent <- readFile fname
pure $ eitherErrDecode configContent
(Left err) -> pure $ Left err
}
(Left err) -> Left err
-- Module: CLI
data StdInput
= MkEmptyStdInput
| MkStdInputVal String
instance Show StdInput where
show MkEmptyStdInput = ""
show (MkStdInputVal v) = v
data CLIOpt
= MkEmptyOpts
| MkOptAtomic String
| MkOptTuple (String, String)
type CLIOptsInput = [String]
type CLICmd = String
eitherExecProcessParser ::
FromJSON a =>
CLICmd -> [CLIOpt] -> StdInput -> IOEitherError a
eitherExecProcessParser p2prcCmd opts stdInput =
do
val <- eitherExecProcess p2prcCmd opts stdInput
pure $ case val of
(Right v) -> eitherErrDecode v
(Left e) -> Left e
eitherErrDecode ::
FromJSON a =>
String -> Either Error a
eitherErrDecode = eitherErrorDecode . eitherDecode . LBC8.pack
eitherExecProcess :: CLICmd -> [CLIOpt] -> StdInput -> IOEitherError String
eitherExecProcess cmd opts input =
do
(code, out, err) <-
readProcessWithExitCode
cmd
(optsToCLI opts)
(show input)
pure $ case code of
ExitFailure i -> Left $ MkSystemError i cmd err
_ -> Right out
optsToCLI :: [CLIOpt] -> CLIOptsInput
optsToCLI = concatMap _optToCLI
where
_optToCLI :: CLIOpt -> CLIOptsInput
_optToCLI MkEmptyOpts = []
_optToCLI (MkOptAtomic o) = [o]
_optToCLI (MkOptTuple (o, v)) = [o, v]
spawnProcP2Prc :: CLICmd -> [CLIOpt] -> IOEitherError ProcessHandle
spawnProcP2Prc cmd opts =
do
let prc = proc cmd $ optsToCLI opts
creationResult <- createProcess prc
let (_, _, _, ph) = creationResult
case creationResult of
(_, _, Just _, _) -> do
terminateProcess ph
pure $ Left $ MkErrorSpawningProcess cmd
_-> pure $ Right ph
eitherErrorDecode :: Either String a -> Either Error a
eitherErrorDecode esa =
case esa of
(Left s) -> Left $ assignError s
(Right v) -> Right v
getP2PrcCmd :: IOEitherError String
getP2PrcCmd = do
-- assumes the program is ran inside the haskell module in p2prc's repo
-- assumes that last path segment is "haskell" and that p2prc binary's name is "p2p-rendering-computation"
let trimString = T.unpack . T.strip . T.pack
eitherErrPwd <- eitherExecProcess "pwd" [MkEmptyOpts] MkEmptyStdInput
case eitherErrPwd of
(Right pwdOut) ->
eitherExecProcess
"sed"
[ MkOptAtomic "s/haskell/p2p-rendering-computation/" ]
$ MkStdInputVal
$ trimString pwdOut
err -> pure err