Bläddra i källkod

Added partial relative path support in agi

Toby Chui 2 år sedan
förälder
incheckning
e9e5d33bc5
13 ändrade filer med 2622 tillägg och 2551 borttagningar
  1. 12 2
      AGI Documentation.md
  2. 130 130
      mod/agi/agi.appdata.go
  3. 30 29
      mod/agi/agi.audio.go
  4. 901 863
      mod/agi/agi.file.go
  5. 8 7
      mod/agi/agi.go
  6. 263 262
      mod/agi/agi.http.go
  7. 407 406
      mod/agi/agi.image.go
  8. 289 288
      mod/agi/agi.iot.go
  9. 132 131
      mod/agi/agi.share.go
  10. 61 61
      mod/agi/handler.go
  11. 81 72
      mod/agi/static.go
  12. 1 0
      mod/agi/systemFunc.go
  13. 307 300
      mod/agi/userFunc.go

+ 12 - 2
AGI Documentation.md

@@ -511,7 +511,7 @@ To use the user default option which user has set in File Manager WebApp, pass i
 filelib.aglob("user:/Desktop/*.jpg", "user");
 ```
 
-##### Return type of filelib.readdir (Since ArozOS v2.002)
+##### Return type of filelib.readdir (Since ArozOS v2.002 / AGI 2.1)
 
 For filelib.readdir, it will return an array with the following object structure
 
@@ -557,11 +557,21 @@ Example return value (in JSON object, not stringify JSON string)
 ]
 ```
 
+### Relative path support when execute in personal page (Since ArozOS v2.005 / AGI 2.2)
+
+AGI 2.2 filelib support execution of file operations as relative path when the file is executed via personal page link. For example:
+
+```javascript
+var content = filelib.readFile("./untitled.md");
+```
+
+**In other library or use case, please use the full path of resources instead.**
+
 ### appdata
 
 An API for access files inside the web folder. This API only provide read only functions. Include the appdata lib as follows.
 
-```
+```javascript
 requirelib("appdata");
 ```
 

+ 130 - 130
mod/agi/agi.appdata.go

@@ -1,130 +1,130 @@
-package agi
-
-import (
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"log"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/filesystem"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AGI Appdata Access Library
-	Author: tobychui
-
-	This library allow agi script to access files located in the web root
-	*This library provide READ ONLY function*
-	You cannot write to web folder due to security reasons. If you need to read write
-	web root (which is not recommended), ask the user to mount it top web:/ manually
-*/
-
-var webRoot string = "./web" //The web folder root
-
-func (g *Gateway) AppdataLibRegister() {
-	err := g.RegisterLib("appdata", g.injectAppdataLibFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectAppdataLibFunctions(vm *otto.Otto, u *user.User) {
-	vm.Set("_appdata_readfile", func(call otto.FunctionCall) otto.Value {
-		relpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if this is path escape
-		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if escaped {
-			g.raiseError(errors.New("Path escape detected"))
-			return otto.FalseValue()
-		}
-
-		//Check if file exists
-		targetFile := filepath.Join(webRoot, relpath)
-		if fileExists(targetFile) && !filesystem.IsDir(targetFile) {
-			content, err := ioutil.ReadFile(targetFile)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			//OK. Return the content of the file
-			result, _ := vm.ToValue(string(content))
-			return result
-		} else if filesystem.IsDir(targetFile) {
-			g.raiseError(errors.New("Cannot read from directory"))
-			return otto.FalseValue()
-
-		} else {
-			g.raiseError(errors.New("File not exists"))
-			return otto.FalseValue()
-		}
-	})
-
-	vm.Set("_appdata_listdir", func(call otto.FunctionCall) otto.Value {
-		relpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if this is path escape
-		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if escaped {
-			g.raiseError(errors.New("Path escape detected"))
-			return otto.FalseValue()
-		}
-
-		//Check if file exists
-		targetFolder := filepath.Join(webRoot, relpath)
-		if fileExists(targetFolder) && filesystem.IsDir(targetFolder) {
-			//Glob the directory for filelist
-			files, err := filepath.Glob(filepath.ToSlash(filepath.Clean(targetFolder)) + "/*")
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			results := []string{}
-			for _, file := range files {
-				rel, _ := filepath.Rel(webRoot, file)
-				rel = filepath.ToSlash(rel)
-				results = append(results, rel)
-			}
-
-			js, _ := json.Marshal(results)
-
-			//OK. Return the content of the file
-			result, _ := vm.ToValue(string(js))
-			return result
-
-		} else {
-			g.raiseError(errors.New("Directory not exists"))
-			return otto.FalseValue()
-		}
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var appdata = {};
-		appdata.readFile = _appdata_readfile;
-		appdata.listDir = _appdata_listdir;
-	`)
-}
+package agi
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AGI Appdata Access Library
+	Author: tobychui
+
+	This library allow agi script to access files located in the web root
+	*This library provide READ ONLY function*
+	You cannot write to web folder due to security reasons. If you need to read write
+	web root (which is not recommended), ask the user to mount it top web:/ manually
+*/
+
+var webRoot string = "./web" //The web folder root
+
+func (g *Gateway) AppdataLibRegister() {
+	err := g.RegisterLib("appdata", g.injectAppdataLibFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectAppdataLibFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	vm.Set("_appdata_readfile", func(call otto.FunctionCall) otto.Value {
+		relpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if this is path escape
+		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if escaped {
+			g.raiseError(errors.New("Path escape detected"))
+			return otto.FalseValue()
+		}
+
+		//Check if file exists
+		targetFile := filepath.Join(webRoot, relpath)
+		if fileExists(targetFile) && !filesystem.IsDir(targetFile) {
+			content, err := ioutil.ReadFile(targetFile)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			//OK. Return the content of the file
+			result, _ := vm.ToValue(string(content))
+			return result
+		} else if filesystem.IsDir(targetFile) {
+			g.raiseError(errors.New("Cannot read from directory"))
+			return otto.FalseValue()
+
+		} else {
+			g.raiseError(errors.New("File not exists"))
+			return otto.FalseValue()
+		}
+	})
+
+	vm.Set("_appdata_listdir", func(call otto.FunctionCall) otto.Value {
+		relpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if this is path escape
+		escaped, err := g.checkRootEscape(webRoot, filepath.Join(webRoot, relpath))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if escaped {
+			g.raiseError(errors.New("Path escape detected"))
+			return otto.FalseValue()
+		}
+
+		//Check if file exists
+		targetFolder := filepath.Join(webRoot, relpath)
+		if fileExists(targetFolder) && filesystem.IsDir(targetFolder) {
+			//Glob the directory for filelist
+			files, err := filepath.Glob(filepath.ToSlash(filepath.Clean(targetFolder)) + "/*")
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			results := []string{}
+			for _, file := range files {
+				rel, _ := filepath.Rel(webRoot, file)
+				rel = filepath.ToSlash(rel)
+				results = append(results, rel)
+			}
+
+			js, _ := json.Marshal(results)
+
+			//OK. Return the content of the file
+			result, _ := vm.ToValue(string(js))
+			return result
+
+		} else {
+			g.raiseError(errors.New("Directory not exists"))
+			return otto.FalseValue()
+		}
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var appdata = {};
+		appdata.readFile = _appdata_readfile;
+		appdata.listDir = _appdata_listdir;
+	`)
+}

+ 30 - 29
mod/agi/agi.audio.go

@@ -1,29 +1,30 @@
-package agi
-
-import (
-	"log"
-
-	"github.com/robertkrimen/otto"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI Audio Library
-
-	This is a library for allowing audio playback from AGI script
-	Powered by Go Beep and the usage might be a bit tricky
-
-	Author: tobychui
-
-*/
-
-func (g *Gateway) AudioLibRegister() {
-	err := g.RegisterLib("audio", g.injectAudioFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectAudioFunctions(vm *otto.Otto, u *user.User) {
-
-}
+package agi
+
+import (
+	"log"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI Audio Library
+
+	This is a library for allowing audio playback from AGI script
+	Powered by Go Beep and the usage might be a bit tricky
+
+	Author: tobychui
+
+*/
+
+func (g *Gateway) AudioLibRegister() {
+	err := g.RegisterLib("audio", g.injectAudioFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectAudioFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+
+}

+ 901 - 863
mod/agi/agi.file.go

@@ -1,863 +1,901 @@
-package agi
-
-import (
-	"crypto/md5"
-	"encoding/hex"
-	"encoding/json"
-	"errors"
-	"io"
-	"io/fs"
-	"log"
-	"os"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/filesystem/fssort"
-	"imuslab.com/arozos/mod/filesystem/hidden"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI File Processing Library
-
-	This is a library for handling image related functionalities in agi scripts.
-
-	By Alanyueng 2020 <- This person write shitty code that need me to tidy up (by tobychui)
-	Complete rewrite by tobychui in Sept 2020
-*/
-
-func (g *Gateway) FileLibRegister() {
-	err := g.RegisterLib("filelib", g.injectFileLibFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectFileLibFunctions(vm *otto.Otto, u *user.User) {
-
-	//Legacy File system API
-	//writeFile(virtualFilepath, content) => return true/false when succeed / failed
-	vm.Set("_filelib_writeFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		content, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if there is quota for the given length
-		if !u.StorageQuota.HaveSpace(int64(len(content))) {
-			//User have no remaining storage quota
-			g.raiseError(errors.New("Storage Quota Fulled"))
-			return otto.FalseValue()
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if file already exists.
-		if fsh.FileSystemAbstraction.FileExists(rpath) {
-			//Check if this user own this file
-			isOwner := u.IsOwnerOfFile(fsh, vpath)
-			if isOwner {
-				//This user own this system. Remove this file from his quota
-				u.RemoveOwnershipFromFile(fsh, vpath)
-			}
-		}
-
-		//Create and write to file using ioutil
-		err = fsh.FileSystemAbstraction.WriteFile(rpath, []byte(content), 0755)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Add the filesize to user quota
-		u.SetOwnerOfFile(fsh, vpath)
-
-		reply, _ := vm.ToValue(true)
-		return reply
-	})
-
-	vm.Set("_filelib_deleteFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check if file already exists.
-		if fsh.FileSystemAbstraction.FileExists(rpath) {
-			//Check if this user own this file
-			isOwner := u.IsOwnerOfFile(fsh, vpath)
-			if isOwner {
-				//This user own this system. Remove this file from his quota
-				u.RemoveOwnershipFromFile(fsh, vpath)
-			}
-		} else {
-			g.raiseError(errors.New("File not exists"))
-			return otto.FalseValue()
-		}
-
-		//Remove the file
-		fsh.FileSystemAbstraction.Remove(rpath)
-
-		reply, _ := vm.ToValue(true)
-		return reply
-	})
-
-	//readFile(virtualFilepath) => return content in string
-	vm.Set("_filelib_readFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Create and write to file using ioUtil
-		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		reply, _ := vm.ToValue(string(content))
-		return reply
-	})
-
-	//Listdir
-	//readdir("user:/Desktop") => return filelist in array
-	/*
-		vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
-			vpath, err := call.Argument(0).ToString()
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			//Translate the virtual path to realpath
-			fsh, rpath, err := virtualPathToRealPath(vpath, u)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			fshAbs := fsh.FileSystemAbstraction
-
-			rpath = filepath.ToSlash(filepath.Clean(rpath)) + "/*"
-			fileList, err := fshAbs.Glob(rpath)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			//Translate all paths to virtual paths
-			results := []string{}
-			for _, file := range fileList {
-				isHidden, _ := hidden.IsHidden(file, true)
-				if !isHidden {
-					thisRpath, _ := fshAbs.RealPathToVirtualPath(file, u.Username)
-					results = append(results, thisRpath)
-				}
-			}
-
-			reply, _ := vm.ToValue(results)
-			return reply
-		})
-	*/
-
-	//Usage
-	//filelib.walk("user:/") => list everything recursively
-	//filelib.walk("user:/", "folder") => list all folder recursively
-	//filelib.walk("user:/", "file") => list all files recursively
-	vm.Set("_filelib_walk", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		mode, err := call.Argument(1).ToString()
-		if err != nil {
-			mode = "all"
-		}
-
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		results := []string{}
-		fsh.FileSystemAbstraction.Walk(rpath, func(path string, info os.FileInfo, err error) error {
-			if err != nil {
-				//Ignore this error file and continue
-				return nil
-			}
-			thisVpath, err := realpathToVirtualpath(fsh, path, u)
-			if err != nil {
-				return nil
-			}
-			if mode == "file" {
-				if !info.IsDir() {
-					results = append(results, thisVpath)
-				}
-			} else if mode == "folder" {
-				if info.IsDir() {
-					results = append(results, thisVpath)
-				}
-			} else {
-				results = append(results, thisVpath)
-			}
-
-			return nil
-		})
-
-		reply, _ := vm.ToValue(results)
-		return reply
-	})
-
-	//Glob
-	//glob("user:/Desktop/*.mp3") => return fileList in array
-	//glob("/") => return a list of root directories
-	//glob("user:/Desktop/*", "mostRecent") => return fileList in mostRecent sorting mode
-	//glob("user:/Desktop/*", "user") => return fileList in array in user prefered sorting method
-	vm.Set("_filelib_glob", func(call otto.FunctionCall) otto.Value {
-		regex, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		userSortMode, err := call.Argument(1).ToString()
-		if err != nil || userSortMode == "" || userSortMode == "undefined" {
-			userSortMode = "default"
-		}
-
-		//Handle when regex = "." or "./" (listroot)
-		if filepath.ToSlash(filepath.Clean(regex)) == "/" || filepath.Clean(regex) == "." {
-			//List Root
-			rootDirs := []string{}
-			fileHandlers := u.GetAllFileSystemHandler()
-			for _, fsh := range fileHandlers {
-				if fsh.Hierarchy == "backup" {
-
-				} else {
-					rootDirs = append(rootDirs, fsh.UUID+":/")
-				}
-			}
-
-			reply, _ := vm.ToValue(rootDirs)
-			return reply
-		} else {
-			//Check for permission
-			if !u.CanRead(regex) {
-				panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-			}
-			//This function can only handle wildcard in filename but not in dir name
-			vrootPath := filepath.Dir(regex)
-			regexFilename := filepath.Base(regex)
-
-			//Rewrite and validate the sort mode
-			if userSortMode == "user" {
-				//Use user sorting mode.
-				if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
-					g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
-				} else {
-					userSortMode = "default"
-				}
-			}
-
-			if !fssort.SortModeIsSupported(userSortMode) {
-				log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
-				userSortMode = "default"
-			}
-
-			//Translate the virtual path to realpath
-			fsh, rrootPath, err := virtualPathToRealPath(vrootPath, u)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			suitableFiles, err := fsh.FileSystemAbstraction.Glob(filepath.Join(rrootPath, regexFilename))
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			fileList := []string{}
-			fis := []fs.FileInfo{}
-			for _, thisFile := range suitableFiles {
-				fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
-				if err == nil {
-					fileList = append(fileList, thisFile)
-					fis = append(fis, fi)
-				}
-			}
-
-			//Sort the files
-			newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
-
-			//Return the results in virtual paths
-			results := []string{}
-			for _, file := range newFilelist {
-				isHidden, _ := hidden.IsHidden(file, true)
-				if isHidden {
-					//Hidden file. Skip this
-					continue
-				}
-				thisVpath, _ := realpathToVirtualpath(fsh, file, u)
-				results = append(results, thisVpath)
-			}
-			reply, _ := vm.ToValue(results)
-			return reply
-		}
-	})
-
-	//Advance Glob using file system special Glob, cannot use to scan root dirs
-	vm.Set("_filelib_aglob", func(call otto.FunctionCall) otto.Value {
-		regex, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		userSortMode, err := call.Argument(1).ToString()
-		if err != nil || userSortMode == "" || userSortMode == "undefined" {
-			userSortMode = "default"
-		}
-
-		if regex != "/" && !u.CanRead(regex) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		//This function can only handle wildcard in filename but not in dir name
-		vrootPath := filepath.Dir(regex)
-		regexFilename := filepath.Base(regex)
-
-		//Rewrite and validate the sort mode
-		if userSortMode == "user" {
-			//Use user sorting mode.
-			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
-				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
-			} else {
-				userSortMode = "default"
-			}
-		}
-
-		if !fssort.SortModeIsSupported(userSortMode) {
-			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
-			userSortMode = "default"
-		}
-
-		//Translate the virtual path to realpath
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vrootPath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rrootPath, _ := fshAbs.VirtualPathToRealPath(vrootPath, u.Username)
-		suitableFiles, err := fshAbs.Glob(filepath.Join(rrootPath, regexFilename))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		fileList := []string{}
-		fis := []fs.FileInfo{}
-		for _, thisFile := range suitableFiles {
-			fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
-			if err == nil {
-				fileList = append(fileList, thisFile)
-				fis = append(fis, fi)
-			}
-		}
-
-		//Sort the files
-		newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
-
-		//Parse the results (Only extract the filepath)
-		results := []string{}
-		for _, filename := range newFilelist {
-			isHidden, _ := hidden.IsHidden(filename, true)
-			if isHidden {
-				//Hidden file. Skip this
-				continue
-			}
-			thisVpath, _ := realpathToVirtualpath(fsh, filename, u)
-			results = append(results, thisVpath)
-		}
-		reply, _ := vm.ToValue(results)
-		return reply
-	})
-
-	vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		userSortMode, err := call.Argument(1).ToString()
-		if err != nil || userSortMode == "" || userSortMode == "undefined" {
-			userSortMode = "default"
-		}
-
-		//Rewrite and validate the sort mode
-		if userSortMode == "user" {
-			//Use user sorting mode.
-			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath))) {
-				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath)), &userSortMode)
-			} else {
-				userSortMode = "default"
-			}
-		}
-
-		if !fssort.SortModeIsSupported(userSortMode) {
-			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
-			userSortMode = "default"
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		dirEntry, err := fshAbs.ReadDir(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		type fileInfo struct {
-			Filename string
-			Filepath string
-			Ext      string
-			Filesize int64
-			Modtime  int64
-			IsDir    bool
-		}
-
-		//Sort the dirEntry by file info, a bit slow :(
-		if userSortMode != "default" {
-			//Prepare the data structure for sorting
-			newDirEntry := fssort.SortDirEntryList(dirEntry, userSortMode)
-			dirEntry = newDirEntry
-		}
-
-		results := []fileInfo{}
-		for _, de := range dirEntry {
-			isHidden, _ := hidden.IsHidden(de.Name(), false)
-			if isHidden {
-				continue
-			}
-			fstat, _ := de.Info()
-			vpath, _ := realpathToVirtualpath(fsh, filepath.ToSlash(filepath.Join(rpath, de.Name())), u)
-			thisInfo := fileInfo{
-				Filename: de.Name(),
-				Filepath: vpath,
-				Ext:      filepath.Ext(de.Name()),
-				Filesize: fstat.Size(),
-				Modtime:  fstat.ModTime().Unix(),
-				IsDir:    de.IsDir(),
-			}
-
-			results = append(results, thisInfo)
-		}
-
-		js, _ := json.Marshal(results)
-		r, _ := vm.ToValue(string(js))
-		return r
-	})
-
-	//filesize("user:/Desktop/test.txt")
-	vm.Set("_filelib_filesize", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Get filesize of file
-		rawsize := fshAbs.GetFileSize(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		reply, _ := vm.ToValue(rawsize)
-		return reply
-	})
-
-	//fileExists("user:/Desktop/test.txt") => return true / false
-	vm.Set("_filelib_fileExists", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if fshAbs.FileExists(rpath) {
-			return otto.TrueValue()
-		} else {
-			return otto.FalseValue()
-		}
-	})
-
-	//fileExists("user:/Desktop/test.txt") => return true / false
-	vm.Set("_filelib_isDir", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
-		}
-
-		//Translate the virtual path to realpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if _, err := fsh.FileSystemAbstraction.Stat(rpath); os.IsNotExist(err) {
-			//File not exists
-			panic(vm.MakeCustomError("File Not Exists", "Required path not exists"))
-		}
-
-		if fsh.FileSystemAbstraction.IsDir(rpath) {
-			return otto.TrueValue()
-		} else {
-			return otto.FalseValue()
-		}
-	})
-
-	//Make directory command
-	vm.Set("_filelib_mkdir", func(call otto.FunctionCall) otto.Value {
-		vdir, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vdir) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		//Translate the path to realpath
-		fsh, rdir, err := virtualPathToRealPath(vdir, u)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		//Create the directory at rdir location
-		err = fsh.FileSystemAbstraction.MkdirAll(rdir, 0755)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Get MD5 of the given filepath, not implemented
-	vm.Set("_filelib_md5", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		fshAbs := fsh.FileSystemAbstraction
-		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		f, err := fshAbs.ReadStream(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		defer f.Close()
-		h := md5.New()
-		if _, err := io.Copy(h, f); err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		md5Sum := hex.EncodeToString(h.Sum(nil))
-		result, _ := vm.ToValue(md5Sum)
-		return result
-	})
-
-	//Get the root name of the given virtual path root
-	vm.Set("_filelib_rname", func(call otto.FunctionCall) otto.Value {
-		//Get virtual path from the function input
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Get fs handler from the vpath
-		fsHandler, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Return the name of the fsHandler
-		name, _ := vm.ToValue(fsHandler.Name)
-		return name
-
-	})
-
-	vm.Set("_filelib_mtime", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		parseToUnix, err := call.Argument(1).ToBoolean()
-		if err != nil {
-			parseToUnix = false
-		}
-
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		info, err := fsh.FileSystemAbstraction.Stat(rpath)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		modTime := info.ModTime()
-		if parseToUnix {
-			result, _ := otto.ToValue(modTime.Unix())
-			return result
-		} else {
-			result, _ := otto.ToValue(modTime.Format("2006-01-02 15:04:05"))
-			return result
-		}
-	})
-
-	//ArozOS v2.0 New features
-	//Reading or writing from hex to target virtual filepath
-
-	//Write binary from hex string
-	vm.Set("_filelib_writeBinaryFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Check for permission
-		if !u.CanWrite(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		hexContent, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Get the target vpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			log.Println(err.Error())
-			return otto.FalseValue()
-		}
-
-		//Decode the hex content to bytes
-		hexContentInByte, err := hex.DecodeString(hexContent)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Write the file to target file
-		err = fsh.FileSystemAbstraction.WriteFile(rpath, hexContentInByte, 0775)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		return otto.TrueValue()
-
-	})
-
-	//Read file from external fsh. Small file only
-	vm.Set("_filelib_readBinaryFile", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		//Check for permission
-		if !u.CanRead(vpath) {
-			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
-		}
-
-		//Get the target vpath
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		if !fsh.FileSystemAbstraction.FileExists(rpath) {
-			//Check if the target file exists
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.NullValue()
-		}
-
-		hexifiedContent := hex.EncodeToString(content)
-		val, _ := vm.ToValue(hexifiedContent)
-		return val
-
-	})
-
-	//Other file operations, wip
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var filelib = {};
-		filelib.writeFile = _filelib_writeFile;
-		filelib.readFile = _filelib_readFile;
-		filelib.deleteFile = _filelib_deleteFile;
-		filelib.walk = _filelib_walk;
-		filelib.glob = _filelib_glob;
-		filelib.aglob = _filelib_aglob;
-		filelib.filesize = _filelib_filesize;
-		filelib.fileExists = _filelib_fileExists;
-		filelib.isDir = _filelib_isDir;
-		filelib.md5 = _filelib_md5;
-		filelib.mkdir = _filelib_mkdir;
-		filelib.mtime = _filelib_mtime;
-		filelib.rootName = _filelib_rname;
-
-		filelib.readdir = function(path, sortmode){
-			var s = _filelib_readdir(path, sortmode);
-			return JSON.parse(s);
-		};
-	`)
-}
+package agi
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"io"
+	"io/fs"
+	"log"
+	"os"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/fssort"
+	"imuslab.com/arozos/mod/filesystem/hidden"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI File Processing Library
+
+	This is a library for handling image related functionalities in agi scripts.
+
+	By Alanyueng 2020 <- This person write shitty code that need me to tidy up (by tobychui)
+	Complete rewrite by tobychui in Sept 2020
+*/
+
+func (g *Gateway) FileLibRegister() {
+	err := g.RegisterLib("filelib", g.injectFileLibFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectFileLibFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	//writeFile(virtualFilepath, content) => return true/false when succeed / failed
+	vm.Set("_filelib_writeFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanWrite(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		content, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if there is quota for the given length
+		if !u.StorageQuota.HaveSpace(int64(len(content))) {
+			//User have no remaining storage quota
+			g.raiseError(errors.New("Storage Quota Fulled"))
+			return otto.FalseValue()
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if file already exists.
+		if fsh.FileSystemAbstraction.FileExists(rpath) {
+			//Check if this user own this file
+			isOwner := u.IsOwnerOfFile(fsh, vpath)
+			if isOwner {
+				//This user own this system. Remove this file from his quota
+				u.RemoveOwnershipFromFile(fsh, vpath)
+			}
+		}
+
+		//Create and write to file using ioutil
+		err = fsh.FileSystemAbstraction.WriteFile(rpath, []byte(content), 0755)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Add the filesize to user quota
+		u.SetOwnerOfFile(fsh, vpath)
+
+		reply, _ := vm.ToValue(true)
+		return reply
+	})
+
+	vm.Set("_filelib_deleteFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanWrite(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Check if file already exists.
+		if fsh.FileSystemAbstraction.FileExists(rpath) {
+			//Check if this user own this file
+			isOwner := u.IsOwnerOfFile(fsh, vpath)
+			if isOwner {
+				//This user own this system. Remove this file from his quota
+				u.RemoveOwnershipFromFile(fsh, vpath)
+			}
+		} else {
+			g.raiseError(errors.New("File not exists"))
+			return otto.FalseValue()
+		}
+
+		//Remove the file
+		fsh.FileSystemAbstraction.Remove(rpath)
+
+		reply, _ := vm.ToValue(true)
+		return reply
+	})
+
+	//readFile(virtualFilepath) => return content in string
+	vm.Set("_filelib_readFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Create and write to file using ioUtil
+		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		reply, _ := vm.ToValue(string(content))
+		return reply
+	})
+
+	//Listdir
+	//readdir("user:/Desktop") => return filelist in array
+	/*
+		vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
+			vpath, err := call.Argument(0).ToString()
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			//Translate the virtual path to realpath
+			fsh, rpath, err := virtualPathToRealPath(vpath, u)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			fshAbs := fsh.FileSystemAbstraction
+
+			rpath = filepath.ToSlash(filepath.Clean(rpath)) + "/*"
+			fileList, err := fshAbs.Glob(rpath)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			//Translate all paths to virtual paths
+			results := []string{}
+			for _, file := range fileList {
+				isHidden, _ := hidden.IsHidden(file, true)
+				if !isHidden {
+					thisRpath, _ := fshAbs.RealPathToVirtualPath(file, u.Username)
+					results = append(results, thisRpath)
+				}
+			}
+
+			reply, _ := vm.ToValue(results)
+			return reply
+		})
+	*/
+
+	//Usage
+	//filelib.walk("user:/") => list everything recursively
+	//filelib.walk("user:/", "folder") => list all folder recursively
+	//filelib.walk("user:/", "file") => list all files recursively
+	vm.Set("_filelib_walk", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		mode, err := call.Argument(1).ToString()
+		if err != nil {
+			mode = "all"
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		results := []string{}
+		fsh.FileSystemAbstraction.Walk(rpath, func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				//Ignore this error file and continue
+				return nil
+			}
+			thisVpath, err := realpathToVirtualpath(fsh, path, u)
+			if err != nil {
+				return nil
+			}
+			if mode == "file" {
+				if !info.IsDir() {
+					results = append(results, thisVpath)
+				}
+			} else if mode == "folder" {
+				if info.IsDir() {
+					results = append(results, thisVpath)
+				}
+			} else {
+				results = append(results, thisVpath)
+			}
+
+			return nil
+		})
+
+		reply, _ := vm.ToValue(results)
+		return reply
+	})
+
+	//Glob
+	//glob("user:/Desktop/*.mp3") => return fileList in array
+	//glob("/") => return a list of root directories
+	//glob("user:/Desktop/*", "mostRecent") => return fileList in mostRecent sorting mode
+	//glob("user:/Desktop/*", "user") => return fileList in array in user prefered sorting method
+	vm.Set("_filelib_glob", func(call otto.FunctionCall) otto.Value {
+		regex, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		userSortMode, err := call.Argument(1).ToString()
+		if err != nil || userSortMode == "" || userSortMode == "undefined" {
+			userSortMode = "default"
+		}
+
+		//Handle when regex = "." or "./" (listroot)
+		if filepath.ToSlash(filepath.Clean(regex)) == "/" || filepath.Clean(regex) == "." {
+			//List Root
+			rootDirs := []string{}
+			fileHandlers := u.GetAllFileSystemHandler()
+			for _, fsh := range fileHandlers {
+				if fsh.Hierarchy == "backup" {
+
+				} else {
+					rootDirs = append(rootDirs, fsh.UUID+":/")
+				}
+			}
+
+			reply, _ := vm.ToValue(rootDirs)
+			return reply
+		} else {
+			//Check for permission
+			if !u.CanRead(regex) {
+				panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+			}
+			//This function can only handle wildcard in filename but not in dir name
+			vrootPath := filepath.Dir(regex)
+			regexFilename := filepath.Base(regex)
+
+			//Rewrite and validate the sort mode
+			if userSortMode == "user" {
+				//Use user sorting mode.
+				if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
+					g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
+				} else {
+					userSortMode = "default"
+				}
+			}
+
+			if !fssort.SortModeIsSupported(userSortMode) {
+				log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
+				userSortMode = "default"
+			}
+
+			//Translate the virtual path to realpath
+			fsh, rrootPath, err := virtualPathToRealPath(vrootPath, u)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			suitableFiles, err := fsh.FileSystemAbstraction.Glob(filepath.Join(rrootPath, regexFilename))
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			fileList := []string{}
+			fis := []fs.FileInfo{}
+			for _, thisFile := range suitableFiles {
+				fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
+				if err == nil {
+					fileList = append(fileList, thisFile)
+					fis = append(fis, fi)
+				}
+			}
+
+			//Sort the files
+			newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
+
+			//Return the results in virtual paths
+			results := []string{}
+			for _, file := range newFilelist {
+				isHidden, _ := hidden.IsHidden(file, true)
+				if isHidden {
+					//Hidden file. Skip this
+					continue
+				}
+				thisVpath, _ := realpathToVirtualpath(fsh, file, u)
+				results = append(results, thisVpath)
+			}
+			reply, _ := vm.ToValue(results)
+			return reply
+		}
+	})
+
+	//Advance Glob using file system special Glob, cannot use to scan root dirs
+	vm.Set("_filelib_aglob", func(call otto.FunctionCall) otto.Value {
+		regex, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		userSortMode, err := call.Argument(1).ToString()
+		if err != nil || userSortMode == "" || userSortMode == "undefined" {
+			userSortMode = "default"
+		}
+
+		if regex != "/" && !u.CanRead(regex) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		//This function can only handle wildcard in filename but not in dir name
+		vrootPath := filepath.Dir(regex)
+		regexFilename := filepath.Base(regex)
+
+		//Rewrite and validate the sort mode
+		if userSortMode == "user" {
+			//Use user sorting mode.
+			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath))) {
+				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vrootPath)), &userSortMode)
+			} else {
+				userSortMode = "default"
+			}
+		}
+
+		if !fssort.SortModeIsSupported(userSortMode) {
+			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
+			userSortMode = "default"
+		}
+
+		//Translate the virtual path to realpath
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vrootPath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rrootPath, _ := fshAbs.VirtualPathToRealPath(vrootPath, u.Username)
+		suitableFiles, err := fshAbs.Glob(filepath.Join(rrootPath, regexFilename))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		fileList := []string{}
+		fis := []fs.FileInfo{}
+		for _, thisFile := range suitableFiles {
+			fi, err := fsh.FileSystemAbstraction.Stat(thisFile)
+			if err == nil {
+				fileList = append(fileList, thisFile)
+				fis = append(fis, fi)
+			}
+		}
+
+		//Sort the files
+		newFilelist := fssort.SortFileList(fileList, fis, userSortMode)
+
+		//Parse the results (Only extract the filepath)
+		results := []string{}
+		for _, filename := range newFilelist {
+			isHidden, _ := hidden.IsHidden(filename, true)
+			if isHidden {
+				//Hidden file. Skip this
+				continue
+			}
+			thisVpath, _ := realpathToVirtualpath(fsh, filename, u)
+			results = append(results, thisVpath)
+		}
+		reply, _ := vm.ToValue(results)
+		return reply
+	})
+
+	vm.Set("_filelib_readdir", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		userSortMode, err := call.Argument(1).ToString()
+		if err != nil || userSortMode == "" || userSortMode == "undefined" {
+			userSortMode = "default"
+		}
+
+		//Rewrite and validate the sort mode
+		if userSortMode == "user" {
+			//Use user sorting mode.
+			if g.Option.UserHandler.GetDatabase().KeyExists("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath))) {
+				g.Option.UserHandler.GetDatabase().Read("fs-sortpref", u.Username+"/"+filepath.ToSlash(filepath.Clean(vpath)), &userSortMode)
+			} else {
+				userSortMode = "default"
+			}
+		}
+
+		if !fssort.SortModeIsSupported(userSortMode) {
+			log.Println("[AGI] Sort mode: " + userSortMode + " not supported. Using default")
+			userSortMode = "default"
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		dirEntry, err := fshAbs.ReadDir(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		type fileInfo struct {
+			Filename string
+			Filepath string
+			Ext      string
+			Filesize int64
+			Modtime  int64
+			IsDir    bool
+		}
+
+		//Sort the dirEntry by file info, a bit slow :(
+		if userSortMode != "default" {
+			//Prepare the data structure for sorting
+			newDirEntry := fssort.SortDirEntryList(dirEntry, userSortMode)
+			dirEntry = newDirEntry
+		}
+
+		results := []fileInfo{}
+		for _, de := range dirEntry {
+			isHidden, _ := hidden.IsHidden(de.Name(), false)
+			if isHidden {
+				continue
+			}
+			fstat, _ := de.Info()
+			vpath, _ := realpathToVirtualpath(fsh, filepath.ToSlash(filepath.Join(rpath, de.Name())), u)
+			thisInfo := fileInfo{
+				Filename: de.Name(),
+				Filepath: vpath,
+				Ext:      filepath.Ext(de.Name()),
+				Filesize: fstat.Size(),
+				Modtime:  fstat.ModTime().Unix(),
+				IsDir:    de.IsDir(),
+			}
+
+			results = append(results, thisInfo)
+		}
+
+		js, _ := json.Marshal(results)
+		r, _ := vm.ToValue(string(js))
+		return r
+	})
+
+	//filesize("user:/Desktop/test.txt")
+	vm.Set("_filelib_filesize", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Get filesize of file
+		rawsize := fshAbs.GetFileSize(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		reply, _ := vm.ToValue(rawsize)
+		return reply
+	})
+
+	//fileExists("user:/Desktop/test.txt") => return true / false
+	vm.Set("_filelib_fileExists", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if fshAbs.FileExists(rpath) {
+			return otto.TrueValue()
+		} else {
+			return otto.FalseValue()
+		}
+	})
+
+	//fileExists("user:/Desktop/test.txt") => return true / false
+	vm.Set("_filelib_isDir", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
+		}
+
+		//Translate the virtual path to realpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if _, err := fsh.FileSystemAbstraction.Stat(rpath); os.IsNotExist(err) {
+			//File not exists
+			panic(vm.MakeCustomError("File Not Exists", "Required path not exists"))
+		}
+
+		if fsh.FileSystemAbstraction.IsDir(rpath) {
+			return otto.TrueValue()
+		} else {
+			return otto.FalseValue()
+		}
+	})
+
+	//Make directory command
+	vm.Set("_filelib_mkdir", func(call otto.FunctionCall) otto.Value {
+		vdir, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Check for permission
+		if !u.CanWrite(vdir) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		//Translate the path to realpath
+		fsh, rdir, err := virtualPathToRealPath(vdir, u)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		//Create the directory at rdir location
+		err = fsh.FileSystemAbstraction.MkdirAll(rdir, 0755)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Get MD5 of the given filepath, not implemented
+	vm.Set("_filelib_md5", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		fshAbs := fsh.FileSystemAbstraction
+		rpath, err := fshAbs.VirtualPathToRealPath(vpath, u.Username)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		f, err := fshAbs.ReadStream(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		defer f.Close()
+		h := md5.New()
+		if _, err := io.Copy(h, f); err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		md5Sum := hex.EncodeToString(h.Sum(nil))
+		result, _ := vm.ToValue(md5Sum)
+		return result
+	})
+
+	//Get the root name of the given virtual path root
+	vm.Set("_filelib_rname", func(call otto.FunctionCall) otto.Value {
+		//Get virtual path from the function input
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Get fs handler from the vpath
+		fsHandler, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Return the name of the fsHandler
+		name, _ := vm.ToValue(fsHandler.Name)
+		return name
+
+	})
+
+	vm.Set("_filelib_mtime", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		parseToUnix, err := call.Argument(1).ToBoolean()
+		if err != nil {
+			parseToUnix = false
+		}
+
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		info, err := fsh.FileSystemAbstraction.Stat(rpath)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		modTime := info.ModTime()
+		if parseToUnix {
+			result, _ := otto.ToValue(modTime.Unix())
+			return result
+		} else {
+			result, _ := otto.ToValue(modTime.Format("2006-01-02 15:04:05"))
+			return result
+		}
+	})
+
+	//ArozOS v2.0 New features
+	//Reading or writing from hex to target virtual filepath
+
+	//Write binary from hex string
+	vm.Set("_filelib_writeBinaryFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanWrite(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		hexContent, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Get the target vpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			log.Println(err.Error())
+			return otto.FalseValue()
+		}
+
+		//Decode the hex content to bytes
+		hexContentInByte, err := hex.DecodeString(hexContent)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Write the file to target file
+		err = fsh.FileSystemAbstraction.WriteFile(rpath, hexContentInByte, 0775)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		return otto.TrueValue()
+
+	})
+
+	//Read file from external fsh. Small file only
+	vm.Set("_filelib_readBinaryFile", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		//Rewrite the vpath if it is relative
+		vpath = relativeVpathRewrite(scriptFsh, vpath, vm, u)
+
+		//Check for permission
+		if !u.CanRead(vpath) {
+			panic(vm.MakeCustomError("PermissionDenied", "Path access denied"))
+		}
+
+		//Get the target vpath
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		if !fsh.FileSystemAbstraction.FileExists(rpath) {
+			//Check if the target file exists
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		content, err := fsh.FileSystemAbstraction.ReadFile(rpath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.NullValue()
+		}
+
+		hexifiedContent := hex.EncodeToString(content)
+		val, _ := vm.ToValue(hexifiedContent)
+		return val
+
+	})
+
+	//Other file operations, wip
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var filelib = {};
+		filelib.writeFile = _filelib_writeFile;
+		filelib.readFile = _filelib_readFile;
+		filelib.deleteFile = _filelib_deleteFile;
+		filelib.walk = _filelib_walk;
+		filelib.glob = _filelib_glob;
+		filelib.aglob = _filelib_aglob;
+		filelib.filesize = _filelib_filesize;
+		filelib.fileExists = _filelib_fileExists;
+		filelib.isDir = _filelib_isDir;
+		filelib.md5 = _filelib_md5;
+		filelib.mkdir = _filelib_mkdir;
+		filelib.mtime = _filelib_mtime;
+		filelib.rootName = _filelib_rname;
+
+		filelib.readdir = function(path, sortmode){
+			var s = _filelib_readdir(path, sortmode);
+			return JSON.parse(s);
+		};
+	`)
+}

+ 8 - 7
mod/agi/agi.go

@@ -33,14 +33,15 @@ import (
 */
 
 var (
-	AgiVersion string = "2.1" //Defination of the agi runtime version. Update this when new function is added
+	AgiVersion string = "2.2" //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
+//Lib interface, require vm, user, target system file handler and the vpath of the running script
+type AgiLibIntergface func(*otto.Otto, *user.User, *filesystem.FileSystemHandler, string) //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
 }
@@ -205,7 +206,7 @@ func (g *Gateway) APIHandler(w http.ResponseWriter, r *http.Request, thisuser *u
 		w.Write([]byte("400 - Bad Request (Missing script content)"))
 		return
 	}
-	g.ExecuteAGIScript(scriptContent, "", "", w, r, thisuser)
+	g.ExecuteAGIScript(scriptContent, nil, "", "", w, r, thisuser)
 }
 
 //Handle user requests
@@ -269,7 +270,7 @@ func (g *Gateway) InterfaceHandler(w http.ResponseWriter, r *http.Request, thisu
 	scriptContentByte, _ := ioutil.ReadFile(scriptFile)
 	scriptContent := string(scriptContentByte)
 
-	g.ExecuteAGIScript(scriptContent, scriptFile, scriptScope, w, r, thisuser)
+	g.ExecuteAGIScript(scriptContent, nil, scriptFile, scriptScope, w, r, thisuser)
 }
 
 /*
@@ -281,12 +282,12 @@ func (g *Gateway) InterfaceHandler(w http.ResponseWriter, r *http.Request, thisu
 	thisuser: userObject
 
 */
-func (g *Gateway) ExecuteAGIScript(scriptContent string, scriptFile string, scriptScope string, w http.ResponseWriter, r *http.Request, thisuser *user.User) {
+func (g *Gateway) ExecuteAGIScript(scriptContent string, fsh *filesystem.FileSystemHandler, 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)
+	g.injectUserFunctions(vm, fsh, scriptFile, scriptScope, thisuser, w, r)
 
 	//Detect cotent type
 	contentType := r.Header.Get("Content-type")
@@ -346,7 +347,7 @@ func (g *Gateway) ExecuteAGIScriptAsUser(fsh *filesystem.FileSystemHandler, scri
 	vm := otto.New()
 	//Inject standard libs into the vm
 	g.injectStandardLibs(vm, scriptFile, "")
-	g.injectUserFunctions(vm, scriptFile, "", targetUser, nil, nil)
+	g.injectUserFunctions(vm, fsh, scriptFile, "", targetUser, nil, nil)
 
 	if r != nil {
 		//Inject serverless script to enable access to GET / POST paramters

+ 263 - 262
mod/agi/agi.http.go

@@ -1,262 +1,263 @@
-package agi
-
-import (
-	"bytes"
-	"encoding/base64"
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"net/url"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI HTTP Request Library
-
-	This is a library for allowing AGI script to make HTTP Request from the VM
-	Returning either the head or the body of the request
-
-	Author: tobychui
-*/
-
-func (g *Gateway) HTTPLibRegister() {
-	err := g.RegisterLib("http", g.injectHTTPFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectHTTPFunctions(vm *otto.Otto, u *user.User) {
-	vm.Set("_http_get", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function variable
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Get respond of the url
-		res, err := http.Get(url)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		bodyContent, err := ioutil.ReadAll(res.Body)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		returnValue, err := vm.ToValue(string(bodyContent))
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		return returnValue
-	})
-
-	vm.Set("_http_post", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Get JSON content from 2nd paramter
-		sendWithPayload := true
-		jsonContent, err := call.Argument(1).ToString()
-		if err != nil {
-			//Disable the payload send
-			sendWithPayload = false
-		}
-
-		//Create the request
-		var req *http.Request
-		if sendWithPayload {
-			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonContent)))
-		} else {
-			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte("")))
-		}
-
-		req.Header.Set("Content-Type", "application/json")
-
-		//Send the request
-		client := &http.Client{}
-		resp, err := client.Do(req)
-		if err != nil {
-			log.Println(err)
-			return otto.NullValue()
-		}
-		defer resp.Body.Close()
-
-		bodyContent, err := ioutil.ReadAll(resp.Body)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		returnValue, _ := vm.ToValue(string(bodyContent))
-
-		return returnValue
-	})
-
-	vm.Set("_http_head", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Request the url
-		resp, err := http.Get(url)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		headerKey, err := call.Argument(1).ToString()
-		if err != nil || headerKey == "undefined" {
-			//No headkey set. Return the whole header as JSON
-			js, _ := json.Marshal(resp.Header)
-			returnValue, _ := vm.ToValue(string(js))
-			return returnValue
-		} else {
-			//headerkey is set. Return if exists
-			possibleValue := resp.Header.Get(headerKey)
-			js, _ := json.Marshal(possibleValue)
-			returnValue, _ := vm.ToValue(string(js))
-			return returnValue
-		}
-
-	})
-
-	//Get target status code for response
-	vm.Set("_http_code", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		req, err := http.NewRequest("GET", url, nil)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		payload := ""
-		client := new(http.Client)
-		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
-			//Redirection. Return the target location as well
-			dest, _ := req.Response.Location()
-			payload = dest.String()
-			return errors.New("Redirect")
-		}
-
-		response, err := client.Do(req)
-		if err != nil {
-			return otto.FalseValue()
-		}
-		defer client.CloseIdleConnections()
-		vm.Run(`var _location = "` + payload + `";`)
-		value, _ := otto.ToValue(response.StatusCode)
-		return value
-
-	})
-
-	vm.Set("_http_download", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function paramter
-		downloadURL, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-		decodedURL, _ := url.QueryUnescape(downloadURL)
-
-		//Get download desintation from paramter
-		vpath, err := call.Argument(1).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Optional: filename paramter
-		filename, err := call.Argument(2).ToString()
-		if err != nil || filename == "undefined" {
-			//Extract the filename from the url instead
-			filename = filepath.Base(decodedURL)
-		}
-
-		//Check user acess permission
-		if !u.CanWrite(vpath) {
-			g.raiseError(errors.New("Permission Denied"))
-			return otto.FalseValue()
-		}
-
-		//Convert the vpath to realpath. Check if it exists
-		fsh, rpath, err := virtualPathToRealPath(vpath, u)
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		if !fsh.FileSystemAbstraction.FileExists(rpath) || !fsh.FileSystemAbstraction.IsDir(rpath) {
-			g.raiseError(errors.New(vpath + " is a file not a directory."))
-			return otto.FalseValue()
-		}
-
-		downloadDest := filepath.Join(rpath, filename)
-
-		//Ok. Download the file
-		resp, err := http.Get(decodedURL)
-		if err != nil {
-			return otto.FalseValue()
-		}
-		defer resp.Body.Close()
-
-		// Create the file
-		err = fsh.FileSystemAbstraction.WriteStream(downloadDest, resp.Body, 0775)
-		if err != nil {
-			return otto.FalseValue()
-		}
-		return otto.TrueValue()
-	})
-
-	vm.Set("_http_getb64", func(call otto.FunctionCall) otto.Value {
-		//Get URL from function variable and return bytes as base64
-		url, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		//Get respond of the url
-		res, err := http.Get(url)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		bodyContent, err := ioutil.ReadAll(res.Body)
-		if err != nil {
-			return otto.NullValue()
-		}
-
-		sEnc := base64.StdEncoding.EncodeToString(bodyContent)
-
-		r, err := otto.ToValue(string(sEnc))
-		if err != nil {
-			log.Println(err.Error())
-			return otto.NullValue()
-		}
-		return r
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var http = {};
-		http.get = _http_get;
-		http.post = _http_post;
-		http.head = _http_head;
-		http.download = _http_download;
-		http.getb64 = _http_getb64;
-		http.getCode = _http_code;
-	`)
-
-}
+package agi
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/url"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI HTTP Request Library
+
+	This is a library for allowing AGI script to make HTTP Request from the VM
+	Returning either the head or the body of the request
+
+	Author: tobychui
+*/
+
+func (g *Gateway) HTTPLibRegister() {
+	err := g.RegisterLib("http", g.injectHTTPFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectHTTPFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	vm.Set("_http_get", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function variable
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Get respond of the url
+		res, err := http.Get(url)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		bodyContent, err := ioutil.ReadAll(res.Body)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		returnValue, err := vm.ToValue(string(bodyContent))
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		return returnValue
+	})
+
+	vm.Set("_http_post", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Get JSON content from 2nd paramter
+		sendWithPayload := true
+		jsonContent, err := call.Argument(1).ToString()
+		if err != nil {
+			//Disable the payload send
+			sendWithPayload = false
+		}
+
+		//Create the request
+		var req *http.Request
+		if sendWithPayload {
+			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonContent)))
+		} else {
+			req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte("")))
+		}
+
+		req.Header.Set("Content-Type", "application/json")
+
+		//Send the request
+		client := &http.Client{}
+		resp, err := client.Do(req)
+		if err != nil {
+			log.Println(err)
+			return otto.NullValue()
+		}
+		defer resp.Body.Close()
+
+		bodyContent, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		returnValue, _ := vm.ToValue(string(bodyContent))
+
+		return returnValue
+	})
+
+	vm.Set("_http_head", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Request the url
+		resp, err := http.Get(url)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		headerKey, err := call.Argument(1).ToString()
+		if err != nil || headerKey == "undefined" {
+			//No headkey set. Return the whole header as JSON
+			js, _ := json.Marshal(resp.Header)
+			returnValue, _ := vm.ToValue(string(js))
+			return returnValue
+		} else {
+			//headerkey is set. Return if exists
+			possibleValue := resp.Header.Get(headerKey)
+			js, _ := json.Marshal(possibleValue)
+			returnValue, _ := vm.ToValue(string(js))
+			return returnValue
+		}
+
+	})
+
+	//Get target status code for response
+	vm.Set("_http_code", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		req, err := http.NewRequest("GET", url, nil)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		payload := ""
+		client := new(http.Client)
+		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+			//Redirection. Return the target location as well
+			dest, _ := req.Response.Location()
+			payload = dest.String()
+			return errors.New("Redirect")
+		}
+
+		response, err := client.Do(req)
+		if err != nil {
+			return otto.FalseValue()
+		}
+		defer client.CloseIdleConnections()
+		vm.Run(`var _location = "` + payload + `";`)
+		value, _ := otto.ToValue(response.StatusCode)
+		return value
+
+	})
+
+	vm.Set("_http_download", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function paramter
+		downloadURL, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+		decodedURL, _ := url.QueryUnescape(downloadURL)
+
+		//Get download desintation from paramter
+		vpath, err := call.Argument(1).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Optional: filename paramter
+		filename, err := call.Argument(2).ToString()
+		if err != nil || filename == "undefined" {
+			//Extract the filename from the url instead
+			filename = filepath.Base(decodedURL)
+		}
+
+		//Check user acess permission
+		if !u.CanWrite(vpath) {
+			g.raiseError(errors.New("Permission Denied"))
+			return otto.FalseValue()
+		}
+
+		//Convert the vpath to realpath. Check if it exists
+		fsh, rpath, err := virtualPathToRealPath(vpath, u)
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		if !fsh.FileSystemAbstraction.FileExists(rpath) || !fsh.FileSystemAbstraction.IsDir(rpath) {
+			g.raiseError(errors.New(vpath + " is a file not a directory."))
+			return otto.FalseValue()
+		}
+
+		downloadDest := filepath.Join(rpath, filename)
+
+		//Ok. Download the file
+		resp, err := http.Get(decodedURL)
+		if err != nil {
+			return otto.FalseValue()
+		}
+		defer resp.Body.Close()
+
+		// Create the file
+		err = fsh.FileSystemAbstraction.WriteStream(downloadDest, resp.Body, 0775)
+		if err != nil {
+			return otto.FalseValue()
+		}
+		return otto.TrueValue()
+	})
+
+	vm.Set("_http_getb64", func(call otto.FunctionCall) otto.Value {
+		//Get URL from function variable and return bytes as base64
+		url, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		//Get respond of the url
+		res, err := http.Get(url)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		bodyContent, err := ioutil.ReadAll(res.Body)
+		if err != nil {
+			return otto.NullValue()
+		}
+
+		sEnc := base64.StdEncoding.EncodeToString(bodyContent)
+
+		r, err := otto.ToValue(string(sEnc))
+		if err != nil {
+			log.Println(err.Error())
+			return otto.NullValue()
+		}
+		return r
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var http = {};
+		http.get = _http_get;
+		http.post = _http_post;
+		http.head = _http_head;
+		http.download = _http_download;
+		http.getb64 = _http_getb64;
+		http.getCode = _http_code;
+	`)
+
+}

+ 407 - 406
mod/agi/agi.image.go

@@ -1,406 +1,407 @@
-package agi
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"image"
-	"image/jpeg"
-	_ "image/jpeg"
-	"image/png"
-	_ "image/png"
-	"log"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/disintegration/imaging"
-	"github.com/oliamb/cutter"
-	"github.com/robertkrimen/otto"
-
-	"imuslab.com/arozos/mod/neuralnet"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AJGI Image Processing Library
-
-	This is a library for handling image related functionalities in agi scripts.
-
-*/
-
-func (g *Gateway) ImageLibRegister() {
-	err := g.RegisterLib("imagelib", g.injectImageLibFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectImageLibFunctions(vm *otto.Otto, u *user.User) {
-	//Get image dimension, requires filepath (virtual)
-	vm.Set("_imagelib_getImageDimension", func(call otto.FunctionCall) otto.Value {
-		imageFileVpath, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		fsh, imagePath, err := virtualPathToRealPath(imageFileVpath, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if !fsh.FileSystemAbstraction.FileExists(imagePath) {
-			g.raiseError(errors.New("File not exists! Given " + imagePath))
-			return otto.FalseValue()
-		}
-
-		openingPath := imagePath
-		var closerFunc func()
-		if fsh.RequireBuffer {
-			bufferPath, cf := g.getUserSpecificTempFilePath(u, imagePath)
-			closerFunc = cf
-			defer closerFunc()
-			c, err := fsh.FileSystemAbstraction.ReadFile(imagePath)
-			if err != nil {
-				g.raiseError(errors.New("Read from file system failed: " + err.Error()))
-				return otto.FalseValue()
-			}
-			os.WriteFile(bufferPath, c, 0775)
-			openingPath = bufferPath
-		}
-
-		file, err := os.Open(openingPath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		image, _, err := image.DecodeConfig(file)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		file.Close()
-		rawResults := []int{image.Width, image.Height}
-		result, _ := vm.ToValue(rawResults)
-		return result
-	})
-
-	//Resize image, require (filepath, outputpath, width, height)
-	vm.Set("_imagelib_resizeImage", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		vdest, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		width, err := call.Argument(2).ToInteger()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		height, err := call.Argument(3).ToInteger()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Convert the virtual paths to real paths
-		srcfsh, rsrc, err := virtualPathToRealPath(vsrc, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		destfsh, rdest, err := virtualPathToRealPath(vdest, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		ext := strings.ToLower(filepath.Ext(rdest))
-		if !inArray([]string{".jpg", ".jpeg", ".png"}, ext) {
-			g.raiseError(errors.New("File extension not supported. Only support .jpg and .png"))
-			return otto.FalseValue()
-		}
-
-		if destfsh.FileSystemAbstraction.FileExists(rdest) {
-			err := destfsh.FileSystemAbstraction.Remove(rdest)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-		}
-
-		resizeOpeningFile := rsrc
-		resizeWritingFile := rdest
-		var srcCloser func()
-		var destCloser func()
-		if srcfsh.RequireBuffer {
-			resizeOpeningFile, srcCloser, err = g.bufferRemoteResourcesToLocal(srcfsh, u, rsrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer srcCloser()
-		}
-
-		if destfsh.RequireBuffer {
-			resizeWritingFile, destCloser, err = g.bufferRemoteResourcesToLocal(destfsh, u, rdest)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer destCloser()
-		}
-
-		//Resize the image
-		src, err := imaging.Open(resizeOpeningFile)
-		if err != nil {
-			//Opening failed
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		src = imaging.Resize(src, int(width), int(height), imaging.Lanczos)
-		err = imaging.Save(src, resizeWritingFile)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if destfsh.RequireBuffer {
-			c, _ := os.ReadFile(resizeWritingFile)
-			destfsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Crop the given image, require (input, output, posx, posy, width, height)
-	vm.Set("_imagelib_cropImage", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		vdest, err := call.Argument(1).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		posx, err := call.Argument(2).ToInteger()
-		if err != nil {
-			posx = 0
-		}
-
-		posy, err := call.Argument(3).ToInteger()
-		if err != nil {
-			posy = 0
-		}
-
-		width, err := call.Argument(4).ToInteger()
-		if err != nil {
-			g.raiseError(errors.New("Image width not defined"))
-			return otto.FalseValue()
-		}
-
-		height, err := call.Argument(5).ToInteger()
-		if err != nil {
-			g.raiseError(errors.New("Image height not defined"))
-			return otto.FalseValue()
-		}
-
-		//Convert the virtual paths to realpaths
-
-		srcFsh, rsrc, err := virtualPathToRealPath(vsrc, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		srcFshAbs := srcFsh.FileSystemAbstraction
-		destFsh, rdest, err := virtualPathToRealPath(vdest, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		destWritePath := rdest
-		var destCloserFunction func()
-		if destFsh.RequireBuffer {
-			destWritePath, destCloserFunction = g.getUserSpecificTempFilePath(u, rdest)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer destCloserFunction()
-		}
-
-		//Try to read the source image
-		imageBytes, err := srcFshAbs.ReadFile(rsrc)
-		if err != nil {
-			fmt.Println(err)
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		img, _, err := image.Decode(bytes.NewReader(imageBytes))
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Crop the image
-		croppedImg, _ := cutter.Crop(img, cutter.Config{
-			Width:  int(width),
-			Height: int(height),
-			Anchor: image.Point{int(posx), int(posy)},
-			Mode:   cutter.TopLeft,
-		})
-
-		//Create the new image
-		out, err := os.Create(destWritePath)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		if strings.ToLower(filepath.Ext(destWritePath)) == ".png" {
-			png.Encode(out, croppedImg)
-		} else if strings.ToLower(filepath.Ext(destWritePath)) == ".jpg" {
-			jpeg.Encode(out, croppedImg, nil)
-		} else {
-			g.raiseError(errors.New("Not supported format: Only support jpg or png"))
-			return otto.FalseValue()
-		}
-		out.Close()
-
-		if destFsh.RequireBuffer {
-			c, _ := os.ReadFile(destWritePath)
-			err := destFsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
-			if err != nil {
-				fmt.Println(">", err.Error())
-			}
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Get the given file's thumbnail in base64
-	vm.Set("_imagelib_loadThumbString", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vsrc)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-		rpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vsrc, u.Username)
-
-		//Get the files' thumb base64 string
-		base64String, err := g.Option.FileSystemRender.LoadCache(fsh, rpath, false)
-		if err != nil {
-			return otto.FalseValue()
-		} else {
-			value, _ := vm.ToValue(base64String)
-			return value
-		}
-	})
-
-	vm.Set("_imagelib_classify", func(call otto.FunctionCall) otto.Value {
-		vsrc, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		classifier, err := call.Argument(1).ToString()
-		if err != nil {
-			classifier = "default"
-		}
-
-		if classifier == "" || classifier == "undefined" {
-			classifier = "default"
-		}
-
-		//Convert the vsrc to real path
-		fsh, rsrc, err := virtualPathToRealPath(vsrc, u)
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		analysisSrc := rsrc
-		var closerFunc func()
-		if fsh.RequireBuffer {
-			analysisSrc, closerFunc, err = g.bufferRemoteResourcesToLocal(fsh, u, rsrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-			defer closerFunc()
-		}
-
-		if classifier == "default" || classifier == "darknet19" {
-			//Use darknet19 for classification
-			r, err := neuralnet.AnalysisPhotoDarknet19(analysisSrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			result, err := vm.ToValue(r)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			return result
-
-		} else if classifier == "yolo3" {
-			//Use yolo3 for classification, return positions of object as well
-			r, err := neuralnet.AnalysisPhotoYOLO3(analysisSrc)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			result, err := vm.ToValue(r)
-			if err != nil {
-				g.raiseError(err)
-				return otto.FalseValue()
-			}
-
-			return result
-
-		} else {
-			//Unsupported classifier
-			log.Println("[AGI] Unsupported image classifier name: " + classifier)
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var imagelib = {};
-		imagelib.getImageDimension = _imagelib_getImageDimension;
-		imagelib.resizeImage = _imagelib_resizeImage;
-		imagelib.cropImage = _imagelib_cropImage;
-		imagelib.loadThumbString = _imagelib_loadThumbString;
-		imagelib.classify = _imagelib_classify;
-	`)
-}
+package agi
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"image"
+	"image/jpeg"
+	_ "image/jpeg"
+	"image/png"
+	_ "image/png"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/disintegration/imaging"
+	"github.com/oliamb/cutter"
+	"github.com/robertkrimen/otto"
+
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/neuralnet"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AJGI Image Processing Library
+
+	This is a library for handling image related functionalities in agi scripts.
+
+*/
+
+func (g *Gateway) ImageLibRegister() {
+	err := g.RegisterLib("imagelib", g.injectImageLibFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectImageLibFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	//Get image dimension, requires filepath (virtual)
+	vm.Set("_imagelib_getImageDimension", func(call otto.FunctionCall) otto.Value {
+		imageFileVpath, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		fsh, imagePath, err := virtualPathToRealPath(imageFileVpath, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if !fsh.FileSystemAbstraction.FileExists(imagePath) {
+			g.raiseError(errors.New("File not exists! Given " + imagePath))
+			return otto.FalseValue()
+		}
+
+		openingPath := imagePath
+		var closerFunc func()
+		if fsh.RequireBuffer {
+			bufferPath, cf := g.getUserSpecificTempFilePath(u, imagePath)
+			closerFunc = cf
+			defer closerFunc()
+			c, err := fsh.FileSystemAbstraction.ReadFile(imagePath)
+			if err != nil {
+				g.raiseError(errors.New("Read from file system failed: " + err.Error()))
+				return otto.FalseValue()
+			}
+			os.WriteFile(bufferPath, c, 0775)
+			openingPath = bufferPath
+		}
+
+		file, err := os.Open(openingPath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		image, _, err := image.DecodeConfig(file)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		file.Close()
+		rawResults := []int{image.Width, image.Height}
+		result, _ := vm.ToValue(rawResults)
+		return result
+	})
+
+	//Resize image, require (filepath, outputpath, width, height)
+	vm.Set("_imagelib_resizeImage", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		vdest, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		width, err := call.Argument(2).ToInteger()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		height, err := call.Argument(3).ToInteger()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Convert the virtual paths to real paths
+		srcfsh, rsrc, err := virtualPathToRealPath(vsrc, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		destfsh, rdest, err := virtualPathToRealPath(vdest, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		ext := strings.ToLower(filepath.Ext(rdest))
+		if !inArray([]string{".jpg", ".jpeg", ".png"}, ext) {
+			g.raiseError(errors.New("File extension not supported. Only support .jpg and .png"))
+			return otto.FalseValue()
+		}
+
+		if destfsh.FileSystemAbstraction.FileExists(rdest) {
+			err := destfsh.FileSystemAbstraction.Remove(rdest)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+		}
+
+		resizeOpeningFile := rsrc
+		resizeWritingFile := rdest
+		var srcCloser func()
+		var destCloser func()
+		if srcfsh.RequireBuffer {
+			resizeOpeningFile, srcCloser, err = g.bufferRemoteResourcesToLocal(srcfsh, u, rsrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer srcCloser()
+		}
+
+		if destfsh.RequireBuffer {
+			resizeWritingFile, destCloser, err = g.bufferRemoteResourcesToLocal(destfsh, u, rdest)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer destCloser()
+		}
+
+		//Resize the image
+		src, err := imaging.Open(resizeOpeningFile)
+		if err != nil {
+			//Opening failed
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		src = imaging.Resize(src, int(width), int(height), imaging.Lanczos)
+		err = imaging.Save(src, resizeWritingFile)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if destfsh.RequireBuffer {
+			c, _ := os.ReadFile(resizeWritingFile)
+			destfsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Crop the given image, require (input, output, posx, posy, width, height)
+	vm.Set("_imagelib_cropImage", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		vdest, err := call.Argument(1).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		posx, err := call.Argument(2).ToInteger()
+		if err != nil {
+			posx = 0
+		}
+
+		posy, err := call.Argument(3).ToInteger()
+		if err != nil {
+			posy = 0
+		}
+
+		width, err := call.Argument(4).ToInteger()
+		if err != nil {
+			g.raiseError(errors.New("Image width not defined"))
+			return otto.FalseValue()
+		}
+
+		height, err := call.Argument(5).ToInteger()
+		if err != nil {
+			g.raiseError(errors.New("Image height not defined"))
+			return otto.FalseValue()
+		}
+
+		//Convert the virtual paths to realpaths
+
+		srcFsh, rsrc, err := virtualPathToRealPath(vsrc, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		srcFshAbs := srcFsh.FileSystemAbstraction
+		destFsh, rdest, err := virtualPathToRealPath(vdest, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		destWritePath := rdest
+		var destCloserFunction func()
+		if destFsh.RequireBuffer {
+			destWritePath, destCloserFunction = g.getUserSpecificTempFilePath(u, rdest)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer destCloserFunction()
+		}
+
+		//Try to read the source image
+		imageBytes, err := srcFshAbs.ReadFile(rsrc)
+		if err != nil {
+			fmt.Println(err)
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		img, _, err := image.Decode(bytes.NewReader(imageBytes))
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Crop the image
+		croppedImg, _ := cutter.Crop(img, cutter.Config{
+			Width:  int(width),
+			Height: int(height),
+			Anchor: image.Point{int(posx), int(posy)},
+			Mode:   cutter.TopLeft,
+		})
+
+		//Create the new image
+		out, err := os.Create(destWritePath)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		if strings.ToLower(filepath.Ext(destWritePath)) == ".png" {
+			png.Encode(out, croppedImg)
+		} else if strings.ToLower(filepath.Ext(destWritePath)) == ".jpg" {
+			jpeg.Encode(out, croppedImg, nil)
+		} else {
+			g.raiseError(errors.New("Not supported format: Only support jpg or png"))
+			return otto.FalseValue()
+		}
+		out.Close()
+
+		if destFsh.RequireBuffer {
+			c, _ := os.ReadFile(destWritePath)
+			err := destFsh.FileSystemAbstraction.WriteFile(rdest, c, 0775)
+			if err != nil {
+				fmt.Println(">", err.Error())
+			}
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Get the given file's thumbnail in base64
+	vm.Set("_imagelib_loadThumbString", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		fsh, err := u.GetFileSystemHandlerFromVirtualPath(vsrc)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+		rpath, _ := fsh.FileSystemAbstraction.VirtualPathToRealPath(vsrc, u.Username)
+
+		//Get the files' thumb base64 string
+		base64String, err := g.Option.FileSystemRender.LoadCache(fsh, rpath, false)
+		if err != nil {
+			return otto.FalseValue()
+		} else {
+			value, _ := vm.ToValue(base64String)
+			return value
+		}
+	})
+
+	vm.Set("_imagelib_classify", func(call otto.FunctionCall) otto.Value {
+		vsrc, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		classifier, err := call.Argument(1).ToString()
+		if err != nil {
+			classifier = "default"
+		}
+
+		if classifier == "" || classifier == "undefined" {
+			classifier = "default"
+		}
+
+		//Convert the vsrc to real path
+		fsh, rsrc, err := virtualPathToRealPath(vsrc, u)
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		analysisSrc := rsrc
+		var closerFunc func()
+		if fsh.RequireBuffer {
+			analysisSrc, closerFunc, err = g.bufferRemoteResourcesToLocal(fsh, u, rsrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+			defer closerFunc()
+		}
+
+		if classifier == "default" || classifier == "darknet19" {
+			//Use darknet19 for classification
+			r, err := neuralnet.AnalysisPhotoDarknet19(analysisSrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			result, err := vm.ToValue(r)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			return result
+
+		} else if classifier == "yolo3" {
+			//Use yolo3 for classification, return positions of object as well
+			r, err := neuralnet.AnalysisPhotoYOLO3(analysisSrc)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			result, err := vm.ToValue(r)
+			if err != nil {
+				g.raiseError(err)
+				return otto.FalseValue()
+			}
+
+			return result
+
+		} else {
+			//Unsupported classifier
+			log.Println("[AGI] Unsupported image classifier name: " + classifier)
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var imagelib = {};
+		imagelib.getImageDimension = _imagelib_getImageDimension;
+		imagelib.resizeImage = _imagelib_resizeImage;
+		imagelib.cropImage = _imagelib_cropImage;
+		imagelib.loadThumbString = _imagelib_loadThumbString;
+		imagelib.classify = _imagelib_classify;
+	`)
+}

+ 289 - 288
mod/agi/agi.iot.go

@@ -1,288 +1,289 @@
-package agi
-
-import (
-	"encoding/json"
-	"log"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/iot"
-	user "imuslab.com/arozos/mod/user"
-)
-
-/*
-	AGI IoT Control Protocols
-
-	This is a library for allowing AGI script to control / send commands to IoT devices
-	Use with caution and prepare to handle errors. IoT devices are not always online / connectabe.
-
-	Author: tobychui
-*/
-
-func (g *Gateway) IoTLibRegister() {
-	err := g.RegisterLib("iot", g.injectIoTFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectIoTFunctions(vm *otto.Otto, u *user.User) {
-	//Scan and return the latest iot device list
-	vm.Set("_iot_scan", func(call otto.FunctionCall) otto.Value {
-		scannedDevices := g.Option.IotManager.ScanDevices()
-		js, _ := json.Marshal(scannedDevices)
-		devList, err := vm.ToValue(string(js))
-		if err != nil {
-			return otto.FalseValue()
-		}
-		return devList
-	})
-
-	//List the current scanned device list from cache
-	vm.Set("_iot_list", func(call otto.FunctionCall) otto.Value {
-		devices := g.Option.IotManager.GetCachedDeviceList()
-		js, _ := json.Marshal(devices)
-		devList, err := vm.ToValue(string(js))
-		if err != nil {
-			return otto.FalseValue()
-		}
-		return devList
-	})
-
-	//Conenct an iot device. Return true if the device is connected or the device do not require connection before command exec
-	vm.Set("_iot_connect", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Get the auth info from paramters
-		username, err := call.Argument(1).ToString()
-		if err != nil {
-			username = ""
-		}
-
-		password, err := call.Argument(2).ToString()
-		if err != nil {
-			password = ""
-		}
-
-		token, err := call.Argument(3).ToString()
-		if err != nil {
-			token = ""
-		}
-
-		//Get device by id
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-		if dev == nil {
-			//No device with that ID found
-			return otto.FalseValue()
-		}
-
-		if dev.RequireConnect == true {
-			//Build the auto info
-			autoInfo := iot.AuthInfo{
-				Username: username,
-				Password: password,
-				Token:    token,
-			}
-
-			//Connect the device
-			dev.Handler.Connect(dev, &autoInfo)
-		}
-
-		//Return true
-		return otto.TrueValue()
-	})
-
-	//Get the status of the given device
-	vm.Set("_iot_status", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-
-		if dev == nil {
-			return otto.FalseValue()
-		}
-
-		//We have no idea what is the structure of the dev status.
-		//Just leave it to the front end to handle :P
-		devStatus, err := dev.Handler.Status(dev)
-		if err != nil {
-			log.Println("*AGI IoT* " + err.Error())
-			return otto.FalseValue()
-		}
-
-		js, _ := json.Marshal(devStatus)
-		results, _ := vm.ToValue(string(js))
-		return results
-	})
-
-	vm.Set("_iot_exec", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Get endpoint name
-		epname, err := call.Argument(1).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		//Get payload if any
-		payload, err := call.Argument(2).ToString()
-		if err != nil {
-			payload = ""
-		}
-
-		//Get device by id
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-		if dev == nil {
-			//Device not found
-			log.Println("*AGI IoT* Given device ID do not match any IoT devices")
-			return otto.FalseValue()
-		}
-
-		//Get the endpoint from name
-		var targetEp *iot.Endpoint
-		for _, ep := range dev.ControlEndpoints {
-			if ep.Name == epname {
-				//This is the target endpoint
-				thisEp := ep
-				targetEp = thisEp
-			}
-
-		}
-
-		if targetEp == nil {
-			//Endpoint not found
-			log.Println("*AGI IoT* Failed to get endpoint by name in this device")
-			return otto.FalseValue()
-		}
-
-		var results interface{}
-
-		//Try to convert it into a string map
-		if payload != "" {
-			payloadMap := map[string]interface{}{}
-
-			err = json.Unmarshal([]byte(payload), &payloadMap)
-			if err != nil {
-				log.Println("*AGI IoT* Failed to parse input payload: " + err.Error())
-				return otto.FalseValue()
-			}
-
-			//Execute the request
-			results, err = dev.Handler.Execute(dev, targetEp, payloadMap)
-
-		} else {
-			//Execute the request without payload
-			results, err = dev.Handler.Execute(dev, targetEp, nil)
-		}
-
-		if err != nil {
-			log.Println("*AGI IoT* Failed to execute request to device: " + err.Error())
-			return otto.FalseValue()
-		}
-
-		js, _ := json.Marshal(results)
-		reply, _ := vm.ToValue(string(js))
-		return reply
-	})
-
-	//Disconnect a given iot device using the device UUID
-	vm.Set("_iot_disconnect", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-
-		if dev == nil {
-			return otto.FalseValue()
-		}
-
-		if dev.RequireConnect == true {
-			err = dev.Handler.Disconnect(dev)
-			if err != nil {
-				return otto.FalseValue()
-			}
-		}
-
-		return otto.TrueValue()
-	})
-
-	//Return the icon tag for this device
-	vm.Set("_iot_iconTag", func(call otto.FunctionCall) otto.Value {
-		//Get device ID from paratmer
-		devID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.FalseValue()
-		}
-
-		dev := g.Option.IotManager.GetDeviceByID(devID)
-		if dev == nil {
-			//device not found
-			return otto.NullValue()
-		}
-
-		deviceIconTag := dev.Handler.Icon(dev)
-		it, _ := vm.ToValue(deviceIconTag)
-
-		return it
-	})
-
-	vm.Set("_iot_ready", func(call otto.FunctionCall) otto.Value {
-		if g.Option.IotManager == nil {
-			return otto.FalseValue()
-		} else {
-			return otto.TrueValue()
-		}
-	})
-
-	//Wrap all the native code function into an imagelib class
-	_, err := vm.Run(`
-		var iot = {
-			"scan": function(){
-				var devList = _iot_scan();
-				return JSON.parse(devList);
-			},
-			"list": function(){
-				var devList = _iot_list();
-				return JSON.parse(devList);
-			},
-			"status": function(devid){
-				var devStatus = _iot_status(devid);
-				return JSON.parse(devStatus);
-			},
-			"exec": function(devid, epname, payload){
-				payload = payload || "";
-				payload = JSON.stringify(payload);
-				var resp = _iot_exec(devid, epname, payload);
-				if (resp == false){
-					return false;
-				}else{
-					return JSON.parse(resp);
-				}
-			}
-		};
-
-		iot.ready = _iot_ready;
-		iot.connect = _iot_connect;
-		iot.disconnect = _iot_disconnect;
-		iot.iconTag = _iot_iconTag;
-		
-	`)
-
-	if err != nil {
-		log.Println("*AGI* IoT Functions Injection Error", err.Error())
-	}
-}
+package agi
+
+import (
+	"encoding/json"
+	"log"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/iot"
+	user "imuslab.com/arozos/mod/user"
+)
+
+/*
+	AGI IoT Control Protocols
+
+	This is a library for allowing AGI script to control / send commands to IoT devices
+	Use with caution and prepare to handle errors. IoT devices are not always online / connectabe.
+
+	Author: tobychui
+*/
+
+func (g *Gateway) IoTLibRegister() {
+	err := g.RegisterLib("iot", g.injectIoTFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectIoTFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	//Scan and return the latest iot device list
+	vm.Set("_iot_scan", func(call otto.FunctionCall) otto.Value {
+		scannedDevices := g.Option.IotManager.ScanDevices()
+		js, _ := json.Marshal(scannedDevices)
+		devList, err := vm.ToValue(string(js))
+		if err != nil {
+			return otto.FalseValue()
+		}
+		return devList
+	})
+
+	//List the current scanned device list from cache
+	vm.Set("_iot_list", func(call otto.FunctionCall) otto.Value {
+		devices := g.Option.IotManager.GetCachedDeviceList()
+		js, _ := json.Marshal(devices)
+		devList, err := vm.ToValue(string(js))
+		if err != nil {
+			return otto.FalseValue()
+		}
+		return devList
+	})
+
+	//Conenct an iot device. Return true if the device is connected or the device do not require connection before command exec
+	vm.Set("_iot_connect", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Get the auth info from paramters
+		username, err := call.Argument(1).ToString()
+		if err != nil {
+			username = ""
+		}
+
+		password, err := call.Argument(2).ToString()
+		if err != nil {
+			password = ""
+		}
+
+		token, err := call.Argument(3).ToString()
+		if err != nil {
+			token = ""
+		}
+
+		//Get device by id
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+		if dev == nil {
+			//No device with that ID found
+			return otto.FalseValue()
+		}
+
+		if dev.RequireConnect == true {
+			//Build the auto info
+			autoInfo := iot.AuthInfo{
+				Username: username,
+				Password: password,
+				Token:    token,
+			}
+
+			//Connect the device
+			dev.Handler.Connect(dev, &autoInfo)
+		}
+
+		//Return true
+		return otto.TrueValue()
+	})
+
+	//Get the status of the given device
+	vm.Set("_iot_status", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+
+		if dev == nil {
+			return otto.FalseValue()
+		}
+
+		//We have no idea what is the structure of the dev status.
+		//Just leave it to the front end to handle :P
+		devStatus, err := dev.Handler.Status(dev)
+		if err != nil {
+			log.Println("*AGI IoT* " + err.Error())
+			return otto.FalseValue()
+		}
+
+		js, _ := json.Marshal(devStatus)
+		results, _ := vm.ToValue(string(js))
+		return results
+	})
+
+	vm.Set("_iot_exec", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Get endpoint name
+		epname, err := call.Argument(1).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		//Get payload if any
+		payload, err := call.Argument(2).ToString()
+		if err != nil {
+			payload = ""
+		}
+
+		//Get device by id
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+		if dev == nil {
+			//Device not found
+			log.Println("*AGI IoT* Given device ID do not match any IoT devices")
+			return otto.FalseValue()
+		}
+
+		//Get the endpoint from name
+		var targetEp *iot.Endpoint
+		for _, ep := range dev.ControlEndpoints {
+			if ep.Name == epname {
+				//This is the target endpoint
+				thisEp := ep
+				targetEp = thisEp
+			}
+
+		}
+
+		if targetEp == nil {
+			//Endpoint not found
+			log.Println("*AGI IoT* Failed to get endpoint by name in this device")
+			return otto.FalseValue()
+		}
+
+		var results interface{}
+
+		//Try to convert it into a string map
+		if payload != "" {
+			payloadMap := map[string]interface{}{}
+
+			err = json.Unmarshal([]byte(payload), &payloadMap)
+			if err != nil {
+				log.Println("*AGI IoT* Failed to parse input payload: " + err.Error())
+				return otto.FalseValue()
+			}
+
+			//Execute the request
+			results, err = dev.Handler.Execute(dev, targetEp, payloadMap)
+
+		} else {
+			//Execute the request without payload
+			results, err = dev.Handler.Execute(dev, targetEp, nil)
+		}
+
+		if err != nil {
+			log.Println("*AGI IoT* Failed to execute request to device: " + err.Error())
+			return otto.FalseValue()
+		}
+
+		js, _ := json.Marshal(results)
+		reply, _ := vm.ToValue(string(js))
+		return reply
+	})
+
+	//Disconnect a given iot device using the device UUID
+	vm.Set("_iot_disconnect", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+
+		if dev == nil {
+			return otto.FalseValue()
+		}
+
+		if dev.RequireConnect == true {
+			err = dev.Handler.Disconnect(dev)
+			if err != nil {
+				return otto.FalseValue()
+			}
+		}
+
+		return otto.TrueValue()
+	})
+
+	//Return the icon tag for this device
+	vm.Set("_iot_iconTag", func(call otto.FunctionCall) otto.Value {
+		//Get device ID from paratmer
+		devID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.FalseValue()
+		}
+
+		dev := g.Option.IotManager.GetDeviceByID(devID)
+		if dev == nil {
+			//device not found
+			return otto.NullValue()
+		}
+
+		deviceIconTag := dev.Handler.Icon(dev)
+		it, _ := vm.ToValue(deviceIconTag)
+
+		return it
+	})
+
+	vm.Set("_iot_ready", func(call otto.FunctionCall) otto.Value {
+		if g.Option.IotManager == nil {
+			return otto.FalseValue()
+		} else {
+			return otto.TrueValue()
+		}
+	})
+
+	//Wrap all the native code function into an imagelib class
+	_, err := vm.Run(`
+		var iot = {
+			"scan": function(){
+				var devList = _iot_scan();
+				return JSON.parse(devList);
+			},
+			"list": function(){
+				var devList = _iot_list();
+				return JSON.parse(devList);
+			},
+			"status": function(devid){
+				var devStatus = _iot_status(devid);
+				return JSON.parse(devStatus);
+			},
+			"exec": function(devid, epname, payload){
+				payload = payload || "";
+				payload = JSON.stringify(payload);
+				var resp = _iot_exec(devid, epname, payload);
+				if (resp == false){
+					return false;
+				}else{
+					return JSON.parse(resp);
+				}
+			}
+		};
+
+		iot.ready = _iot_ready;
+		iot.connect = _iot_connect;
+		iot.disconnect = _iot_disconnect;
+		iot.iconTag = _iot_iconTag;
+		
+	`)
+
+	if err != nil {
+		log.Println("*AGI* IoT Functions Injection Error", err.Error())
+	}
+}

+ 132 - 131
mod/agi/agi.share.go

@@ -1,131 +1,132 @@
-package agi
-
-import (
-	"log"
-	"time"
-
-	"github.com/robertkrimen/otto"
-	user "imuslab.com/arozos/mod/user"
-)
-
-func (g *Gateway) ShareLibRegister() {
-	err := g.RegisterLib("share", g.injectShareFunctions)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
-
-func (g *Gateway) injectShareFunctions(vm *otto.Otto, u *user.User) {
-	vm.Set("_share_file", func(call otto.FunctionCall) otto.Value {
-		//Get the vpath of file to share
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Unable to decode filepath", "No given filepath for sharing")
-		}
-
-		//Get the timeout from the 2nd parameter for how long this share will exists
-		timeout, err := call.Argument(1).ToInteger()
-		if err != nil {
-			//Not defined -> Do not expire
-			timeout = 0
-		}
-
-		//Create a share object for this request
-		vpathSourceFsh := u.GetRootFSHFromVpathInUserScope(vpath)
-		shareID, err := g.Option.ShareManager.CreateNewShare(u, vpathSourceFsh, vpath)
-		if err != nil {
-			log.Println("[AGI] Create Share Failed: " + err.Error())
-			return otto.New().MakeCustomError("Share failed", err.Error())
-		}
-
-		if timeout > 0 {
-			go func(timeout int) {
-				time.Sleep(time.Duration(timeout) * time.Second)
-				g.Option.ShareManager.RemoveShareByUUID(u, shareID.UUID)
-				log.Println("[AGI] Share auto-removed: " + shareID.UUID)
-			}(int(timeout))
-		}
-
-		r, _ := otto.ToValue(shareID.UUID)
-		return r
-	})
-
-	vm.Set("_share_removeShare", func(call otto.FunctionCall) otto.Value {
-		shareUUID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to remove share", "No share UUID given")
-		}
-		err = g.Option.ShareManager.RemoveShareByUUID(u, shareUUID)
-		if err != nil {
-			log.Println("[AGI] Share remove failed: " + err.Error())
-			return otto.New().MakeCustomError("Failed to remove share", err.Error())
-		}
-
-		return otto.TrueValue()
-	})
-
-	vm.Set("_share_getShareUUID", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			log.Println("[AGI] Failed to get share UUID: filepath not given")
-			return otto.NullValue()
-		}
-
-		shareObject := g.Option.ShareManager.GetShareObjectFromUserAndVpath(u, vpath)
-		if shareObject == nil {
-			log.Println("[AGI] Failed to get share UUID: File not shared")
-			return otto.NullValue()
-		}
-
-		shareUUID := shareObject.UUID
-		val, _ := otto.ToValue(shareUUID)
-		return val
-	})
-
-	vm.Set("_share_checkShareExists", func(call otto.FunctionCall) otto.Value {
-		shareUUID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to check share exists", "No share UUID given")
-		}
-
-		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
-		r, _ := otto.ToValue(!(shareObject == nil))
-		return r
-	})
-
-	vm.Set("_share_checkSharePermission", func(call otto.FunctionCall) otto.Value {
-		shareUUID, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to check share permission", "No share UUID given")
-		}
-
-		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
-		if shareObject == nil {
-			return otto.NullValue()
-		}
-		r, _ := otto.ToValue(shareObject.Permission)
-		return r
-	})
-
-	vm.Set("_share_fileIsShared", func(call otto.FunctionCall) otto.Value {
-		vpath, err := call.Argument(0).ToString()
-		if err != nil {
-			return otto.New().MakeCustomError("Failed to check share exists", "No filepath given")
-		}
-
-		isShared := g.Option.ShareManager.FileIsShared(u, vpath)
-		r, _ := otto.ToValue(isShared)
-		return r
-	})
-
-	//Wrap all the native code function into an imagelib class
-	vm.Run(`
-		var share = {};
-		share.shareFile = _share_file;
-		share.removeShare = _share_removeShare;
-		share.checkShareExists = _share_checkShareExists;
-		share.fileIsShared = _share_fileIsShared;
-		share.getFileShareUUID = _share_getShareUUID;
-		share.checkSharePermission = _share_checkSharePermission;
-	`)
-}
+package agi
+
+import (
+	"log"
+	"time"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	user "imuslab.com/arozos/mod/user"
+)
+
+func (g *Gateway) ShareLibRegister() {
+	err := g.RegisterLib("share", g.injectShareFunctions)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (g *Gateway) injectShareFunctions(vm *otto.Otto, u *user.User, scriptFsh *filesystem.FileSystemHandler, scriptPath string) {
+	vm.Set("_share_file", func(call otto.FunctionCall) otto.Value {
+		//Get the vpath of file to share
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Unable to decode filepath", "No given filepath for sharing")
+		}
+
+		//Get the timeout from the 2nd parameter for how long this share will exists
+		timeout, err := call.Argument(1).ToInteger()
+		if err != nil {
+			//Not defined -> Do not expire
+			timeout = 0
+		}
+
+		//Create a share object for this request
+		vpathSourceFsh := u.GetRootFSHFromVpathInUserScope(vpath)
+		shareID, err := g.Option.ShareManager.CreateNewShare(u, vpathSourceFsh, vpath)
+		if err != nil {
+			log.Println("[AGI] Create Share Failed: " + err.Error())
+			return otto.New().MakeCustomError("Share failed", err.Error())
+		}
+
+		if timeout > 0 {
+			go func(timeout int) {
+				time.Sleep(time.Duration(timeout) * time.Second)
+				g.Option.ShareManager.RemoveShareByUUID(u, shareID.UUID)
+				log.Println("[AGI] Share auto-removed: " + shareID.UUID)
+			}(int(timeout))
+		}
+
+		r, _ := otto.ToValue(shareID.UUID)
+		return r
+	})
+
+	vm.Set("_share_removeShare", func(call otto.FunctionCall) otto.Value {
+		shareUUID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to remove share", "No share UUID given")
+		}
+		err = g.Option.ShareManager.RemoveShareByUUID(u, shareUUID)
+		if err != nil {
+			log.Println("[AGI] Share remove failed: " + err.Error())
+			return otto.New().MakeCustomError("Failed to remove share", err.Error())
+		}
+
+		return otto.TrueValue()
+	})
+
+	vm.Set("_share_getShareUUID", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			log.Println("[AGI] Failed to get share UUID: filepath not given")
+			return otto.NullValue()
+		}
+
+		shareObject := g.Option.ShareManager.GetShareObjectFromUserAndVpath(u, vpath)
+		if shareObject == nil {
+			log.Println("[AGI] Failed to get share UUID: File not shared")
+			return otto.NullValue()
+		}
+
+		shareUUID := shareObject.UUID
+		val, _ := otto.ToValue(shareUUID)
+		return val
+	})
+
+	vm.Set("_share_checkShareExists", func(call otto.FunctionCall) otto.Value {
+		shareUUID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to check share exists", "No share UUID given")
+		}
+
+		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
+		r, _ := otto.ToValue(!(shareObject == nil))
+		return r
+	})
+
+	vm.Set("_share_checkSharePermission", func(call otto.FunctionCall) otto.Value {
+		shareUUID, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to check share permission", "No share UUID given")
+		}
+
+		shareObject := g.Option.ShareManager.GetShareObjectFromUUID(shareUUID)
+		if shareObject == nil {
+			return otto.NullValue()
+		}
+		r, _ := otto.ToValue(shareObject.Permission)
+		return r
+	})
+
+	vm.Set("_share_fileIsShared", func(call otto.FunctionCall) otto.Value {
+		vpath, err := call.Argument(0).ToString()
+		if err != nil {
+			return otto.New().MakeCustomError("Failed to check share exists", "No filepath given")
+		}
+
+		isShared := g.Option.ShareManager.FileIsShared(u, vpath)
+		r, _ := otto.ToValue(isShared)
+		return r
+	})
+
+	//Wrap all the native code function into an imagelib class
+	vm.Run(`
+		var share = {};
+		share.shareFile = _share_file;
+		share.removeShare = _share_removeShare;
+		share.checkShareExists = _share_checkShareExists;
+		share.fileIsShared = _share_fileIsShared;
+		share.getFileShareUUID = _share_getShareUUID;
+		share.checkSharePermission = _share_checkSharePermission;
+	`)
+}

+ 61 - 61
mod/agi/handler.go

@@ -1,61 +1,61 @@
-package agi
-
-import (
-	"io/ioutil"
-	"net/http"
-	"path/filepath"
-)
-
-//Handle AGI Exectuion Request with token, design for letting other web scripting language like php to interface with AGI
-func (g *Gateway) HandleAgiExecutionRequestWithToken(w http.ResponseWriter, r *http.Request) {
-	token, err := mv(r, "token", false)
-	if err != nil {
-		//Username not defined
-		sendErrorResponse(w, "Token not defined or empty.")
-		return
-	}
-
-	script, err := mv(r, "script", false)
-	if err != nil {
-		//Username not defined
-		sendErrorResponse(w, "Script path not defined or empty.")
-		return
-	}
-
-	//Try to get the username from token
-	username, err := g.Option.UserHandler.GetAuthAgent().GetUsernameFromToken(token)
-	if err != nil {
-		//This token is not valid
-		w.WriteHeader(http.StatusUnauthorized)
-		w.Write([]byte("401 - Unauthorized (Token not valid)"))
-		return
-	}
-
-	//Check if user exists and have access to the script
-	targetUser, err := g.Option.UserHandler.GetUserInfoFromUsername(username)
-	if err != nil {
-		//This user not exists
-		w.WriteHeader(http.StatusUnauthorized)
-		w.Write([]byte("401 - Unauthorized (User not exists)"))
-		return
-	}
-
-	scriptScope := ""
-	allowAccess := checkUserAccessToScript(targetUser, script, scriptScope)
-	if !allowAccess {
-		w.WriteHeader(http.StatusUnauthorized)
-		w.Write([]byte("401 - Unauthorized (Permission Denied)"))
-		return
-	}
-
-	//Get the content of the script
-	scriptContentByte, err := ioutil.ReadFile(filepath.Join("./web/", script))
-	if err != nil {
-		w.WriteHeader(http.StatusNotFound)
-		w.Write([]byte("404 - Script Not Found"))
-		return
-	}
-	scriptContent := string(scriptContentByte)
-
-	g.ExecuteAGIScript(scriptContent, script, scriptScope, w, r, targetUser)
-}
+package agi
+
+import (
+	"io/ioutil"
+	"net/http"
+	"path/filepath"
+)
+
+//Handle AGI Exectuion Request with token, design for letting other web scripting language like php to interface with AGI
+func (g *Gateway) HandleAgiExecutionRequestWithToken(w http.ResponseWriter, r *http.Request) {
+	token, err := mv(r, "token", false)
+	if err != nil {
+		//Username not defined
+		sendErrorResponse(w, "Token not defined or empty.")
+		return
+	}
+
+	script, err := mv(r, "script", false)
+	if err != nil {
+		//Username not defined
+		sendErrorResponse(w, "Script path not defined or empty.")
+		return
+	}
+
+	//Try to get the username from token
+	username, err := g.Option.UserHandler.GetAuthAgent().GetUsernameFromToken(token)
+	if err != nil {
+		//This token is not valid
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Write([]byte("401 - Unauthorized (Token not valid)"))
+		return
+	}
+
+	//Check if user exists and have access to the script
+	targetUser, err := g.Option.UserHandler.GetUserInfoFromUsername(username)
+	if err != nil {
+		//This user not exists
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Write([]byte("401 - Unauthorized (User not exists)"))
+		return
+	}
+
+	scriptScope := ""
+	allowAccess := checkUserAccessToScript(targetUser, script, scriptScope)
+	if !allowAccess {
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Write([]byte("401 - Unauthorized (Permission Denied)"))
+		return
+	}
+
+	//Get the content of the script
+	scriptContentByte, err := ioutil.ReadFile(filepath.Join("./web/", script))
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		w.Write([]byte("404 - Script Not Found"))
+		return
+	}
+	scriptContent := string(scriptContentByte)
+
+	g.ExecuteAGIScript(scriptContent, nil, script, scriptScope, w, r, targetUser)
+}

+ 81 - 72
mod/agi/static.go

@@ -1,72 +1,81 @@
-package agi
-
-import (
-	"net/url"
-	"path/filepath"
-	"strings"
-
-	user "imuslab.com/arozos/mod/user"
-)
-
-//Check if the user can access this script file
-func checkUserAccessToScript(thisuser *user.User, scriptFile string, scriptScope string) bool {
-	moduleName := getScriptRoot(scriptFile, scriptScope)
-	if !thisuser.GetModuleAccessPermission(moduleName) {
-		return false
-	}
-	return true
-}
-
-//validate the given path is a script from webroot
-func isValidAGIScript(scriptPath string) bool {
-	return fileExists(filepath.Join("./web", scriptPath)) && (filepath.Ext(scriptPath) == ".js" || filepath.Ext(scriptPath) == ".agi")
-}
-
-//Return the script root of the current executing script
-func getScriptRoot(scriptFile string, scriptScope string) string {
-	//Get the script root from the script path
-	webRootAbs, _ := filepath.Abs(scriptScope)
-	webRootAbs = filepath.ToSlash(filepath.Clean(webRootAbs) + "/")
-	scriptFileAbs, _ := filepath.Abs(scriptFile)
-	scriptFileAbs = filepath.ToSlash(filepath.Clean(scriptFileAbs))
-	scriptRoot := strings.Replace(scriptFileAbs, webRootAbs, "", 1)
-	scriptRoot = strings.Split(scriptRoot, "/")[0]
-	return scriptRoot
-}
-
-//For handling special url decode in the request
-func specialURIDecode(inputPath string) string {
-	inputPath = strings.ReplaceAll(inputPath, "+", "{{plus_sign}}")
-	inputPath, _ = url.QueryUnescape(inputPath)
-	inputPath = strings.ReplaceAll(inputPath, "{{plus_sign}}", "+")
-	return inputPath
-}
-
-func specialGlob(path string) ([]string, error) {
-	files, err := filepath.Glob(path)
-	if err != nil {
-		return []string{}, err
-	}
-
-	if strings.Contains(path, "[") == true || strings.Contains(path, "]") == true {
-		if len(files) == 0 {
-			//Handle reverse check. Replace all [ and ] with *
-			newSearchPath := strings.ReplaceAll(path, "[", "?")
-			newSearchPath = strings.ReplaceAll(newSearchPath, "]", "?")
-			newSearchPath = strings.ReplaceAll(newSearchPath, ":", "?")
-			//Scan with all the similar structure except [ and ]
-			tmpFilelist, _ := filepath.Glob(newSearchPath)
-			for _, file := range tmpFilelist {
-				file = filepath.ToSlash(file)
-				if strings.Contains(file, filepath.ToSlash(filepath.Dir(path))) {
-					files = append(files, file)
-				}
-			}
-		}
-	}
-	//Convert all filepaths to slash
-	for i := 0; i < len(files); i++ {
-		files[i] = filepath.ToSlash(files[i])
-	}
-	return files, nil
-}
+package agi
+
+import (
+	"net/url"
+	"path/filepath"
+	"strings"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/arozfs"
+	user "imuslab.com/arozos/mod/user"
+)
+
+//Get the full vpath if the passing value is a relative path
+//Return the original vpath if any error occured
+func relativeVpathRewrite(fsh *filesystem.FileSystemHandler, vpath string, vm *otto.Otto, u *user.User) string {
+	//Check if the vpath contain a UUID
+	if strings.Contains(vpath, ":/") || (len(vpath) > 0 && vpath[len(vpath)-1:] == ":") {
+		//This vpath contain root uuid.
+		return vpath
+	}
+
+	//We have no idea where the script is from. Trust its vpath is always full path
+	if fsh == nil {
+		return vpath
+	}
+
+	//Get the script execution root path
+	rootPath, err := vm.Get("__FILE__")
+	if err != nil {
+		return vpath
+	}
+
+	rootPathString, err := rootPath.ToString()
+	if err != nil {
+		return vpath
+	}
+
+	//Convert the root path to vpath
+	rootVpath, err := fsh.FileSystemAbstraction.RealPathToVirtualPath(rootPathString, u.Username)
+	if err != nil {
+		return vpath
+	}
+
+	rootScriptDir := filepath.Dir(rootVpath)
+	return arozfs.ToSlash(filepath.Clean(filepath.Join(rootScriptDir, vpath)))
+}
+
+//Check if the user can access this script file
+func checkUserAccessToScript(thisuser *user.User, scriptFile string, scriptScope string) bool {
+	moduleName := getScriptRoot(scriptFile, scriptScope)
+	if !thisuser.GetModuleAccessPermission(moduleName) {
+		return false
+	}
+	return true
+}
+
+//validate the given path is a script from webroot
+func isValidAGIScript(scriptPath string) bool {
+	return fileExists(filepath.Join("./web", scriptPath)) && (filepath.Ext(scriptPath) == ".js" || filepath.Ext(scriptPath) == ".agi")
+}
+
+//Return the script root of the current executing script
+func getScriptRoot(scriptFile string, scriptScope string) string {
+	//Get the script root from the script path
+	webRootAbs, _ := filepath.Abs(scriptScope)
+	webRootAbs = filepath.ToSlash(filepath.Clean(webRootAbs) + "/")
+	scriptFileAbs, _ := filepath.Abs(scriptFile)
+	scriptFileAbs = filepath.ToSlash(filepath.Clean(scriptFileAbs))
+	scriptRoot := strings.Replace(scriptFileAbs, webRootAbs, "", 1)
+	scriptRoot = strings.Split(scriptRoot, "/")[0]
+	return scriptRoot
+}
+
+//For handling special url decode in the request
+func specialURIDecode(inputPath string) string {
+	inputPath = strings.ReplaceAll(inputPath, "+", "{{plus_sign}}")
+	inputPath, _ = url.QueryUnescape(inputPath)
+	inputPath = strings.ReplaceAll(inputPath, "{{plus_sign}}", "+")
+	return inputPath
+}

+ 1 - 0
mod/agi/systemFunc.go

@@ -24,6 +24,7 @@ func (g *Gateway) injectStandardLibs(vm *otto.Otto, scriptFile string, scriptSco
 	vm.Set("INTERNAL_VERSION", g.Option.InternalVersion)
 	vm.Set("LOADED_MODULES", g.Option.LoadedModule)
 	vm.Set("LOADED_STORAGES", g.Option.UserHandler.GetStoragePool())
+	vm.Set("__FILE__", scriptFile)
 	vm.Set("HTTP_RESP", "")
 	vm.Set("HTTP_HEADER", "text/plain")
 

+ 307 - 300
mod/agi/userFunc.go

@@ -1,300 +1,307 @@
-package agi
-
-import (
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"path/filepath"
-
-	"github.com/robertkrimen/otto"
-	"imuslab.com/arozos/mod/filesystem"
-	user "imuslab.com/arozos/mod/user"
-)
-
-//Define path translation function
-func virtualPathToRealPath(vpath string, u *user.User) (*filesystem.FileSystemHandler, string, error) {
-	fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
-	if err != nil {
-		return nil, "", err
-	}
-	rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, u.Username)
-	if err != nil {
-		return nil, "", err
-	}
-	return fsh, rpath, nil
-}
-
-func realpathToVirtualpath(fsh *filesystem.FileSystemHandler, path string, u *user.User) (string, error) {
-	return fsh.FileSystemAbstraction.RealPathToVirtualPath(path, u.Username)
-}
-
-//Inject user based functions into the virtual machine
-func (g *Gateway) injectUserFunctions(vm *otto.Otto, scriptFile string, scriptScope string, u *user.User, w http.ResponseWriter, r *http.Request) {
-	username := u.Username
-	vm.Set("USERNAME", username)
-	vm.Set("USERICON", u.GetUserIcon())
-	vm.Set("USERQUOTA_TOTAL", u.StorageQuota.TotalStorageQuota)
-	vm.Set("USERQUOTA_USED", u.StorageQuota.UsedStorageQuota)
-	vm.Set("USER_VROOTS", u.GetAllAccessibleFileSystemHandler())
-	vm.Set("USER_MODULES", u.GetUserAccessibleModules())
-
-	//File system and path related
-	vm.Set("decodeVirtualPath", func(call otto.FunctionCall) otto.Value {
-		log.Println("Call to deprecated function decodeVirtualPath")
-		return otto.FalseValue()
-	})
-
-	vm.Set("decodeAbsoluteVirtualPath", func(call otto.FunctionCall) otto.Value {
-		log.Println("Call to deprecated function decodeAbsoluteVirtualPath")
-		return otto.FalseValue()
-	})
-
-	vm.Set("encodeRealPath", func(call otto.FunctionCall) otto.Value {
-		log.Println("Call to deprecated function encodeRealPath")
-		return otto.FalseValue()
-	})
-
-	//Check if a given virtual path is readonly
-	vm.Set("pathCanWrite", func(call otto.FunctionCall) otto.Value {
-		vpath, _ := call.Argument(0).ToString()
-		if u.CanWrite(vpath) {
-			return otto.TrueValue()
-		} else {
-			return otto.FalseValue()
-		}
-	})
-
-	//Permission related
-	vm.Set("getUserPermissionGroup", func(call otto.FunctionCall) otto.Value {
-		groupinfo := u.GetUserPermissionGroup()
-		jsonString, _ := json.Marshal(groupinfo)
-		reply, _ := vm.ToValue(string(jsonString))
-		return reply
-	})
-
-	vm.Set("userIsAdmin", func(call otto.FunctionCall) otto.Value {
-		reply, _ := vm.ToValue(u.IsAdmin())
-		return reply
-	})
-
-	//User Account Related
-	/*
-		userExists(username);
-	*/
-	vm.Set("userExists", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-			//Get username from function paramter
-			username, err := call.Argument(0).ToString()
-			if err != nil || username == "undefined" {
-				g.raiseError(errors.New("username is undefined"))
-				reply, _ := vm.ToValue(nil)
-				return reply
-			}
-
-			//Check if user exists
-			userExists := u.Parent().GetAuthAgent().UserExists(username)
-			if userExists {
-				return otto.TrueValue()
-			} else {
-				return otto.FalseValue()
-			}
-
-		} else {
-			g.raiseError(errors.New("Permission Denied: userExists require admin permission"))
-			return otto.FalseValue()
-		}
-
-	})
-
-	/*
-		createUser(username, password, defaultGroup);
-	*/
-	vm.Set("createUser", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-			//Ok. Create user base on given information
-			username, err := call.Argument(0).ToString()
-			if err != nil || username == "undefined" {
-				g.raiseError(errors.New("username is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			password, err := call.Argument(1).ToString()
-			if err != nil || password == "undefined" {
-				g.raiseError(errors.New("password is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			defaultGroup, err := call.Argument(2).ToString()
-			if err != nil || defaultGroup == "undefined" {
-				g.raiseError(errors.New("defaultGroup is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Check if username already used
-			userExists := u.Parent().GetAuthAgent().UserExists(username)
-			if userExists {
-				g.raiseError(errors.New("Username already exists"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Check if the given permission group exists
-			groupExists := u.Parent().GetPermissionHandler().GroupExists(defaultGroup)
-			if !groupExists {
-				g.raiseError(errors.New(defaultGroup + " user-group not exists"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Create the user
-			err = u.Parent().GetAuthAgent().CreateUserAccount(username, password, []string{defaultGroup})
-
-			if err != nil {
-				g.raiseError(errors.New("User creation failed: " + err.Error()))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			return otto.TrueValue()
-		} else {
-			g.raiseError(errors.New("Permission Denied: createUser require admin permission"))
-			return otto.FalseValue()
-		}
-
-	})
-
-	vm.Set("editUser", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-
-		} else {
-			g.raiseError(errors.New("Permission Denied: editUser require admin permission"))
-			return otto.FalseValue()
-		}
-		//libname, err := call.Argument(0).ToString()
-		return otto.FalseValue()
-	})
-
-	/*
-		removeUser(username)
-	*/
-	vm.Set("removeUser", func(call otto.FunctionCall) otto.Value {
-		if u.IsAdmin() {
-			//Get username from function paramters
-			username, err := call.Argument(0).ToString()
-			if err != nil || username == "undefined" {
-				g.raiseError(errors.New("username is undefined"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//Check if the user exists
-			userExists := u.Parent().GetAuthAgent().UserExists(username)
-			if !userExists {
-				g.raiseError(errors.New(username + " not exists"))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			//User exists. Remove it from the system
-			err = u.Parent().GetAuthAgent().UnregisterUser(username)
-			if err != nil {
-				g.raiseError(errors.New("User removal failed: " + err.Error()))
-				reply, _ := vm.ToValue(false)
-				return reply
-			}
-
-			return otto.TrueValue()
-		} else {
-			g.raiseError(errors.New("Permission Denied: removeUser require admin permission"))
-			return otto.FalseValue()
-		}
-	})
-
-	vm.Set("getUserInfoByName", func(call otto.FunctionCall) otto.Value {
-		//libname, err := call.Argument(0).ToString()
-		if u.IsAdmin() {
-
-		} else {
-
-			g.raiseError(errors.New("Permission Denied: getUserInfoByName require admin permission"))
-			return otto.FalseValue()
-		}
-		return otto.TrueValue()
-	})
-
-	//Allow real time library includsion into the virtual machine
-	vm.Set("requirelib", func(call otto.FunctionCall) otto.Value {
-		libname, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			reply, _ := vm.ToValue(nil)
-			return reply
-		}
-
-		//Handle special case on high level libraries
-		if libname == "websocket" && w != nil && r != nil {
-			g.injectWebSocketFunctions(vm, u, w, r)
-			return otto.TrueValue()
-		} else {
-			//Check if the library name exists. If yes, run the initiation script on the vm
-			if entryPoint, ok := g.LoadedAGILibrary[libname]; ok {
-				entryPoint(vm, u)
-				return otto.TrueValue()
-			} else {
-				//Lib not exists
-				log.Println("Lib not found: " + libname)
-				return otto.FalseValue()
-			}
-		}
-
-		//Unknown status
-		return otto.FalseValue()
-	})
-
-	//Execd (Execute & detach) run another script and detach the execution
-	vm.Set("execd", func(call otto.FunctionCall) otto.Value {
-		//Check if the pkg is already registered
-		scriptName, err := call.Argument(0).ToString()
-		if err != nil {
-			g.raiseError(err)
-			return otto.FalseValue()
-		}
-
-		//Carry the payload to the forked process if there are any
-		payload, _ := call.Argument(1).ToString()
-
-		//Check if the script file exists
-		targetScriptPath := filepath.ToSlash(filepath.Join(filepath.Dir(scriptFile), scriptName))
-		if !fileExists(targetScriptPath) {
-			g.raiseError(errors.New("*AGI* Target path not exists!"))
-			return otto.FalseValue()
-		}
-
-		//Run the script
-		scriptContent, _ := ioutil.ReadFile(targetScriptPath)
-		go func() {
-			//Create a new VM to execute the script (also for isolation)
-			vm := otto.New()
-			//Inject standard libs into the vm
-			g.injectStandardLibs(vm, scriptFile, scriptScope)
-			g.injectUserFunctions(vm, scriptFile, scriptScope, u, w, r)
-
-			vm.Set("PARENT_DETACHED", true)
-			vm.Set("PARENT_PAYLOAD", payload)
-			_, err = vm.Run(string(scriptContent))
-			if err != nil {
-				//Script execution failed
-				log.Println("Script Execution Failed: ", err.Error())
-				g.raiseError(err)
-			}
-		}()
-
-		return otto.TrueValue()
-	})
-
-}
+package agi
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"path/filepath"
+
+	"github.com/robertkrimen/otto"
+	"imuslab.com/arozos/mod/filesystem"
+	"imuslab.com/arozos/mod/filesystem/arozfs"
+	user "imuslab.com/arozos/mod/user"
+)
+
+//Define path translation function
+func virtualPathToRealPath(vpath string, u *user.User) (*filesystem.FileSystemHandler, string, error) {
+	fsh, err := u.GetFileSystemHandlerFromVirtualPath(vpath)
+	if err != nil {
+		return nil, "", err
+	}
+	rpath, err := fsh.FileSystemAbstraction.VirtualPathToRealPath(vpath, u.Username)
+	if err != nil {
+		return nil, "", err
+	}
+	return fsh, rpath, nil
+}
+
+func realpathToVirtualpath(fsh *filesystem.FileSystemHandler, path string, u *user.User) (string, error) {
+	return fsh.FileSystemAbstraction.RealPathToVirtualPath(path, u.Username)
+}
+
+//Inject user based functions into the virtual machine
+//Note that the fsh might be nil and scriptPath must be real path of script being executed
+//**Use local file system check if fsh == nil**
+func (g *Gateway) injectUserFunctions(vm *otto.Otto, fsh *filesystem.FileSystemHandler, scriptPath string, scriptScope string, u *user.User, w http.ResponseWriter, r *http.Request) {
+	username := u.Username
+	vm.Set("USERNAME", username)
+	vm.Set("USERICON", u.GetUserIcon())
+	vm.Set("USERQUOTA_TOTAL", u.StorageQuota.TotalStorageQuota)
+	vm.Set("USERQUOTA_USED", u.StorageQuota.UsedStorageQuota)
+	vm.Set("USER_VROOTS", u.GetAllAccessibleFileSystemHandler())
+	vm.Set("USER_MODULES", u.GetUserAccessibleModules())
+
+	//File system and path related
+	vm.Set("decodeVirtualPath", func(call otto.FunctionCall) otto.Value {
+		log.Println("Call to deprecated function decodeVirtualPath")
+		return otto.FalseValue()
+	})
+
+	vm.Set("decodeAbsoluteVirtualPath", func(call otto.FunctionCall) otto.Value {
+		log.Println("Call to deprecated function decodeAbsoluteVirtualPath")
+		return otto.FalseValue()
+	})
+
+	vm.Set("encodeRealPath", func(call otto.FunctionCall) otto.Value {
+		log.Println("Call to deprecated function encodeRealPath")
+		return otto.FalseValue()
+	})
+
+	//Check if a given virtual path is readonly
+	vm.Set("pathCanWrite", func(call otto.FunctionCall) otto.Value {
+		vpath, _ := call.Argument(0).ToString()
+		if u.CanWrite(vpath) {
+			return otto.TrueValue()
+		} else {
+			return otto.FalseValue()
+		}
+	})
+
+	//Permission related
+	vm.Set("getUserPermissionGroup", func(call otto.FunctionCall) otto.Value {
+		groupinfo := u.GetUserPermissionGroup()
+		jsonString, _ := json.Marshal(groupinfo)
+		reply, _ := vm.ToValue(string(jsonString))
+		return reply
+	})
+
+	vm.Set("userIsAdmin", func(call otto.FunctionCall) otto.Value {
+		reply, _ := vm.ToValue(u.IsAdmin())
+		return reply
+	})
+
+	//User Account Related
+	/*
+		userExists(username);
+	*/
+	vm.Set("userExists", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+			//Get username from function paramter
+			username, err := call.Argument(0).ToString()
+			if err != nil || username == "undefined" {
+				g.raiseError(errors.New("username is undefined"))
+				reply, _ := vm.ToValue(nil)
+				return reply
+			}
+
+			//Check if user exists
+			userExists := u.Parent().GetAuthAgent().UserExists(username)
+			if userExists {
+				return otto.TrueValue()
+			} else {
+				return otto.FalseValue()
+			}
+
+		} else {
+			g.raiseError(errors.New("Permission Denied: userExists require admin permission"))
+			return otto.FalseValue()
+		}
+
+	})
+
+	/*
+		createUser(username, password, defaultGroup);
+	*/
+	vm.Set("createUser", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+			//Ok. Create user base on given information
+			username, err := call.Argument(0).ToString()
+			if err != nil || username == "undefined" {
+				g.raiseError(errors.New("username is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			password, err := call.Argument(1).ToString()
+			if err != nil || password == "undefined" {
+				g.raiseError(errors.New("password is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			defaultGroup, err := call.Argument(2).ToString()
+			if err != nil || defaultGroup == "undefined" {
+				g.raiseError(errors.New("defaultGroup is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Check if username already used
+			userExists := u.Parent().GetAuthAgent().UserExists(username)
+			if userExists {
+				g.raiseError(errors.New("Username already exists"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Check if the given permission group exists
+			groupExists := u.Parent().GetPermissionHandler().GroupExists(defaultGroup)
+			if !groupExists {
+				g.raiseError(errors.New(defaultGroup + " user-group not exists"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Create the user
+			err = u.Parent().GetAuthAgent().CreateUserAccount(username, password, []string{defaultGroup})
+
+			if err != nil {
+				g.raiseError(errors.New("User creation failed: " + err.Error()))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			return otto.TrueValue()
+		} else {
+			g.raiseError(errors.New("Permission Denied: createUser require admin permission"))
+			return otto.FalseValue()
+		}
+
+	})
+
+	vm.Set("editUser", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+
+		} else {
+			g.raiseError(errors.New("Permission Denied: editUser require admin permission"))
+			return otto.FalseValue()
+		}
+		//libname, err := call.Argument(0).ToString()
+		return otto.FalseValue()
+	})
+
+	/*
+		removeUser(username)
+	*/
+	vm.Set("removeUser", func(call otto.FunctionCall) otto.Value {
+		if u.IsAdmin() {
+			//Get username from function paramters
+			username, err := call.Argument(0).ToString()
+			if err != nil || username == "undefined" {
+				g.raiseError(errors.New("username is undefined"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//Check if the user exists
+			userExists := u.Parent().GetAuthAgent().UserExists(username)
+			if !userExists {
+				g.raiseError(errors.New(username + " not exists"))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			//User exists. Remove it from the system
+			err = u.Parent().GetAuthAgent().UnregisterUser(username)
+			if err != nil {
+				g.raiseError(errors.New("User removal failed: " + err.Error()))
+				reply, _ := vm.ToValue(false)
+				return reply
+			}
+
+			return otto.TrueValue()
+		} else {
+			g.raiseError(errors.New("Permission Denied: removeUser require admin permission"))
+			return otto.FalseValue()
+		}
+	})
+
+	vm.Set("getUserInfoByName", func(call otto.FunctionCall) otto.Value {
+		//libname, err := call.Argument(0).ToString()
+		if u.IsAdmin() {
+
+		} else {
+
+			g.raiseError(errors.New("Permission Denied: getUserInfoByName require admin permission"))
+			return otto.FalseValue()
+		}
+		return otto.TrueValue()
+	})
+
+	//Allow real time library includsion into the virtual machine
+	vm.Set("requirelib", func(call otto.FunctionCall) otto.Value {
+		libname, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			reply, _ := vm.ToValue(nil)
+			return reply
+		}
+
+		//Handle special case on high level libraries
+		if libname == "websocket" && w != nil && r != nil {
+			g.injectWebSocketFunctions(vm, u, w, r)
+			return otto.TrueValue()
+		} else {
+			//Check if the library name exists. If yes, run the initiation script on the vm
+			if entryPoint, ok := g.LoadedAGILibrary[libname]; ok {
+				entryPoint(vm, u, fsh, scriptPath)
+				return otto.TrueValue()
+			} else {
+				//Lib not exists
+				log.Println("Lib not found: " + libname)
+				return otto.FalseValue()
+			}
+		}
+	})
+
+	//Execd (Execute & detach) run another script and detach the execution
+	vm.Set("execd", func(call otto.FunctionCall) otto.Value {
+		//Check if the pkg is already registered
+		scriptName, err := call.Argument(0).ToString()
+		if err != nil {
+			g.raiseError(err)
+			return otto.FalseValue()
+		}
+
+		//Carry the payload to the forked process if there are any
+		payload, _ := call.Argument(1).ToString()
+
+		//Check if the script file exists
+		targetScriptPath := arozfs.ToSlash(filepath.Join(filepath.Dir(scriptPath), scriptName))
+		if fsh != nil {
+			if !fsh.FileSystemAbstraction.FileExists(targetScriptPath) {
+				g.raiseError(errors.New("[AGI] Target path not exists!"))
+				return otto.FalseValue()
+			}
+		} else {
+			if !filesystem.FileExists(targetScriptPath) {
+				g.raiseError(errors.New("[AGI] Target path not exists!"))
+				return otto.FalseValue()
+			}
+		}
+
+		//Run the script
+		scriptContent, _ := ioutil.ReadFile(targetScriptPath)
+		go func() {
+			//Create a new VM to execute the script (also for isolation)
+			vm := otto.New()
+			//Inject standard libs into the vm
+			g.injectStandardLibs(vm, scriptPath, scriptScope)
+			g.injectUserFunctions(vm, fsh, scriptPath, scriptScope, u, w, r)
+
+			vm.Set("PARENT_DETACHED", true)
+			vm.Set("PARENT_PAYLOAD", payload)
+			_, err = vm.Run(string(scriptContent))
+			if err != nil {
+				//Script execution failed
+				log.Println("Script Execution Failed: ", err.Error())
+				g.raiseError(err)
+			}
+		}()
+
+		return otto.TrueValue()
+	})
+
+}