Forráskód Böngészése

Added serverless functions

Toby Chui 2 éve
szülő
commit
8c15fb1520
4 módosított fájl, 823 hozzáadás és 749 törlés
  1. 438 434
      mod/agi/agi.go
  2. 70 0
      mod/agi/agi.serverless.go
  3. 3 3
      mod/agi/external.agi.go
  4. 312 312
      mod/time/scheduler/scheduler.go

+ 438 - 434
mod/agi/agi.go

@@ -1,434 +1,438 @@
-package agi
-
-import (
-	"encoding/json"
-	"errors"
-	"io"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"os"
-	"path/filepath"
-	"strings"
-	"time"
-
-	"github.com/robertkrimen/otto"
-	uuid "github.com/satori/go.uuid"
-
-	apt "imuslab.com/arozos/mod/apt"
-	"imuslab.com/arozos/mod/filesystem"
-	metadata "imuslab.com/arozos/mod/filesystem/metadata"
-	"imuslab.com/arozos/mod/iot"
-	"imuslab.com/arozos/mod/share"
-	"imuslab.com/arozos/mod/time/nightly"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	ArOZ Online Javascript Gateway Interface (AGI)
-	author: tobychui
-
-	This script load plugins written in Javascript and run them in VM inside golang
-	DO NOT CONFUSE PLUGIN WITH SUBSERVICE :))
-*/
-
-var (
-	AgiVersion string = "2.0" //Defination of the agi runtime version. Update this when new function is added
-
-	//AGI Internal Error Standard
-	exitcall  = errors.New("Exit")
-	timelimit = errors.New("Timelimit")
-)
-
-type AgiLibIntergface func(*otto.Otto, *user.User) //Define the lib loader interface for AGI Libraries
-type AgiPackage struct {
-	InitRoot string //The initialization of the root for the module that request this package
-}
-
-type AgiSysInfo struct {
-	//System information
-	BuildVersion    string
-	InternalVersion string
-	LoadedModule    []string
-
-	//System Handlers
-	UserHandler          *user.UserHandler
-	ReservedTables       []string
-	PackageManager       *apt.AptPackageManager
-	ModuleRegisterParser func(string) error
-	FileSystemRender     *metadata.RenderHandler
-	IotManager           *iot.Manager
-	ShareManager         *share.Manager
-	NightlyManager       *nightly.TaskManager
-
-	//Scanning Roots
-	StartupRoot    string
-	ActivateScope  []string
-	TempFolderPath string
-}
-
-type Gateway struct {
-	ReservedTables   []string
-	NightlyScripts   []string
-	AllowAccessPkgs  map[string][]AgiPackage
-	LoadedAGILibrary map[string]AgiLibIntergface
-	Option           *AgiSysInfo
-}
-
-func NewGateway(option AgiSysInfo) (*Gateway, error) {
-	//Handle startup registration of ajgi modules
-	gatewayObject := Gateway{
-		ReservedTables:   option.ReservedTables,
-		NightlyScripts:   []string{},
-		AllowAccessPkgs:  map[string][]AgiPackage{},
-		LoadedAGILibrary: map[string]AgiLibIntergface{},
-		Option:           &option,
-	}
-
-	//Start all WebApps Registration
-	gatewayObject.InitiateAllWebAppModules()
-	gatewayObject.RegisterNightlyOperations()
-
-	//Load all the other libs entry points into the memoary
-	gatewayObject.ImageLibRegister()
-	gatewayObject.FileLibRegister()
-	gatewayObject.HTTPLibRegister()
-	gatewayObject.ShareLibRegister()
-	gatewayObject.IoTLibRegister()
-	gatewayObject.AppdataLibRegister()
-
-	return &gatewayObject, nil
-}
-
-func (g *Gateway) RegisterNightlyOperations() {
-	g.Option.NightlyManager.RegisterNightlyTask(func() {
-		//This function will execute nightly
-		for _, scriptFile := range g.NightlyScripts {
-			if isValidAGIScript(scriptFile) {
-				//Valid script file. Execute it with system
-				for _, username := range g.Option.UserHandler.GetAuthAgent().ListUsers() {
-					userinfo, err := g.Option.UserHandler.GetUserInfoFromUsername(username)
-					if err != nil {
-						continue
-					}
-
-					if checkUserAccessToScript(userinfo, scriptFile, "") {
-						//This user can access the module that provide this script.
-						//Execute this script on his account.
-						log.Println("[AGI_Nightly] WIP (" + scriptFile + ")")
-					}
-				}
-			} else {
-				//Invalid script. Skipping
-				log.Println("[AGI_Nightly] Invalid script file: " + scriptFile)
-			}
-		}
-	})
-}
-
-func (g *Gateway) InitiateAllWebAppModules() {
-	startupScripts, _ := filepath.Glob(filepath.ToSlash(filepath.Clean(g.Option.StartupRoot)) + "/*/init.agi")
-	for _, script := range startupScripts {
-		scriptContentByte, _ := ioutil.ReadFile(script)
-		scriptContent := string(scriptContentByte)
-		log.Println("[AGI] Gateway script loaded (" + script + ")")
-		//Create a new vm for this request
-		vm := otto.New()
-
-		//Only allow non user based operations
-		g.injectStandardLibs(vm, script, "./web/")
-
-		_, err := vm.Run(scriptContent)
-		if err != nil {
-			log.Println("[AGI] Load Failed: " + script + ". Skipping.")
-			log.Println(err)
-			continue
-		}
-	}
-}
-
-func (g *Gateway) RunScript(script string) error {
-	//Create a new vm for this request
-	vm := otto.New()
-
-	//Only allow non user based operations
-	g.injectStandardLibs(vm, "", "./web/")
-
-	_, err := vm.Run(script)
-	if err != nil {
-		log.Println("[AGI] Script Execution Failed: ", err.Error())
-		return err
-	}
-
-	return nil
-}
-
-func (g *Gateway) RegisterLib(libname string, entryPoint AgiLibIntergface) error {
-	_, ok := g.LoadedAGILibrary[libname]
-	if ok {
-		//This lib already registered. Return error
-		return errors.New("This library name already registered")
-	} else {
-		g.LoadedAGILibrary[libname] = entryPoint
-	}
-	return nil
-}
-
-func (g *Gateway) raiseError(err error) {
-	log.Println("[AGI] Runtime Error " + err.Error())
-
-	//To be implemented
-}
-
-//Check if this table is restricted table. Return true if the access is valid
-func (g *Gateway) filterDBTable(tablename string, existsCheck bool) bool {
-	//Check if table is restricted
-	if stringInSlice(tablename, g.ReservedTables) {
-		return false
-	}
-
-	//Check if table exists
-	if existsCheck {
-		if !g.Option.UserHandler.GetDatabase().TableExists(tablename) {
-			return false
-		}
-	}
-
-	return true
-}
-
-//Handle request from RESTFUL API
-func (g *Gateway) APIHandler(w http.ResponseWriter, r *http.Request, thisuser *user.User) {
-	scriptContent, err := mv(r, "script", true)
-	if err != nil {
-		w.WriteHeader(http.StatusBadRequest)
-		w.Write([]byte("400 - Bad Request (Missing script content)"))
-		return
-	}
-	g.ExecuteAGIScript(scriptContent, "", "", w, r, thisuser)
-}
-
-//Handle user requests
-func (g *Gateway) InterfaceHandler(w http.ResponseWriter, r *http.Request, thisuser *user.User) {
-	//Get user object from the request
-	startupRoot := g.Option.StartupRoot
-	startupRoot = filepath.ToSlash(filepath.Clean(startupRoot))
-
-	//Get the script files for the plugin
-	scriptFile, err := mv(r, "script", false)
-	if err != nil {
-		sendErrorResponse(w, "Invalid script path")
-		return
-	}
-	scriptFile = specialURIDecode(scriptFile)
-
-	//Check if the script path exists
-	scriptExists := false
-	scriptScope := "./web/"
-	for _, thisScope := range g.Option.ActivateScope {
-		thisScope = filepath.ToSlash(filepath.Clean(thisScope))
-		if fileExists(thisScope + "/" + scriptFile) {
-			scriptExists = true
-			scriptFile = thisScope + "/" + scriptFile
-			scriptScope = thisScope
-		}
-	}
-
-	if !scriptExists {
-		sendErrorResponse(w, "Script not found")
-		return
-	}
-
-	//Check for user permission on this module
-	moduleName := getScriptRoot(scriptFile, scriptScope)
-	if !thisuser.GetModuleAccessPermission(moduleName) {
-		w.WriteHeader(http.StatusForbidden)
-		if g.Option.BuildVersion == "development" {
-			w.Write([]byte("Permission denied: User do not have permission to access " + moduleName))
-		} else {
-			w.Write([]byte("403 Forbidden"))
-		}
-
-		return
-	}
-
-	//Check the given file is actually agi script
-	if !(filepath.Ext(scriptFile) == ".agi" || filepath.Ext(scriptFile) == ".js") {
-		w.WriteHeader(http.StatusForbidden)
-
-		if g.Option.BuildVersion == "development" {
-			w.Write([]byte("AGI script must have file extension of .agi or .js"))
-		} else {
-			w.Write([]byte("403 Forbidden"))
-		}
-
-		return
-	}
-
-	//Get the content of the script
-	scriptContentByte, _ := ioutil.ReadFile(scriptFile)
-	scriptContent := string(scriptContentByte)
-
-	g.ExecuteAGIScript(scriptContent, scriptFile, scriptScope, w, r, thisuser)
-}
-
-/*
-	Executing the given AGI Script contents. Requires:
-	scriptContent: The AGI command sequence
-	scriptFile: The filepath of the script file
-	scriptScope: The scope of the script file, aka the module base path
-	w / r : Web request and response writer
-	thisuser: userObject
-
-*/
-func (g *Gateway) ExecuteAGIScript(scriptContent string, scriptFile string, scriptScope string, w http.ResponseWriter, r *http.Request, thisuser *user.User) {
-	//Create a new vm for this request
-	vm := otto.New()
-	//Inject standard libs into the vm
-	g.injectStandardLibs(vm, scriptFile, scriptScope)
-	g.injectUserFunctions(vm, scriptFile, scriptScope, thisuser, w, r)
-
-	//Detect cotent type
-	contentType := r.Header.Get("Content-type")
-	if strings.Contains(contentType, "application/json") {
-		//For shitty people who use Angular
-		body, _ := ioutil.ReadAll(r.Body)
-		fields := map[string]interface{}{}
-		json.Unmarshal(body, &fields)
-		for k, v := range fields {
-			vm.Set(k, v)
-		}
-		vm.Set("POST_data", string(body))
-	} else {
-		r.ParseForm()
-		//Insert all paramters into the vm
-		for k, v := range r.PostForm {
-			if len(v) == 1 {
-				vm.Set(k, v[0])
-			} else {
-				vm.Set(k, v)
-			}
-
-		}
-	}
-
-	_, err := vm.Run(scriptContent)
-	if err != nil {
-		scriptpath, _ := filepath.Abs(scriptFile)
-		g.RenderErrorTemplate(w, err.Error(), scriptpath)
-		return
-	}
-
-	//Get the return valu from the script
-	value, err := vm.Get("HTTP_RESP")
-	if err != nil {
-		sendTextResponse(w, "")
-		return
-	}
-	valueString, err := value.ToString()
-
-	//Get respond header type from the vm
-	header, _ := vm.Get("HTTP_HEADER")
-	headerString, _ := header.ToString()
-	if headerString != "" {
-		w.Header().Set("Content-Type", headerString)
-	}
-
-	w.Write([]byte(valueString))
-}
-
-/*
-	Execute AGI script with given user information
-
-*/
-func (g *Gateway) ExecuteAGIScriptAsUser(scriptFile string, targetUser *user.User) (string, error) {
-	//Create a new vm for this request
-	vm := otto.New()
-	//Inject standard libs into the vm
-	g.injectStandardLibs(vm, scriptFile, "")
-	g.injectUserFunctions(vm, scriptFile, "", targetUser, nil, nil)
-
-	//Inject interrupt Channel
-	vm.Interrupt = make(chan func(), 1)
-
-	//Create a panic recovery logic
-	defer func() {
-		if caught := recover(); caught != nil {
-			if caught == timelimit {
-				log.Println("[AGI] Execution timeout: " + scriptFile)
-				return
-			} else if caught == exitcall {
-				//Exit gracefully
-
-				return
-			} else {
-				panic(caught)
-			}
-		}
-	}()
-
-	//Create a max runtime of 5 minutes
-	go func() {
-		time.Sleep(300 * time.Second) // Stop after 300 seconds
-		vm.Interrupt <- func() {
-			panic(timelimit)
-		}
-	}()
-
-	//Try to read the script content
-	scriptContent, err := ioutil.ReadFile(scriptFile)
-	if err != nil {
-		return "", err
-	}
-
-	_, err = vm.Run(scriptContent)
-	if err != nil {
-		return "", err
-	}
-
-	//Get the return value from the script
-	value, err := vm.Get("HTTP_RESP")
-	if err != nil {
-		return "", err
-	}
-
-	valueString, err := value.ToString()
-	return valueString, nil
-}
-
-/*
-
-	Get user specific tmp filepath for buffering remote file. Return filepath and closer
-	tempFilepath, closerFunction := g.getUserSpecificTempFilePath(u, "myfile.txt")
-	//Do something with it, after done
-	closerFunction();
-*/
-func (g *Gateway) getUserSpecificTempFilePath(u *user.User, filename string) (string, func()) {
-	uuid := uuid.NewV4().String()
-	tmpFileLocation := filepath.Join(g.Option.TempFolderPath, "agiBuff", u.Username, uuid, filepath.Base(filename))
-	os.MkdirAll(filepath.Dir(tmpFileLocation), 0775)
-	return tmpFileLocation, func() {
-		os.RemoveAll(filepath.Dir(tmpFileLocation))
-	}
-}
-
-/*
-	Buffer remote reosurces to local by fsh and rpath. Return buffer filepath on local device and its closer function
-*/
-func (g *Gateway) bufferRemoteResourcesToLocal(fsh *filesystem.FileSystemHandler, u *user.User, rpath string) (string, func(), error) {
-	buffFile, closerFunc := g.getUserSpecificTempFilePath(u, rpath)
-	f, err := fsh.FileSystemAbstraction.ReadStream(rpath)
-	if err != nil {
-		return "", nil, err
-	}
-	defer f.Close()
-	dest, err := os.OpenFile(buffFile, os.O_CREATE|os.O_RDWR, 0775)
-	if err != nil {
-		return "", nil, err
-	}
-	io.Copy(dest, f)
-	dest.Close()
-	return buffFile, func() {
-		closerFunc()
-	}, nil
-}
+package agi
+
+import (
+	"encoding/json"
+	"errors"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/robertkrimen/otto"
+	uuid "github.com/satori/go.uuid"
+
+	apt "imuslab.com/arozos/mod/apt"
+	"imuslab.com/arozos/mod/filesystem"
+	metadata "imuslab.com/arozos/mod/filesystem/metadata"
+	"imuslab.com/arozos/mod/iot"
+	"imuslab.com/arozos/mod/share"
+	"imuslab.com/arozos/mod/time/nightly"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	ArOZ Online Javascript Gateway Interface (AGI)
+	author: tobychui
+
+	This script load plugins written in Javascript and run them in VM inside golang
+	DO NOT CONFUSE PLUGIN WITH SUBSERVICE :))
+*/
+
+var (
+	AgiVersion string = "2.0" //Defination of the agi runtime version. Update this when new function is added
+
+	//AGI Internal Error Standard
+	exitcall  = errors.New("Exit")
+	timelimit = errors.New("Timelimit")
+)
+
+type AgiLibIntergface func(*otto.Otto, *user.User) //Define the lib loader interface for AGI Libraries
+type AgiPackage struct {
+	InitRoot string //The initialization of the root for the module that request this package
+}
+
+type AgiSysInfo struct {
+	//System information
+	BuildVersion    string
+	InternalVersion string
+	LoadedModule    []string
+
+	//System Handlers
+	UserHandler          *user.UserHandler
+	ReservedTables       []string
+	PackageManager       *apt.AptPackageManager
+	ModuleRegisterParser func(string) error
+	FileSystemRender     *metadata.RenderHandler
+	IotManager           *iot.Manager
+	ShareManager         *share.Manager
+	NightlyManager       *nightly.TaskManager
+
+	//Scanning Roots
+	StartupRoot    string
+	ActivateScope  []string
+	TempFolderPath string
+}
+
+type Gateway struct {
+	ReservedTables   []string
+	NightlyScripts   []string
+	AllowAccessPkgs  map[string][]AgiPackage
+	LoadedAGILibrary map[string]AgiLibIntergface
+	Option           *AgiSysInfo
+}
+
+func NewGateway(option AgiSysInfo) (*Gateway, error) {
+	//Handle startup registration of ajgi modules
+	gatewayObject := Gateway{
+		ReservedTables:   option.ReservedTables,
+		NightlyScripts:   []string{},
+		AllowAccessPkgs:  map[string][]AgiPackage{},
+		LoadedAGILibrary: map[string]AgiLibIntergface{},
+		Option:           &option,
+	}
+
+	//Start all WebApps Registration
+	gatewayObject.InitiateAllWebAppModules()
+	gatewayObject.RegisterNightlyOperations()
+
+	//Load all the other libs entry points into the memoary
+	gatewayObject.ImageLibRegister()
+	gatewayObject.FileLibRegister()
+	gatewayObject.HTTPLibRegister()
+	gatewayObject.ShareLibRegister()
+	gatewayObject.IoTLibRegister()
+	gatewayObject.AppdataLibRegister()
+
+	return &gatewayObject, nil
+}
+
+func (g *Gateway) RegisterNightlyOperations() {
+	g.Option.NightlyManager.RegisterNightlyTask(func() {
+		//This function will execute nightly
+		for _, scriptFile := range g.NightlyScripts {
+			if isValidAGIScript(scriptFile) {
+				//Valid script file. Execute it with system
+				for _, username := range g.Option.UserHandler.GetAuthAgent().ListUsers() {
+					userinfo, err := g.Option.UserHandler.GetUserInfoFromUsername(username)
+					if err != nil {
+						continue
+					}
+
+					if checkUserAccessToScript(userinfo, scriptFile, "") {
+						//This user can access the module that provide this script.
+						//Execute this script on his account.
+						log.Println("[AGI_Nightly] WIP (" + scriptFile + ")")
+					}
+				}
+			} else {
+				//Invalid script. Skipping
+				log.Println("[AGI_Nightly] Invalid script file: " + scriptFile)
+			}
+		}
+	})
+}
+
+func (g *Gateway) InitiateAllWebAppModules() {
+	startupScripts, _ := filepath.Glob(filepath.ToSlash(filepath.Clean(g.Option.StartupRoot)) + "/*/init.agi")
+	for _, script := range startupScripts {
+		scriptContentByte, _ := ioutil.ReadFile(script)
+		scriptContent := string(scriptContentByte)
+		log.Println("[AGI] Gateway script loaded (" + script + ")")
+		//Create a new vm for this request
+		vm := otto.New()
+
+		//Only allow non user based operations
+		g.injectStandardLibs(vm, script, "./web/")
+
+		_, err := vm.Run(scriptContent)
+		if err != nil {
+			log.Println("[AGI] Load Failed: " + script + ". Skipping.")
+			log.Println(err)
+			continue
+		}
+	}
+}
+
+func (g *Gateway) RunScript(script string) error {
+	//Create a new vm for this request
+	vm := otto.New()
+
+	//Only allow non user based operations
+	g.injectStandardLibs(vm, "", "./web/")
+
+	_, err := vm.Run(script)
+	if err != nil {
+		log.Println("[AGI] Script Execution Failed: ", err.Error())
+		return err
+	}
+
+	return nil
+}
+
+func (g *Gateway) RegisterLib(libname string, entryPoint AgiLibIntergface) error {
+	_, ok := g.LoadedAGILibrary[libname]
+	if ok {
+		//This lib already registered. Return error
+		return errors.New("This library name already registered")
+	} else {
+		g.LoadedAGILibrary[libname] = entryPoint
+	}
+	return nil
+}
+
+func (g *Gateway) raiseError(err error) {
+	log.Println("[AGI] Runtime Error " + err.Error())
+
+	//To be implemented
+}
+
+//Check if this table is restricted table. Return true if the access is valid
+func (g *Gateway) filterDBTable(tablename string, existsCheck bool) bool {
+	//Check if table is restricted
+	if stringInSlice(tablename, g.ReservedTables) {
+		return false
+	}
+
+	//Check if table exists
+	if existsCheck {
+		if !g.Option.UserHandler.GetDatabase().TableExists(tablename) {
+			return false
+		}
+	}
+
+	return true
+}
+
+//Handle request from RESTFUL API
+func (g *Gateway) APIHandler(w http.ResponseWriter, r *http.Request, thisuser *user.User) {
+	scriptContent, err := mv(r, "script", true)
+	if err != nil {
+		w.WriteHeader(http.StatusBadRequest)
+		w.Write([]byte("400 - Bad Request (Missing script content)"))
+		return
+	}
+	g.ExecuteAGIScript(scriptContent, "", "", w, r, thisuser)
+}
+
+//Handle user requests
+func (g *Gateway) InterfaceHandler(w http.ResponseWriter, r *http.Request, thisuser *user.User) {
+	//Get user object from the request
+	startupRoot := g.Option.StartupRoot
+	startupRoot = filepath.ToSlash(filepath.Clean(startupRoot))
+
+	//Get the script files for the plugin
+	scriptFile, err := mv(r, "script", false)
+	if err != nil {
+		sendErrorResponse(w, "Invalid script path")
+		return
+	}
+	scriptFile = specialURIDecode(scriptFile)
+
+	//Check if the script path exists
+	scriptExists := false
+	scriptScope := "./web/"
+	for _, thisScope := range g.Option.ActivateScope {
+		thisScope = filepath.ToSlash(filepath.Clean(thisScope))
+		if fileExists(thisScope + "/" + scriptFile) {
+			scriptExists = true
+			scriptFile = thisScope + "/" + scriptFile
+			scriptScope = thisScope
+		}
+	}
+
+	if !scriptExists {
+		sendErrorResponse(w, "Script not found")
+		return
+	}
+
+	//Check for user permission on this module
+	moduleName := getScriptRoot(scriptFile, scriptScope)
+	if !thisuser.GetModuleAccessPermission(moduleName) {
+		w.WriteHeader(http.StatusForbidden)
+		if g.Option.BuildVersion == "development" {
+			w.Write([]byte("Permission denied: User do not have permission to access " + moduleName))
+		} else {
+			w.Write([]byte("403 Forbidden"))
+		}
+
+		return
+	}
+
+	//Check the given file is actually agi script
+	if !(filepath.Ext(scriptFile) == ".agi" || filepath.Ext(scriptFile) == ".js") {
+		w.WriteHeader(http.StatusForbidden)
+
+		if g.Option.BuildVersion == "development" {
+			w.Write([]byte("AGI script must have file extension of .agi or .js"))
+		} else {
+			w.Write([]byte("403 Forbidden"))
+		}
+
+		return
+	}
+
+	//Get the content of the script
+	scriptContentByte, _ := ioutil.ReadFile(scriptFile)
+	scriptContent := string(scriptContentByte)
+
+	g.ExecuteAGIScript(scriptContent, scriptFile, scriptScope, w, r, thisuser)
+}
+
+/*
+	Executing the given AGI Script contents. Requires:
+	scriptContent: The AGI command sequence
+	scriptFile: The filepath of the script file
+	scriptScope: The scope of the script file, aka the module base path
+	w / r : Web request and response writer
+	thisuser: userObject
+
+*/
+func (g *Gateway) ExecuteAGIScript(scriptContent string, scriptFile string, scriptScope string, w http.ResponseWriter, r *http.Request, thisuser *user.User) {
+	//Create a new vm for this request
+	vm := otto.New()
+	//Inject standard libs into the vm
+	g.injectStandardLibs(vm, scriptFile, scriptScope)
+	g.injectUserFunctions(vm, scriptFile, scriptScope, thisuser, w, r)
+
+	//Detect cotent type
+	contentType := r.Header.Get("Content-type")
+	if strings.Contains(contentType, "application/json") {
+		//For shitty people who use Angular
+		body, _ := ioutil.ReadAll(r.Body)
+		fields := map[string]interface{}{}
+		json.Unmarshal(body, &fields)
+		for k, v := range fields {
+			vm.Set(k, v)
+		}
+		vm.Set("POST_data", string(body))
+	} else {
+		r.ParseForm()
+		//Insert all paramters into the vm
+		for k, v := range r.PostForm {
+			if len(v) == 1 {
+				vm.Set(k, v[0])
+			} else {
+				vm.Set(k, v)
+			}
+
+		}
+	}
+
+	_, err := vm.Run(scriptContent)
+	if err != nil {
+		scriptpath, _ := filepath.Abs(scriptFile)
+		g.RenderErrorTemplate(w, err.Error(), scriptpath)
+		return
+	}
+
+	//Get the return valu from the script
+	value, err := vm.Get("HTTP_RESP")
+	if err != nil {
+		sendTextResponse(w, "")
+		return
+	}
+	valueString, err := value.ToString()
+
+	//Get respond header type from the vm
+	header, _ := vm.Get("HTTP_HEADER")
+	headerString, _ := header.ToString()
+	if headerString != "" {
+		w.Header().Set("Content-Type", headerString)
+	}
+
+	w.Write([]byte(valueString))
+}
+
+/*
+	Execute AGI script with given user information
+	Pass in http.Request pointer to enable serverless GET / POST request
+*/
+func (g *Gateway) ExecuteAGIScriptAsUser(scriptFile string, targetUser *user.User, r *http.Request) (string, error) {
+	//Create a new vm for this request
+	vm := otto.New()
+	//Inject standard libs into the vm
+	g.injectStandardLibs(vm, scriptFile, "")
+	g.injectUserFunctions(vm, scriptFile, "", targetUser, nil, nil)
+
+	if r != nil {
+		//Inject serverless script to enable access to GET / POST paramters
+		g.injectServerlessFunctions(vm, scriptFile, "", targetUser, r)
+	}
+	//Inject interrupt Channel
+	vm.Interrupt = make(chan func(), 1)
+
+	//Create a panic recovery logic
+	defer func() {
+		if caught := recover(); caught != nil {
+			if caught == timelimit {
+				log.Println("[AGI] Execution timeout: " + scriptFile)
+				return
+			} else if caught == exitcall {
+				//Exit gracefully
+
+				return
+			} else {
+				panic(caught)
+			}
+		}
+	}()
+
+	//Create a max runtime of 5 minutes
+	go func() {
+		time.Sleep(300 * time.Second) // Stop after 300 seconds
+		vm.Interrupt <- func() {
+			panic(timelimit)
+		}
+	}()
+
+	//Try to read the script content
+	scriptContent, err := ioutil.ReadFile(scriptFile)
+	if err != nil {
+		return "", err
+	}
+
+	_, err = vm.Run(scriptContent)
+	if err != nil {
+		return "", err
+	}
+
+	//Get the return value from the script
+	value, err := vm.Get("HTTP_RESP")
+	if err != nil {
+		return "", err
+	}
+
+	valueString, err := value.ToString()
+	return valueString, nil
+}
+
+/*
+
+	Get user specific tmp filepath for buffering remote file. Return filepath and closer
+	tempFilepath, closerFunction := g.getUserSpecificTempFilePath(u, "myfile.txt")
+	//Do something with it, after done
+	closerFunction();
+*/
+func (g *Gateway) getUserSpecificTempFilePath(u *user.User, filename string) (string, func()) {
+	uuid := uuid.NewV4().String()
+	tmpFileLocation := filepath.Join(g.Option.TempFolderPath, "agiBuff", u.Username, uuid, filepath.Base(filename))
+	os.MkdirAll(filepath.Dir(tmpFileLocation), 0775)
+	return tmpFileLocation, func() {
+		os.RemoveAll(filepath.Dir(tmpFileLocation))
+	}
+}
+
+/*
+	Buffer remote reosurces to local by fsh and rpath. Return buffer filepath on local device and its closer function
+*/
+func (g *Gateway) bufferRemoteResourcesToLocal(fsh *filesystem.FileSystemHandler, u *user.User, rpath string) (string, func(), error) {
+	buffFile, closerFunc := g.getUserSpecificTempFilePath(u, rpath)
+	f, err := fsh.FileSystemAbstraction.ReadStream(rpath)
+	if err != nil {
+		return "", nil, err
+	}
+	defer f.Close()
+	dest, err := os.OpenFile(buffFile, os.O_CREATE|os.O_RDWR, 0775)
+	if err != nil {
+		return "", nil, err
+	}
+	io.Copy(dest, f)
+	dest.Close()
+	return buffFile, func() {
+		closerFunc()
+	}, nil
+}

+ 70 - 0
mod/agi/agi.serverless.go

@@ -0,0 +1,70 @@
+package agi
+
+import (
+	"io/ioutil"
+	"net/http"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/common"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AGI Serverless Request Handler
+
+	This script allow AGI script to access raw GET / POST parameters for serverless applications
+	Author: tobychui
+*/
+
+func (g *Gateway) injectServerlessFunctions(vm *otto.Otto, scriptFile string, scriptScope string, u *user.User, r *http.Request) {
+	vm.Set("REQ_METHOD", r.Method)
+	vm.Set("getPara", func(call otto.FunctionCall) otto.Value {
+		key, _ := call.Argument(0).ToString()
+		if key == "" {
+			return otto.NullValue()
+		}
+		value, err := common.Mv(r, key, false)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		r, err := vm.ToValue(value)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		return r
+	})
+	vm.Set("postPara", func(call otto.FunctionCall) otto.Value {
+		key, _ := call.Argument(0).ToString()
+		if key == "" {
+			return otto.NullValue()
+		}
+		value, err := common.Mv(r, key, true)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		r, err := vm.ToValue(value)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		return r
+	})
+	vm.Set("readBody", func(call otto.FunctionCall) otto.Value {
+		if r.Body == nil {
+			return otto.NullValue()
+		}
+
+		bodyContent, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			return otto.NullValue()
+		}
+		r, err := vm.ToValue(bodyContent)
+		if err != nil {
+			return otto.NullValue()
+		}
+		return r
+	})
+}

+ 3 - 3
mod/agi/external.agi.go

@@ -63,7 +63,7 @@ func (g *Gateway) ExtAPIHandler(w http.ResponseWriter, r *http.Request) {
 	// execute!
 	start := time.Now()
 	//g.ExecuteAGIScript(scriptContent, "", "", w, r, userInfo)
-	result, err := g.ExecuteAGIScriptAsUser(realPath, userInfo)
+	result, err := g.ExecuteAGIScriptAsUser(realPath, userInfo, r)
 	duration := time.Since(start)
 
 	if err != nil {
@@ -104,7 +104,7 @@ func (g *Gateway) AddExternalEndPoint(w http.ResponseWriter, r *http.Request) {
 
 	jsonStr, err := json.Marshal(dat)
 	if err != nil {
-		common.SendErrorResponse(w, "Invalid JSON string: " + err.Error())
+		common.SendErrorResponse(w, "Invalid JSON string: "+err.Error())
 		return
 	}
 	sysdb.Write("external_agi", id, string(jsonStr))
@@ -189,7 +189,7 @@ func (g *Gateway) ListExternalEndpoint(w http.ResponseWriter, r *http.Request) {
 	// marhsal and return
 	returnJson, err := json.Marshal(dataFromDB)
 	if err != nil {
-		common.SendErrorResponse(w, "Invalid JSON: " + err.Error())
+		common.SendErrorResponse(w, "Invalid JSON: "+err.Error())
 		return
 	}
 	sendJSONResponse(w, string(returnJson))

+ 312 - 312
mod/time/scheduler/scheduler.go

@@ -1,312 +1,312 @@
-package scheduler
-
-import (
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"log"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"time"
-
-	"imuslab.com/arozos/mod/agi"
-	"imuslab.com/arozos/mod/user"
-)
-
-/*
-	ArozOS System Scheduler
-	author: tobychui
-
-	This module provide scheduling executable feature for ArozOS
-	Some feature was migrated from the v1.113 aecron module
-*/
-
-type Job struct {
-	Name              string //The name of this job
-	Creator           string //The creator of this job. When execute, this user permission will be used
-	Description       string //Job description, can be empty
-	Admin             bool   //If the creator has admin permission during the creation of this job. If this doesn't match with the runtime instance, this job wille be skipped
-	ExecutionInterval int64  //Execuation interval in seconds
-	BaseTime          int64  //Exeuction basetime. The next interval is calculated using (current time - base time ) % execution interval
-	JobType           string //Job type, accept {file/function}. If not set default to file
-	FshID             string
-	ScriptFile        string                 //The script file being called. Can be an agi script (.agi / .js) or shell script (.bat or .sh)
-	ScriptFunc        func() (string, error) `json:"-"` //The target function to execute
-}
-
-type Scheduler struct {
-	jobs        []*Job
-	cronfile    string
-	userHandler *user.UserHandler
-	gateway     *agi.Gateway
-	ticker      chan bool
-}
-
-var (
-	logFolder string = "./system/aecron/"
-)
-
-func NewScheduler(userHandler *user.UserHandler, gateway *agi.Gateway, cronfile string) (*Scheduler, error) {
-	if !fileExists(cronfile) {
-		//Cronfile not exists. Create it
-		emptyJobList := []*Job{}
-		ls, _ := json.Marshal(emptyJobList)
-		err := ioutil.WriteFile(cronfile, ls, 0755)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	//Load previous jobs from file
-	jobs, err := loadJobsFromFile(cronfile)
-	if err != nil {
-		return nil, err
-	}
-
-	//Create the ArOZ Emulated Crontask
-	thisScheduler := Scheduler{
-		jobs:        jobs,
-		userHandler: userHandler,
-		gateway:     gateway,
-		cronfile:    cronfile,
-	}
-
-	//Create log folder
-	os.MkdirAll(logFolder, 0755)
-
-	//Start the cronjob at 1 minute ticker interval
-	go func() {
-		//Delay start: Wait until seconds = 0
-		for time.Now().Unix()%60 > 0 {
-			time.Sleep(500 * time.Millisecond)
-		}
-		stopChannel := thisScheduler.createTicker(1 * time.Minute)
-		thisScheduler.ticker = stopChannel
-		log.Println("ArozOS System Scheduler Started")
-	}()
-
-	//Return the crontask
-	return &thisScheduler, nil
-}
-
-//Load a list of jobs from file
-func loadJobsFromFile(cronfile string) ([]*Job, error) {
-	//Try to read the cronfile
-	filecontent, err := ioutil.ReadFile(cronfile)
-	if err != nil {
-		return []*Job{}, err
-	}
-
-	//Phrase the cronfile
-	prevousJobs := []Job{}
-	err = json.Unmarshal(filecontent, &prevousJobs)
-	if err != nil {
-		return []*Job{}, err
-	}
-
-	//Convert the json objets to pointer for easy changing by other process
-	jobsPointers := []*Job{}
-	for _, thisJob := range prevousJobs {
-		thisJob.JobType = "file"
-		var newJobPointer Job = thisJob
-		jobsPointers = append(jobsPointers, &newJobPointer)
-	}
-
-	return jobsPointers, nil
-}
-
-func (a *Scheduler) createTicker(duration time.Duration) chan bool {
-	ticker := time.NewTicker(duration)
-	stop := make(chan bool, 1)
-
-	go func() {
-		defer log.Println("Scheduler Stopped")
-		for {
-			select {
-			case <-ticker.C:
-				//Run jobs
-				for _, thisJob := range a.jobs {
-					if (time.Now().Unix()-thisJob.BaseTime)%thisJob.ExecutionInterval == 0 {
-						//Execute this job
-						if thisJob.JobType == "function" {
-							//Execute the script function
-							returnvalue, err := thisJob.ScriptFunc()
-							if err != nil {
-								//Execution error. Kill this scheule
-								log.Println(`*Scheduler* Error occured when running task ` + thisJob.Name + ": " + err.Error())
-								a.RemoveJobFromScheduleList(thisJob.Name)
-								cronlog("[ERROR]: " + err.Error())
-							}
-
-							//Execution suceed. Log the return value
-							if len(returnvalue) > 0 {
-								cronlog(returnvalue)
-							}
-
-						} else {
-							//This is requesting to execute a script file
-							scriptFile := thisJob.ScriptFile
-							if !fileExists(scriptFile) {
-								//This job no longer exists in the file system. Remove it
-								a.RemoveJobFromScheduleList(thisJob.Name)
-							}
-							clonedJobStructure := *thisJob
-							ext := filepath.Ext(scriptFile)
-							if ext == ".js" || ext == ".agi" {
-								//Run using AGI interface in go routine
-								go func(thisJob Job) {
-									userinfo, err := a.userHandler.GetUserInfoFromUsername(thisJob.Creator)
-									if err != nil {
-										//This user not exists. Skip this script
-										cronlog("[ERROR] User not exists: " + thisJob.Creator + ". Skipping scheduled job: " + thisJob.Name + ".")
-										return
-									}
-
-									//Run the script with this user scope
-									resp, err := a.gateway.ExecuteAGIScriptAsUser(thisJob.ScriptFile, userinfo)
-									if err != nil {
-										cronlog("[ERROR] " + thisJob.Name + " " + err.Error())
-									} else {
-										cronlog(thisJob.Name + " " + resp)
-									}
-								}(clonedJobStructure)
-
-							} else if ext == ".bat" || ext == ".sh" {
-								//Run as shell script
-								go func(thisJob Job) {
-									scriptPath := thisJob.ScriptFile
-									if runtime.GOOS == "windows" {
-										scriptPath = strings.ReplaceAll(filepath.ToSlash(scriptPath), "/", "\\")
-									}
-									cmd := exec.Command(scriptPath)
-									out, err := cmd.CombinedOutput()
-									if err != nil {
-										cronlog("[ERROR] " + thisJob.Name + " " + err.Error() + " => " + string(out))
-									}
-									cronlog(thisJob.Name + " " + string(out))
-								}(clonedJobStructure)
-							} else {
-								//Unknown script file. Ignore this
-								log.Println("This extension is not yet supported: ", ext)
-							}
-						}
-
-					}
-				}
-			case <-stop:
-				return
-			}
-		}
-	}()
-
-	return stop
-}
-
-func (a *Scheduler) Close() {
-	if a.ticker != nil {
-		//Stop the ticker
-		a.ticker <- true
-	}
-}
-
-//Add an job object to system scheduler
-func (a *Scheduler) AddJobToScheduler(job *Job) error {
-	if job.JobType == "" {
-		if job.ScriptFunc == nil && job.ScriptFile == "" {
-			return errors.New("Invalid job file or function")
-		}
-
-		if job.ScriptFunc != nil {
-			job.JobType = "function"
-		} else if job.ScriptFile != "" {
-			job.JobType = "file"
-		}
-
-	}
-	a.jobs = append(a.jobs, job)
-	return nil
-}
-
-//Create a new scheduled function job in the scheduler
-func (a *Scheduler) CreateNewScheduledFunctionJob(name string, desc string, executionInterval int64, targetFunction func() (string, error)) error {
-	if name == "" || desc == "" {
-		return errors.New("Name or description of a scheduled task cannot be empty")
-	}
-
-	if executionInterval < 60 {
-		return errors.New("The minimum execution interval is 60 seconds.")
-	}
-
-	//Get the cloest minute
-	baseTime := time.Now().Unix() - (time.Now().Unix() % 60)
-
-	//Create a new scehduled job
-	newJob := Job{
-		Name:              name,
-		Creator:           "system",
-		Description:       desc,
-		Admin:             true,
-		ExecutionInterval: executionInterval,
-		BaseTime:          baseTime,
-		JobType:           "function",
-		ScriptFunc:        targetFunction,
-	}
-
-	//Add the new job to scheduler
-	a.AddJobToScheduler(&newJob)
-
-	return nil
-}
-
-func (a *Scheduler) GetScheduledJobByName(name string) *Job {
-	for _, thisJob := range a.jobs {
-		if thisJob.Name == name {
-			return thisJob
-		}
-	}
-
-	return nil
-}
-
-func (a *Scheduler) RemoveJobFromScheduleList(taskName string) {
-	newJobSlice := []*Job{}
-	for _, j := range a.jobs {
-		if j.Name != taskName {
-			thisJob := j
-			newJobSlice = append(newJobSlice, thisJob)
-		}
-	}
-	a.jobs = newJobSlice
-}
-
-func (a *Scheduler) JobExists(name string) bool {
-	targetJob := a.GetScheduledJobByName(name)
-	if targetJob == nil {
-		return false
-	} else {
-		return true
-	}
-}
-
-//Write the output to log file. Default to ./system/aecron/{date}.log
-func cronlog(message string) {
-	currentTime := time.Now()
-	timestamp := currentTime.Format("2006-01-02 15:04:05")
-	message = timestamp + " " + message
-	currentLogFile := filepath.ToSlash(filepath.Clean(logFolder)) + "/" + time.Now().Format("2006-02-01") + ".log"
-	f, err := os.OpenFile(currentLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
-	if err != nil {
-		//Unable to write to file. Log to STDOUT instead
-		log.Println(message)
-		return
-	}
-	if _, err := f.WriteString(message + "\n"); err != nil {
-		log.Println(message)
-		return
-	}
-	defer f.Close()
-
-}
+package scheduler
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"time"
+
+	"imuslab.com/arozos/mod/agi"
+	"imuslab.com/arozos/mod/user"
+)
+
+/*
+	ArozOS System Scheduler
+	author: tobychui
+
+	This module provide scheduling executable feature for ArozOS
+	Some feature was migrated from the v1.113 aecron module
+*/
+
+type Job struct {
+	Name              string //The name of this job
+	Creator           string //The creator of this job. When execute, this user permission will be used
+	Description       string //Job description, can be empty
+	Admin             bool   //If the creator has admin permission during the creation of this job. If this doesn't match with the runtime instance, this job wille be skipped
+	ExecutionInterval int64  //Execuation interval in seconds
+	BaseTime          int64  //Exeuction basetime. The next interval is calculated using (current time - base time ) % execution interval
+	JobType           string //Job type, accept {file/function}. If not set default to file
+	FshID             string
+	ScriptFile        string                 //The script file being called. Can be an agi script (.agi / .js) or shell script (.bat or .sh)
+	ScriptFunc        func() (string, error) `json:"-"` //The target function to execute
+}
+
+type Scheduler struct {
+	jobs        []*Job
+	cronfile    string
+	userHandler *user.UserHandler
+	gateway     *agi.Gateway
+	ticker      chan bool
+}
+
+var (
+	logFolder string = "./system/aecron/"
+)
+
+func NewScheduler(userHandler *user.UserHandler, gateway *agi.Gateway, cronfile string) (*Scheduler, error) {
+	if !fileExists(cronfile) {
+		//Cronfile not exists. Create it
+		emptyJobList := []*Job{}
+		ls, _ := json.Marshal(emptyJobList)
+		err := ioutil.WriteFile(cronfile, ls, 0755)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	//Load previous jobs from file
+	jobs, err := loadJobsFromFile(cronfile)
+	if err != nil {
+		return nil, err
+	}
+
+	//Create the ArOZ Emulated Crontask
+	thisScheduler := Scheduler{
+		jobs:        jobs,
+		userHandler: userHandler,
+		gateway:     gateway,
+		cronfile:    cronfile,
+	}
+
+	//Create log folder
+	os.MkdirAll(logFolder, 0755)
+
+	//Start the cronjob at 1 minute ticker interval
+	go func() {
+		//Delay start: Wait until seconds = 0
+		for time.Now().Unix()%60 > 0 {
+			time.Sleep(500 * time.Millisecond)
+		}
+		stopChannel := thisScheduler.createTicker(1 * time.Minute)
+		thisScheduler.ticker = stopChannel
+		log.Println("ArozOS System Scheduler Started")
+	}()
+
+	//Return the crontask
+	return &thisScheduler, nil
+}
+
+//Load a list of jobs from file
+func loadJobsFromFile(cronfile string) ([]*Job, error) {
+	//Try to read the cronfile
+	filecontent, err := ioutil.ReadFile(cronfile)
+	if err != nil {
+		return []*Job{}, err
+	}
+
+	//Phrase the cronfile
+	prevousJobs := []Job{}
+	err = json.Unmarshal(filecontent, &prevousJobs)
+	if err != nil {
+		return []*Job{}, err
+	}
+
+	//Convert the json objets to pointer for easy changing by other process
+	jobsPointers := []*Job{}
+	for _, thisJob := range prevousJobs {
+		thisJob.JobType = "file"
+		var newJobPointer Job = thisJob
+		jobsPointers = append(jobsPointers, &newJobPointer)
+	}
+
+	return jobsPointers, nil
+}
+
+func (a *Scheduler) createTicker(duration time.Duration) chan bool {
+	ticker := time.NewTicker(duration)
+	stop := make(chan bool, 1)
+
+	go func() {
+		defer log.Println("Scheduler Stopped")
+		for {
+			select {
+			case <-ticker.C:
+				//Run jobs
+				for _, thisJob := range a.jobs {
+					if (time.Now().Unix()-thisJob.BaseTime)%thisJob.ExecutionInterval == 0 {
+						//Execute this job
+						if thisJob.JobType == "function" {
+							//Execute the script function
+							returnvalue, err := thisJob.ScriptFunc()
+							if err != nil {
+								//Execution error. Kill this scheule
+								log.Println(`*Scheduler* Error occured when running task ` + thisJob.Name + ": " + err.Error())
+								a.RemoveJobFromScheduleList(thisJob.Name)
+								cronlog("[ERROR]: " + err.Error())
+							}
+
+							//Execution suceed. Log the return value
+							if len(returnvalue) > 0 {
+								cronlog(returnvalue)
+							}
+
+						} else {
+							//This is requesting to execute a script file
+							scriptFile := thisJob.ScriptFile
+							if !fileExists(scriptFile) {
+								//This job no longer exists in the file system. Remove it
+								a.RemoveJobFromScheduleList(thisJob.Name)
+							}
+							clonedJobStructure := *thisJob
+							ext := filepath.Ext(scriptFile)
+							if ext == ".js" || ext == ".agi" {
+								//Run using AGI interface in go routine
+								go func(thisJob Job) {
+									userinfo, err := a.userHandler.GetUserInfoFromUsername(thisJob.Creator)
+									if err != nil {
+										//This user not exists. Skip this script
+										cronlog("[ERROR] User not exists: " + thisJob.Creator + ". Skipping scheduled job: " + thisJob.Name + ".")
+										return
+									}
+
+									//Run the script with this user scope
+									resp, err := a.gateway.ExecuteAGIScriptAsUser(thisJob.ScriptFile, userinfo, nil)
+									if err != nil {
+										cronlog("[ERROR] " + thisJob.Name + " " + err.Error())
+									} else {
+										cronlog(thisJob.Name + " " + resp)
+									}
+								}(clonedJobStructure)
+
+							} else if ext == ".bat" || ext == ".sh" {
+								//Run as shell script
+								go func(thisJob Job) {
+									scriptPath := thisJob.ScriptFile
+									if runtime.GOOS == "windows" {
+										scriptPath = strings.ReplaceAll(filepath.ToSlash(scriptPath), "/", "\\")
+									}
+									cmd := exec.Command(scriptPath)
+									out, err := cmd.CombinedOutput()
+									if err != nil {
+										cronlog("[ERROR] " + thisJob.Name + " " + err.Error() + " => " + string(out))
+									}
+									cronlog(thisJob.Name + " " + string(out))
+								}(clonedJobStructure)
+							} else {
+								//Unknown script file. Ignore this
+								log.Println("This extension is not yet supported: ", ext)
+							}
+						}
+
+					}
+				}
+			case <-stop:
+				return
+			}
+		}
+	}()
+
+	return stop
+}
+
+func (a *Scheduler) Close() {
+	if a.ticker != nil {
+		//Stop the ticker
+		a.ticker <- true
+	}
+}
+
+//Add an job object to system scheduler
+func (a *Scheduler) AddJobToScheduler(job *Job) error {
+	if job.JobType == "" {
+		if job.ScriptFunc == nil && job.ScriptFile == "" {
+			return errors.New("Invalid job file or function")
+		}
+
+		if job.ScriptFunc != nil {
+			job.JobType = "function"
+		} else if job.ScriptFile != "" {
+			job.JobType = "file"
+		}
+
+	}
+	a.jobs = append(a.jobs, job)
+	return nil
+}
+
+//Create a new scheduled function job in the scheduler
+func (a *Scheduler) CreateNewScheduledFunctionJob(name string, desc string, executionInterval int64, targetFunction func() (string, error)) error {
+	if name == "" || desc == "" {
+		return errors.New("Name or description of a scheduled task cannot be empty")
+	}
+
+	if executionInterval < 60 {
+		return errors.New("The minimum execution interval is 60 seconds.")
+	}
+
+	//Get the cloest minute
+	baseTime := time.Now().Unix() - (time.Now().Unix() % 60)
+
+	//Create a new scehduled job
+	newJob := Job{
+		Name:              name,
+		Creator:           "system",
+		Description:       desc,
+		Admin:             true,
+		ExecutionInterval: executionInterval,
+		BaseTime:          baseTime,
+		JobType:           "function",
+		ScriptFunc:        targetFunction,
+	}
+
+	//Add the new job to scheduler
+	a.AddJobToScheduler(&newJob)
+
+	return nil
+}
+
+func (a *Scheduler) GetScheduledJobByName(name string) *Job {
+	for _, thisJob := range a.jobs {
+		if thisJob.Name == name {
+			return thisJob
+		}
+	}
+
+	return nil
+}
+
+func (a *Scheduler) RemoveJobFromScheduleList(taskName string) {
+	newJobSlice := []*Job{}
+	for _, j := range a.jobs {
+		if j.Name != taskName {
+			thisJob := j
+			newJobSlice = append(newJobSlice, thisJob)
+		}
+	}
+	a.jobs = newJobSlice
+}
+
+func (a *Scheduler) JobExists(name string) bool {
+	targetJob := a.GetScheduledJobByName(name)
+	if targetJob == nil {
+		return false
+	} else {
+		return true
+	}
+}
+
+//Write the output to log file. Default to ./system/aecron/{date}.log
+func cronlog(message string) {
+	currentTime := time.Now()
+	timestamp := currentTime.Format("2006-01-02 15:04:05")
+	message = timestamp + " " + message
+	currentLogFile := filepath.ToSlash(filepath.Clean(logFolder)) + "/" + time.Now().Format("2006-02-01") + ".log"
+	f, err := os.OpenFile(currentLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		//Unable to write to file. Log to STDOUT instead
+		log.Println(message)
+		return
+	}
+	if _, err := f.WriteString(message + "\n"); err != nil {
+		log.Println(message)
+		return
+	}
+	defer f.Close()
+
+}