Engine.icl 9.5 KB
Newer Older
1
implementation module iTasks.Engine
2

3
import StdMisc, StdArray, StdList, StdOrdList, StdTuple, StdChar, StdFile, StdBool, StdEnum
4
from StdFunc import o, seqList, ::St, const, id
5
from Data.Map import :: Map
6
from Data.Queue import :: Queue(..)
7
import qualified Data.Map as DM
8
import Data.List, Data.Error, Data.Func, Data.Tuple, Math.Random, Text 
9
import System.Time, System.CommandLine, System.Environment, System.OSError, System.File, System.FilePath, System.Directory
10 11 12
import iTasks.Internal.Util, iTasks.Internal.HtmlUtil
import iTasks.Internal.IWorld, iTasks.Internal.WebService, iTasks.Internal.SDSService
import qualified iTasks.Internal.SDS as SDS
13
import iTasks.UI.Layout, iTasks.UI.Layout.Default
14

15
from iTasks.WF.Combinators.Tune import class tune(..), instance tune ApplyLayout Task, :: ApplyLayout(..)
16
from iTasks.SDS.Combinators.Common import sdsFocus
17

18
import StdInt, StdChar, StdString
19 20
import tcp
import Internet.HTTP, System.Time, System.CommandLine, Data.Func
21

22
import iTasks.Internal.IWorld, iTasks.Internal.TaskEval, iTasks.Internal.TaskStore
23 24
import iTasks.Internal.Util
import iTasks.Internal.TaskServer
25 26
import iTasks.Internal.EngineTasks

27
from iTasks.Extensions.DateTime import toDate, toTime, instance == Date, instance == Time
28

29 30 31
from Data.Set import :: Set, newSet
from Sapl.Linker.LazyLinker import generateLoaderState, :: LoaderStateExt
from Sapl.Linker.SaplLinkerShared import :: SkipSet
32
from Sapl.Target.Flavour import :: Flavour, toFlavour
33

34
from System.OS import IF_POSIX_OR_WINDOWS
35

36 37 38 39 40 41 42 43
MAX_EVENTS 		        :== 5

defaultEngineOptions :: !*World -> (!EngineOptions,!*World)
defaultEngineOptions world
	# (appPath,world)    = determineAppPath world	
	# (appVersion,world) = determineAppVersion appPath world
	# appDir             = takeDirectory appPath
	# appName            = (dropExtension o dropDirectory) appPath
44
	# options =	
45
		{ appName			= appName
46
		, appPath			= appPath
47 48 49 50
        , appVersion        = appVersion
		, serverPort		= IF_POSIX_OR_WINDOWS 8080 80
        , serverUrl         = "http://localhost/"
		, keepaliveTime     = 300 // 5 minutes
51
		, sessionTime       = 60 // 1 minute, (the client pings every 10 seconds by default)
Bas Lijnse's avatar
Bas Lijnse committed
52 53
        , persistTasks      = False
		, autoLayout        = True
54 55 56 57 58 59 60 61 62 63
		, webDirPath 		= appDir </> appName +++ "-www"
		, storeDirPath      = appDir </> appName +++ "-data" </> "stores"
		, tempDirPath       = appDir </> appName +++ "-data" </> "tmp"
		, saplDirPath 	    = appDir </> appName +++ "-sapl"
		}
	= (options,world)

defaultEngineCLIOptions :: [String] EngineOptions -> (!Maybe EngineOptions,![String])
defaultEngineCLIOptions cli defaults
	//If -help option is given show help and stop
64 65 66
	# help					= fromMaybe False (boolOpt "-help" "-no-help" cli)
	| help					= (Nothing, instructions defaults)
	//Check commandline options
67 68
	# options =	
		{ defaults 
69 70 71 72 73
		& serverPort		= fromMaybe defaults.serverPort (intOpt "-port" cli)
		, webDirPath 		= fromMaybe defaults.webDirPath (stringOpt "-webdir" cli)
		, storeDirPath      = fromMaybe defaults.storeDirPath (stringOpt "-storedir" cli)
		, tempDirPath 		= fromMaybe defaults.webDirPath (stringOpt "-tempdir" cli)
		, saplDirPath 	    = fromMaybe defaults.saplDirPath (stringOpt "-sapldir" cli)
74
		}
75
	= (Just options,running options.appName options.serverPort)
76
where
77 78
	instructions :: EngineOptions -> [String]
	instructions {serverPort,webDirPath,storeDirPath,tempDirPath,saplDirPath} =
79
		["Available commandline options:"
80 81 82 83 84 85 86 87 88 89
		," -help            : Show this message and exit"
		," -port <port>     : Listen on TCP port number (default " +++ toString serverPort +++ ")"
		," -webdir <path>   : Use <path> to point to the folder that contain the application's static web content"
		,"                  : (default "+++ webDirPath +++ ")"
	    ," -storedir <path> : Use <path> as data store location"
		,"                  : (default " +++ storeDirPath +++ ")"
	    ," -tempdir <path>  : Use <path> as temporary file location"
		,"                  : (default " +++ tempDirPath +++ ")"
	    ," -sapldir <path>  : Use <path> to point to the folder that contains the sapl version of the application"
		,"                  : (default "+++ saplDirPath +++ ")"
90 91
		,""
		]
92

93 94 95 96 97
	running :: !String !Int -> [String]
	running app port = ["*** " +++ app +++ " HTTP server ***"
                       ,""
                       ,"Running at http://localhost" +++ (if (port == 80) "/" (":" +++ toString port +++ "/"))]

98 99 100 101 102
	boolOpt :: !String !String ![String] -> Maybe Bool
	boolOpt trueKey falseKey opts
		| isMember trueKey opts = Just True
		| isMember falseKey opts = Just False
		                         = Nothing
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
	
	intOpt :: !String ![String] -> Maybe Int
	intOpt key []	= Nothing
	intOpt key [_]	= Nothing
	intOpt key [n,v:r]
		| n == key && isInteger v	= Just (toInt v)
									= intOpt key [v:r]
	where								
		isInteger v = and (map isDigit (fromString v))

	stringOpt :: !String [String] -> Maybe String
	stringOpt key [] = Nothing
	stringOpt key [_] = Nothing
	stringOpt key [n,v:r]
		| n == key	= Just v
					= stringOpt key [v:r]
119

120
startEngine :: a !*World -> *World | Publishable a
121 122 123 124 125 126 127 128 129 130 131 132 133 134
startEngine publishable world = startEngineWithOptions defaultEngineCLIOptions publishable world

startEngineWithOptions :: ([String] EngineOptions -> (!Maybe EngineOptions,![String])) a !*World -> *World | Publishable a
startEngineWithOptions initFun publishable world
	# (cli,world)			= getCommandLine world
	# (options,world)       = defaultEngineOptions world
	# (mbOptions,msg)       = initFun cli options
	# world                 = show msg world
	= case mbOptions of
		Nothing = world
		Just options
 			# iworld				= createIWorld (fromJust mbOptions) world
 			# (res,iworld) 			= initJSCompilerState iworld
		 	| res =:(Error _) 		= show ["Fatal error: " +++ fromError res] (destroyIWorld iworld)
135
			# iworld				= serve [] (tcpTasks options.serverPort options.keepaliveTime) engineTasks timeout iworld
136
			= destroyIWorld iworld
137
where
138
	tcpTasks serverPort keepaliveTime = [(serverPort,httpServer serverPort keepaliveTime (engineWebService publishable) taskOutput)]
139
	engineTasks =
140
 		[BackgroundTask updateClock
141
		,BackgroundTask (processEvents MAX_EVENTS)
Bas Lijnse's avatar
Bas Lijnse committed
142 143
		,BackgroundTask removeOutdatedSessions
		,BackgroundTask flushWritesWhenIdle]
144

145
runTasks :: a !*World -> *World | Runnable a
146 147 148 149 150 151 152 153 154 155
runTasks tasks world = runTasksWithOptions (\c o -> (Just o,[])) tasks world

runTasksWithOptions :: ([String] EngineOptions -> (!Maybe EngineOptions,![String])) a !*World -> *World | Runnable a
runTasksWithOptions initFun runnable world
	# (cli,world)			= getCommandLine world
	# (options,world)       = defaultEngineOptions world
	# (mbOptions,msg)       = initFun cli options
	# world                 = show msg world
	| mbOptions =: Nothing  = world
 	# iworld				= createIWorld (fromJust mbOptions) world
156 157
 	# (res,iworld) 			= initJSCompilerState iworld
 	| res =:(Error _) 		= show ["Fatal error: " +++ fromError res] (destroyIWorld iworld)
158
	# iworld				= serve (toRunnable runnable) [] systemTasks timeout iworld
159
	= destroyIWorld iworld
160 161
where
	systemTasks =
162
 		[BackgroundTask updateClock
163 164
		,BackgroundTask (processEvents MAX_EVENTS)
		,BackgroundTask stopOnStable]
165

166 167 168 169 170 171
show :: ![String] !*World -> *World
show lines world
	# (console,world)	= stdio world
	# console			= seqSt (\s c -> fwrites (s +++ "\n") c) lines console
	# (_,world)			= fclose console world
	= world
172

173
// The iTasks engine consist of a set of HTTP WebService 
174
engineWebService :: publish -> [WebService (Map InstanceNo TaskOutput) (Map InstanceNo TaskOutput)] | Publishable publish
175
engineWebService publishable = [taskUIService published, documentService, sdsService, staticResourceService [url \\ {PublishedTask|url} <- published]]
176
where
177
	published = publishAll publishable 
178

179
publish :: String (HTTPRequest -> Task a) -> PublishedTask | iTask a
Bas Lijnse's avatar
Bas Lijnse committed
180
publish url task = {url = url, task = WebTaskWrapper task}
181 182 183

instance Publishable (Task a) | iTask a
where
184
	publishAll task = [publish "/" (const task)]
185

Bas Lijnse's avatar
Bas Lijnse committed
186 187
instance Publishable (HTTPRequest -> Task a) | iTask a
where
188
	publishAll task = [publish "/" task]
Bas Lijnse's avatar
Bas Lijnse committed
189
	
190 191 192 193
instance Publishable [PublishedTask]
where
	publishAll list = list

194 195 196 197 198 199 200 201 202 203 204 205
class Runnable a
where
	toRunnable :: !a -> [TaskWrapper] 

instance Runnable (Task a) | iTask a
where
	toRunnable task = [TaskWrapper task]

instance Runnable [TaskWrapper]
where
	toRunnable list = list

ecrombag's avatar
ecrombag committed
206
// Determines the server executables path
207
determineAppPath :: !*World -> (!FilePath, !*World)
ecrombag's avatar
ecrombag committed
208
determineAppPath world
209 210
	# ([arg:_],world) = getCommandLine world 
	| dropDirectory arg <> "ConsoleClient.exe"	= toCanonicalPath arg world
211 212 213 214 215 216 217 218 219 220 221 222 223
	//Using dynamic linker:	
	# (res, world)				= getCurrentDirectory world	
	| isError res				= abort "Cannot get current directory."	
	# currentDirectory			= fromOk res
	# (res, world)				= readDirectory currentDirectory world	
	| isError res				= abort "Cannot read current directory."	
	# batchfiles				= [f \\ f <- fromOk res | takeExtension f == "bat" ]
	| isEmpty batchfiles		= abort "No dynamic linker batch file found."	
	# (infos, world)			= seqList (map getFileInfo batchfiles) world	
	| any isError infos	 		= abort "Cannot get file information."	
	= (currentDirectory </> (fst o hd o sortBy cmpFileTime) (zip2 batchfiles infos), world)	
	where		
		cmpFileTime (_,Ok {FileInfo | lastModifiedTime = x})
224
					(_,Ok {FileInfo | lastModifiedTime = y}) = timeGm x > timeGm y
225 226 227 228 229 230 231 232 233 234

//By default, we use the modification time of the applaction executable as version id
determineAppVersion :: !FilePath!*World -> (!String,!*World)	
determineAppVersion appPath world
	# (res,world)       = getFileInfo appPath world
	| res =: (Error _)  = ("unknown",world) 
	# tm				= (fromOk res).lastModifiedTime
	# version           = strfTime "%Y%m%d-%H%M%S" tm
	= (version,world)