----------------------------------------------------------------------------- -- -- Module : Transient.Move.Utils -- Copyright : -- License : MIT -- -- Maintainer : agocorona@gmail.com -- Stability : -- Portability : -- -- | -- ----------------------------------------------------------------------------- {-# LANGUAGE ScopedTypeVariables #-} module Transient.Move.Utils (initNode,initNodeDef, initNodeServ, inputNodes, simpleWebApp, initWebApp , onServer, onBrowser, atServer, atBrowser, runTestNodes) where --import Transient.Base import Transient.Internals import Transient.Move.Internals import Control.Applicative import Control.Monad.State import Data.IORef import System.Environment -- | ask in the console for the port number and initializes a node in the port specified -- It needs the application to be initialized with `keep` to get input from the user. -- the port can be entered in the command line with "<program> -p start/<PORT>" -- -- A node is also a web server that send to the browser the program if it has been -- compiled to JavaScript with ghcjs. `initNode` also initializes the web nodes. -- -- This sequence compiles to JScript and executes the program with a node in the port 8080 -- -- > ghc program.hs -- > ghcjs program.hs -o static/out -- > ./program -p start/myhost/8080 -- -- `initNode`, when the application has been loaded and executed in the browser, will perform a `wormhole` to his server node. -- So the application run within this wormhole. -- -- Since the code is executed both in server node and browser node, to avoid confusion and in order -- to execute in a single logical thread, use `onServer` for code that you need to execute only in the server -- node, and `onBrowser` for code that you need in the browser, although server code could call the browser -- and vice-versa. -- -- To invoke from browser to server and vice-versa, use `atRemote`. -- -- To translate the code from the browser to the server node, use `teleport`. -- initNode :: Loggable a => Cloud a -> TransIO a initNode app= do node <- getNodeParams --abduce initWebApp node app getNodeParams :: TransIO Node getNodeParams = if isBrowserInstance then liftIO createWebNode else do oneThread $ option "start" "re/start node" host <- input (const True) "hostname of this node. (Must be reachable)? " port <- input (const True) "port to listen? " liftIO $ createNode host port initNodeDef :: Loggable a => String -> Int -> Cloud a -> TransIO a initNodeDef host port app= do node <- def <|> getNodeParams initWebApp node app where def= do args <- liftIO getArgs if null args then liftIO $ createNode host port else empty initNodeServ :: Loggable a => Service -> String -> Int -> Cloud a -> TransIO a initNodeServ services host port app= do node <- def <|> getNodeParams let node'= node{nodeServices=services} initWebApp node' $ app where def= do args <- liftIO getArgs if null args then liftIO $ createNode host port else empty -- | ask for nodes to be added to the list of known nodes. it also ask to connect to the node to get -- his list of known nodes. It returns empty inputNodes :: Cloud empty inputNodes= onServer $ do --local abduce listNodes <|> addNew where addNew= do local $ oneThread $ option "add" "add a new node" host <- local $ do r <- input (const True) "Hostname of the node (none): " if r == "" then stop else return r port <- local $ input (const True) "port? " services <- local $ input' (Just []) (const True) "services? ([]) " connectit <- local $ input (\x -> x=="y" || x== "n") "connect to the node to interchange node lists? (n) " nnode <- localIO $ createNodeServ host port services if connectit== "y" then connect' nnode else local $ do liftIO $ putStr "Added node: ">> print nnode addNodes [nnode] empty listNodes= do local $ option "list" "list nodes" local $ getNodes >>= liftIO . print empty -- | executes the application in the server and the Web browser. -- the browser must point to http://hostname:port where port is the first parameter. -- It creates a wormhole to the server. -- The code of the program after `simpleWebApp` run in the browser unless `teleport` translates the execution to the server. -- To run something in the server and get the result back to the browser, use `atRemote` -- This last also works in the other side; If the application was teleported to the server, `atRemote` will -- execute his parameter in the browser. -- -- It is necesary to compile the application with ghcjs: -- -- > ghcjs program.js -- > ghcjs program.hs -o static/out -- -- > ./program -- -- simpleWebApp :: Loggable a => Integer -> Cloud a -> IO () simpleWebApp port app = do node <- createNode "localhost" $ fromIntegral port keep $ initWebApp node app return () -- | use this instead of smpleWebApp when you have to do some initializations in the server prior to the -- initialization of the web server initWebApp :: Loggable a => Node -> Cloud a -> TransIO a initWebApp node app= do conn <- defConnection liftIO $ writeIORef (myNode conn) node addNodes [node] serverNode <- getWebServerNode :: TransIO Node mynode <- if isBrowserInstance then liftIO $ createWebNode else return serverNode runCloud $ do listen mynode <|> return() wormhole serverNode app -- | only execute if the the program is executing in the browser. The code inside can contain calls to the server. -- Otherwise return empty (so it stop the computation and may execute alternative computations). onBrowser :: Cloud a -> Cloud a onBrowser x= do r <- local $ return isBrowserInstance if r then x else empty -- | only executes the computaion if it is in the server, but the computation can call the browser. Otherwise return empty onServer :: Cloud a -> Cloud a onServer x= do r <- local $ return isBrowserInstance if not r then x else empty -- | If the computation is running in the server, translates i to the browser and return back. -- If it is already in the browser, just execute it atBrowser :: Loggable a => Cloud a -> Cloud a atBrowser x= do r <- local $ return isBrowserInstance if r then x else atRemote x -- | If the computation is running in the browser, translates i to the server and return back. -- If it is already in the server, just execute it atServer :: Loggable a => Cloud a -> Cloud a atServer x= do r <- local $ return isBrowserInstance if not r then x else atRemote x -- | run N nodes (N ports to listen) in the same program. For testing purposes. -- It add them to the list of known nodes, so it is possible to perform `clustered` operations with them. runTestNodes ports= do nodes <- onAll $ mapM (\p -> liftIO $ createNode "localhost" p) ports foldl (<|>) empty (map listen nodes) <|> return()