Browse Source

Added WebApp hot reload function

Toby Chui 3 years ago
parent
commit
caf13c0d0d
8 changed files with 108 additions and 31 deletions
  1. 1 1
      agi.go
  2. 17 12
      mod/agi/agi.go
  3. 5 0
      mod/agi/systemFunc.go
  4. 19 9
      mod/modules/installer.go
  5. 38 8
      mod/modules/module.go
  6. 1 1
      mod/subservice/subservice.go
  7. 14 0
      module.go
  8. 13 0
      web/SystemAO/modules/moduleList.html

+ 1 - 1
agi.go

@@ -18,7 +18,7 @@ func AGIInit() {
 		InternalVersion:      internal_version,
 		LoadedModule:         moduleHandler.GetModuleNameList(),
 		ReservedTables:       []string{"auth", "permisson", "desktop"},
-		ModuleRegisterParser: moduleHandler.RegisterModuleFromJSON,
+		ModuleRegisterParser: moduleHandler.RegisterModuleFromAGI,
 		PackageManager:       packageManager,
 		UserHandler:          userHandler,
 		StartupRoot:          "./web",

+ 17 - 12
mod/agi/agi.go

@@ -63,7 +63,6 @@ type Gateway struct {
 
 func NewGateway(option AgiSysInfo) (*Gateway, error) {
 	//Handle startup registration of ajgi modules
-	startupScripts, _ := filepath.Glob(filepath.ToSlash(filepath.Clean(option.StartupRoot)) + "/*/init.agi")
 	gatewayObject := Gateway{
 		ReservedTables:   option.ReservedTables,
 		AllowAccessPkgs:  map[string][]AgiPackage{},
@@ -71,6 +70,22 @@ func NewGateway(option AgiSysInfo) (*Gateway, error) {
 		Option:           &option,
 	}
 
+	//Start all WebApps Registration
+	gatewayObject.InitiateAllWebAppModules()
+
+	//Load all the other libs entry points into the memoary
+	gatewayObject.ImageLibRegister()
+	gatewayObject.FileLibRegister()
+	gatewayObject.HTTPLibRegister()
+	gatewayObject.ShareLibRegister()
+	gatewayObject.IoTLibRegister()
+	gatewayObject.AppdataLibRegister()
+
+	return &gatewayObject, nil
+}
+
+func (g *Gateway) InitiateAllWebAppModules() {
+	startupScripts, _ := filepath.Glob(filepath.ToSlash(filepath.Clean(g.Option.StartupRoot)) + "/*/init.agi")
 	for _, script := range startupScripts {
 		scriptContentByte, _ := ioutil.ReadFile(script)
 		scriptContent := string(scriptContentByte)
@@ -79,7 +94,7 @@ func NewGateway(option AgiSysInfo) (*Gateway, error) {
 		vm := otto.New()
 
 		//Only allow non user based operations
-		gatewayObject.injectStandardLibs(vm, script, "./web/")
+		g.injectStandardLibs(vm, script, "./web/")
 
 		_, err := vm.Run(scriptContent)
 		if err != nil {
@@ -88,16 +103,6 @@ func NewGateway(option AgiSysInfo) (*Gateway, error) {
 			continue
 		}
 	}
-
-	//Load all the other libs entry points into the memoary
-	gatewayObject.ImageLibRegister()
-	gatewayObject.FileLibRegister()
-	gatewayObject.HTTPLibRegister()
-	gatewayObject.ShareLibRegister()
-	gatewayObject.IoTLibRegister()
-	gatewayObject.AppdataLibRegister()
-
-	return &gatewayObject, nil
 }
 
 func (g *Gateway) RunScript(script string) error {

+ 5 - 0
mod/agi/systemFunc.go

@@ -34,6 +34,11 @@ func (g *Gateway) injectStandardLibs(vm *otto.Otto, scriptFile string, scriptSco
 		return otto.Value{}
 	})
 
+	vm.Set("sendOK", func(call otto.FunctionCall) otto.Value {
+		vm.Set("HTTP_RESP", "ok")
+		return otto.Value{}
+	})
+
 	vm.Set("sendJSONResp", func(call otto.FunctionCall) otto.Value {
 		argString, _ := call.Argument(0).ToString()
 		vm.Set("HTTP_HEADER", "application/json")

+ 19 - 9
mod/modules/installer.go

@@ -15,7 +15,6 @@ import (
 	uuid "github.com/satori/go.uuid"
 	agi "imuslab.com/arozos/mod/agi"
 	fs "imuslab.com/arozos/mod/filesystem"
-	user "imuslab.com/arozos/mod/user"
 )
 
 /*
@@ -70,6 +69,23 @@ func (m *ModuleHandler) InstallViaZip(realpath string, gateway *agi.Gateway) err
 	return nil
 }
 
+//Reload all modules from agi file again
+func (m *ModuleHandler) ReloadAllModules(gateway *agi.Gateway) error {
+	//Clear the current registered module list
+	newModuleList := []*ModuleInfo{}
+	for _, thisModule := range m.LoadedModule {
+		if !thisModule.allowReload {
+			//This module is registered by system. Do not allow reload
+			newModuleList = append(newModuleList, thisModule)
+		}
+	}
+	m.LoadedModule = newModuleList
+	//Reload all webapp init.agi gateway script from source
+	gateway.InitiateAllWebAppModules()
+	m.ModuleSortList()
+	return nil
+}
+
 //Install a module via git clone
 func (m *ModuleHandler) InstallModuleViaGit(gitURL string, gateway *agi.Gateway) error {
 	//Download the module from the gitURL
@@ -210,16 +226,10 @@ func (m *ModuleHandler) HandleModuleInstallationListing(w http.ResponseWriter, r
 	sendJSONResponse(w, string(js))
 }
 
-//Install a module from zip file located in the given vpath
-func (m *ModuleHandler) InstallModuleFromZip(vpath string, u *user.User) error {
-
-	return nil
-}
-
 //Uninstall the given module
 func (m *ModuleHandler) UninstallModule(moduleName string) error {
 	//Check if this module is allowed to be removed
-	var targetModuleInfo ModuleInfo
+	var targetModuleInfo *ModuleInfo = nil
 	for _, mod := range m.LoadedModule {
 		if mod.Name == moduleName {
 			targetModuleInfo = mod
@@ -239,7 +249,7 @@ func (m *ModuleHandler) UninstallModule(moduleName string) error {
 		os.RemoveAll(filepath.Join("./web", moduleName))
 
 		//Unregister the module from loaded list
-		newLoadedModuleList := []ModuleInfo{}
+		newLoadedModuleList := []*ModuleInfo{}
 		for _, thisModule := range m.LoadedModule {
 			if thisModule.Name != moduleName {
 				newLoadedModuleList = append(newLoadedModuleList, thisModule)

+ 38 - 8
mod/modules/module.go

@@ -23,17 +23,20 @@ type ModuleInfo struct {
 	InitFWSize   []int    //Floatwindow init size. [0] => Width, [1] => Height
 	InitEmbSize  []int    //Embedded mode init size. [0] => Width, [1] => Height
 	SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
+
+	//Hidden properties
+	allowReload bool //Allow module reload by user
 }
 
 type ModuleHandler struct {
-	LoadedModule []ModuleInfo
+	LoadedModule []*ModuleInfo
 	userHandler  *user.UserHandler
 	tmpDirectory string
 }
 
 func NewModuleHandler(userHandler *user.UserHandler, tmpFolderPath string) *ModuleHandler {
 	return &ModuleHandler{
-		LoadedModule: []ModuleInfo{},
+		LoadedModule: []*ModuleInfo{},
 		userHandler:  userHandler,
 		tmpDirectory: tmpFolderPath,
 	}
@@ -41,7 +44,7 @@ func NewModuleHandler(userHandler *user.UserHandler, tmpFolderPath string) *Modu
 
 //Register endpoint. Provide moduleInfo datastructure or unparsed json
 func (m *ModuleHandler) RegisterModule(module ModuleInfo) {
-	m.LoadedModule = append(m.LoadedModule, module)
+	m.LoadedModule = append(m.LoadedModule, &module)
 }
 
 //Sort the module list
@@ -52,16 +55,43 @@ func (m *ModuleHandler) ModuleSortList() {
 }
 
 //Register a module from JSON string
-func (m *ModuleHandler) RegisterModuleFromJSON(jsonstring string) error {
+func (m *ModuleHandler) RegisterModuleFromJSON(jsonstring string, allowReload bool) error {
+	var thisModuleInfo ModuleInfo
+	err := json.Unmarshal([]byte(jsonstring), &thisModuleInfo)
+	if err != nil {
+		return err
+	}
+
+	thisModuleInfo.allowReload = allowReload
+	m.RegisterModule(thisModuleInfo)
+	return nil
+}
+
+//Register a module from AGI script
+func (m *ModuleHandler) RegisterModuleFromAGI(jsonstring string) error {
 	var thisModuleInfo ModuleInfo
 	err := json.Unmarshal([]byte(jsonstring), &thisModuleInfo)
 	if err != nil {
 		return err
 	}
+
+	//AGI interface loaded module must allow runtime reload
+	thisModuleInfo.allowReload = true
 	m.RegisterModule(thisModuleInfo)
 	return nil
 }
 
+func (m *ModuleHandler) DeregisterModule(moduleName string) {
+	newLoadedModuleList := []*ModuleInfo{}
+	for _, thisModule := range m.LoadedModule {
+		if thisModule.Name != moduleName {
+			newLoadedModuleList = append(newLoadedModuleList, thisModule)
+		}
+	}
+
+	m.LoadedModule = newLoadedModuleList
+}
+
 //Get a list of module names
 func (m *ModuleHandler) GetModuleNameList() []string {
 	result := []string{}
@@ -101,7 +131,7 @@ func (m *ModuleHandler) HandleDefaultLauncher(w http.ResponseWriter, r *http.Req
 			return
 		}
 		//Get the launch paramter of this module
-		var modInfo ModuleInfo
+		var modInfo *ModuleInfo = nil
 		modExists := false
 		for _, mod := range m.LoadedModule {
 			if mod.Name == value {
@@ -167,7 +197,7 @@ func (m *ModuleHandler) ListLoadedModules(w http.ResponseWriter, r *http.Request
 	userinfo, _ := m.userHandler.GetUserInfoFromRequest(w, r)
 
 	///Parse a list of modules where the user has permission to access
-	userAccessableModules := []ModuleInfo{}
+	userAccessableModules := []*ModuleInfo{}
 	for _, thisModule := range m.LoadedModule {
 		thisModuleName := thisModule.Name
 		if userinfo.GetModuleAccessPermission(thisModuleName) {
@@ -185,7 +215,7 @@ func (m *ModuleHandler) ListLoadedModules(w http.ResponseWriter, r *http.Request
 func (m *ModuleHandler) GetModuleInfoByID(moduleid string) *ModuleInfo {
 	for _, module := range m.LoadedModule {
 		if module.Name == moduleid {
-			return &module
+			return module
 		}
 	}
 	return nil
@@ -199,7 +229,7 @@ func (m *ModuleHandler) GetLaunchParameter(w http.ResponseWriter, r *http.Reques
 	}
 
 	//Loop through the modules and see if the module exists.
-	var targetLaunchInfo ModuleInfo
+	var targetLaunchInfo *ModuleInfo = nil
 	found := false
 	for _, module := range m.LoadedModule {
 		thisModuleName := module.Name

+ 1 - 1
mod/subservice/subservice.go

@@ -301,7 +301,7 @@ func (sr *SubServiceRouter) Launch(servicePath string, startupMode bool) error {
 	sr.RunningSubService = append(sr.RunningSubService, thisSubService)
 
 	//Append this module into the loaded module list
-	sr.moduleHandler.LoadedModule = append(sr.moduleHandler.LoadedModule, thisModuleInfo)
+	sr.moduleHandler.LoadedModule = append(sr.moduleHandler.LoadedModule, &thisModuleInfo)
 
 	return nil
 }

+ 14 - 0
module.go

@@ -17,6 +17,15 @@ func ModuleServiceInit() {
 	//Create a new module handler
 	moduleHandler = module.NewModuleHandler(userHandler, *tmp_directory)
 
+	//Register FTP Endpoints
+	adminRouter := prout.NewModuleRouter(prout.RouterOption{
+		AdminOnly:   true,
+		UserHandler: userHandler,
+		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
+			errorHandlePermissionDenied(w, r)
+		},
+	})
+
 	//Pass through the endpoint to authAgent
 	http.HandleFunc("/system/modules/list", func(w http.ResponseWriter, r *http.Request) {
 		authAgent.HandleCheckAuth(w, r, moduleHandler.ListLoadedModules)
@@ -28,6 +37,11 @@ func ModuleServiceInit() {
 		authAgent.HandleCheckAuth(w, r, moduleHandler.GetLaunchParameter)
 	})
 
+	adminRouter.HandleFunc("/system/modules/reload", func(w http.ResponseWriter, r *http.Request) {
+		moduleHandler.ReloadAllModules(AGIGateway)
+		sendOK(w)
+	})
+
 	//Handle module installer. Require admin
 	http.HandleFunc("/system/modules/installViaZip", func(w http.ResponseWriter, r *http.Request) {
 		//Check if the user is admin

+ 13 - 0
web/SystemAO/modules/moduleList.html

@@ -28,10 +28,23 @@
                   
                 </tbody>
               </table>
+              <div class="ui divider"></div>
+              <p>If you have installed WebApps manually, you can click the "Reload WebApps" button to load it without restarting ArozOS.</p>
+              <button class="ui basic small blue button" onclick="reloadWebapps();">
+                <i class="refresh icon"></i> Reload WebApps
+              </button>
+              <br><br>
         </div>
         <script>
             initModuleList();
 
+            function reloadWebapps(){
+                $("#moduleList").html(`<tr><td colspan="6"><i class="ui loading spinner icon"></i> Reloading...</tr></td>`);
+                $.get("../../system/modules/reload", function(data){
+                    initModuleList();
+                });
+            }
+
             function initModuleList(){
                 $("#moduleList").html("");
                 $.ajax({