diff --git a/foundation/app/data/packageConfig.go b/foundation/app/data/packageConfig.go index 85e555e..ce81ed3 100755 --- a/foundation/app/data/packageConfig.go +++ b/foundation/app/data/packageConfig.go @@ -28,21 +28,28 @@ const SYSTEM = "system" // identifies the package is installed on system lo const EXTERNAL = "external" // identifies the package is installed on external location const NODE_JS_DIR = "nodejs" // sub directory of binary dir, this is where node js scripts reside +type ActivityGroupConfig struct { + CustomLink string `json:"customLink"` + CustomPort int `json:"customPort"` + Activities []string `json:"activities"` // if app has activity. this contains definition of actions that will triggerr activity +} + // This structure represents package json file along with the binary. // this contains information about the application behaviour, permission and services. type PackageConfig struct { - Name string `json:"name"` // package name - Description string `json:"description"` // description of package - PackageId string `json:"packageId"` // identifies the package/application. this should be unique. format = company.package - Build int16 `json:"build"` // this should be incremental starting from 1 - Version string `json:"version"` // major.minor.patch - Program string `json:"program"` // the main program file to execute - ProgramArgs []string `json:"programArgs"` // arguments to pass to program + Name string `json:"name"` // package name + Description string `json:"description"` // description of package + PackageId string `json:"packageId"` // identifies the package/application. this should be unique. format = company.package + Build int16 `json:"build"` // this should be incremental starting from 1 + Version string `json:"version"` // major.minor.patch + Program string `json:"program"` // the main program file to execute + ProgramArgs []string `json:"programArgs"` // arguments to pass to program + ActivityGroup ActivityGroupConfig `json:"activityGroup,omitempty"` // www configuration // request permission for specific action/feature // if the specific action was called and was not defined. the process will be void - Permissions []string `json:"permissions"` - ExportServices bool `json:"exportService"` // true if the package contains services - Activities []string `json:"activities"` // if app has activity. this contains definition of actions that will triggerr activity + Permissions []string `json:"permissions"` + ExportServices bool `json:"exportService"` // true if the package contains services + BroacastListener []string `json:"actionListener"` // defined actions which action listener will listen to InstallLocation string `json:"location"` // either system or external Source string `json:"-"` // the source location @@ -130,10 +137,10 @@ func (c *PackageConfig) HasServices() bool { // use to check if contains activity that has action of func (c *PackageConfig) HasActivity(actionId string) bool { - if c.Activities == nil || len(c.Activities) == 0 { + if c.ActivityGroup.Activities == nil || len(c.ActivityGroup.Activities) == 0 { return false } - for _, act := range c.Activities { + for _, act := range c.ActivityGroup.Activities { if act == actionId { return true } diff --git a/foundation/constants/systemService.go b/foundation/constants/systemService.go index 5fb9cd7..63139ab 100755 --- a/foundation/constants/systemService.go +++ b/foundation/constants/systemService.go @@ -64,6 +64,10 @@ const APP_TERMINATE = "ela.system.APP_TERMINATE" const ACTION_APP_RESTART = "ela.system.APP_RESTART" const ACTION_APP_CLEAR_DATA = "ela.system.APP_CLEAR_DATA" +// initialize package. called after a package was installed +const ACTION_APP_INSTALLED = "ela.system.APP_INSTALLED" +const ACTION_APP_UNINSTALLED = "ela.system.APP_UNINSTALLED" + type AppRunningState int const ( diff --git a/internal/cwd/packageinstaller/activity.go b/internal/cwd/packageinstaller/activity.go index 241b459..ccfe65f 100755 --- a/internal/cwd/packageinstaller/activity.go +++ b/internal/cwd/packageinstaller/activity.go @@ -61,12 +61,14 @@ func (a *activity) OnPendingAction(action *data.Action) error { return a.startNormalInstall(pkgData) } return a.runCustomInstaller(sourcePkg, pkgData) + // uninstall package default: pkid := action.DataToString() if pkid == "" { return errors.SystemNew("failed to uninstall, no package id was provided as parameter", nil) } global.Logger.Info().Msg("start uninstall package " + pkid) + a.terminatePackage(pkid) return utils.UninstallPackage(pkid, global.DELETE_DATA_ONUNINSTALL, true, true) } } @@ -116,11 +118,18 @@ func (a *activity) finish(err string) { } else { global.Logger.Info().Msg("Install success") broadcast.UpdateSystem(a.currentPkg, broadcast.INSTALLED) + broadcast.OnPackageInstalled(a.currentPkg) } // comment this line. system will terminate this activity automatically //a.running = false } +func (a *activity) terminatePackage(pki string) { + if _, err := global.AppController.RPC.CallSystem(data.NewAction(constants.APP_TERMINATE, pki, nil)); err != nil { + global.Logger.Error().Err(err).Msg("failed to terminate package " + pki) + } +} + // callback from installer on progress func (a *activity) onInstallProgress(progress int, pkg string) { broadcast.UpdateProgress(pkg, progress) diff --git a/internal/cwd/packageinstaller/broadcast/normalInstall.go b/internal/cwd/packageinstaller/broadcast/normalInstall.go index 67303ce..a5f8323 100644 --- a/internal/cwd/packageinstaller/broadcast/normalInstall.go +++ b/internal/cwd/packageinstaller/broadcast/normalInstall.go @@ -5,6 +5,7 @@ package broadcast import ( "strconv" + appc "github.com/cansulting/elabox-system-tools/foundation/constants" "github.com/cansulting/elabox-system-tools/foundation/event/data" "github.com/cansulting/elabox-system-tools/internal/cwd/packageinstaller/constants" ) @@ -37,6 +38,17 @@ func UpdateSystem(pkgId string, status InstallState) { } } +// notify system that the installation is complete for specific package +func OnPackageInstalled(pki string) error { + _, err := constants.AppController.RPC.CallSystem(data.NewAction( + appc.ACTION_APP_INSTALLED, pki, nil, + )) + if err != nil { + return err + } + return nil +} + // broadcast error // @param pkgId - the package id currently installing func Error(pkgId string, code int, val string) { diff --git a/internal/cwd/system/appman/appConnect.go b/internal/cwd/system/appman/appConnect.go index fba728d..cf41b78 100755 --- a/internal/cwd/system/appman/appConnect.go +++ b/internal/cwd/system/appman/appConnect.go @@ -14,6 +14,7 @@ package appman import ( + "net/http" "os" "os/exec" "path/filepath" @@ -43,6 +44,8 @@ type AppConnect struct { launched bool // true if this app was launched RPC *RPCBridge nodejs *Nodejs + server *http.Server + initialized bool } // create new app connect @@ -65,11 +68,33 @@ func newAppConnect( nodejs: node, process: nil, Client: client, + initialized: false, } res.RPC = NewRPCBridge(pk.PackageId, res, global.Server.EventServer) return res } +// initialize web serving and services related to this app +func (app *AppConnect) init() error { + if app.initialized { + return nil + } + app.initialized = true + // www serving + if app.Config.ActivityGroup.CustomPort > 0 { + if err := app.startServeWww(); err != nil { + return err + } + } + // start services + if app.Config.ExportServices || app.Config.Nodejs { + ac := eventd.NewActionById(constants.ACTION_START_SERVICE) + app.PendingActions.AddPendingService(&ac) + return app.Launch() + } + return nil +} + // use to check if this app is currently running func (app *AppConnect) IsRunning() bool { if app.nodejs != nil { @@ -118,6 +143,7 @@ func (app *AppConnect) Launch() error { go asyncRun(app, cmd) } + app.launched = true return nil } @@ -187,10 +213,16 @@ func (app *AppConnect) Terminate() error { } }() + // stopping www + if app.Config.ActivityGroup.CustomPort > 0 { + app.stopServeWww() + } + time.Sleep(time.Second * time.Duration(global.APP_TERMINATE_COUNTDOWN)) if app.IsRunning() { return app.forceTerminate() } + return nil } @@ -216,3 +248,42 @@ func (n *AppConnect) Write(data []byte) (int, error) { print(string(data)) return len(data), nil } + +// start serving the www for app with custom port +func (n *AppConnect) startServeWww() error { + + // starts listening to port + go func(port int, pkg string, wwwPath string) { + global.Logger.Debug().Str("category", "web").Msg("Starting www custom port " + strconv.Itoa(port) + " for package " + pkg + ".") + serverMux := http.NewServeMux() + n.server = &http.Server{Addr: ":" + strconv.Itoa(port), Handler: serverMux} + // create file server for package + //serverMux.Handle("/", http.FileServer(http.Dir(wwwPath))) + path := "" + serverMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + path = wwwPath + r.URL.Path + if _, err := os.Stat(path); err == nil { + http.ServeFile(w, r, path) + return + } + http.ServeFile(w, r, wwwPath+"/index.html") + }) + if err := n.server.ListenAndServe(); err != nil { + global.Logger.Warn().Err(err).Str("category", "web").Caller().Msg("Failed to start listening to port for " + pkg + ".") + } + }(n.Config.ActivityGroup.CustomPort, n.Config.PackageId, n.Config.GetWWWDir()+"/"+n.Config.PackageId) + return nil +} + +// stop serving to specific port +func (n *AppConnect) stopServeWww() error { + if n.server == nil { + return nil + } + global.Logger.Debug().Str("category", "web").Msg("Stopping www custom port " + strconv.Itoa(n.Config.ActivityGroup.CustomPort) + " for package " + n.Config.PackageId + ".") + if err := n.server.Close(); err != nil { + return err + } + n.server = nil + return nil +} diff --git a/internal/cwd/system/appman/appConnectManager.go b/internal/cwd/system/appman/appConnectManager.go index 5f6d072..09ba1f4 100755 --- a/internal/cwd/system/appman/appConnectManager.go +++ b/internal/cwd/system/appman/appConnectManager.go @@ -19,11 +19,11 @@ import ( "errors" appd "github.com/cansulting/elabox-system-tools/foundation/app/data" - "github.com/cansulting/elabox-system-tools/foundation/constants" "github.com/cansulting/elabox-system-tools/foundation/event/data" "github.com/cansulting/elabox-system-tools/foundation/event/protocol" "github.com/cansulting/elabox-system-tools/foundation/logger" "github.com/cansulting/elabox-system-tools/internal/cwd/system/global" + "github.com/cansulting/elabox-system-tools/registry/app" registry "github.com/cansulting/elabox-system-tools/registry/app" ) @@ -135,19 +135,6 @@ func LaunchAppActivity( return err } -func LaunchAppService(pkgid string) (*AppConnect, error) { - app := GetAppConnect(pkgid, nil) - if app == nil { - return nil, errors.New("Package " + pkgid + " was not found.") - } - if app.launched { - return app, nil - } - ac := data.NewActionById(constants.ACTION_START_SERVICE) - app.PendingActions.AddPendingService(&ac) - return app, app.Launch() -} - // use to launch app func SendAppPendingAction( app *AppConnect, @@ -165,15 +152,50 @@ func SendAppPendingAction( } // run all start up apps -func InitializeStartups() { +func InitializeAllPackages() { global.Logger.Info().Msg("Services are starting up...") - pkgs, err := registry.RetrieveStartupPackages() + // pkgs, err := registry.RetrieveStartupPackages() + // if err != nil { + // global.Logger.Error().Err(err).Caller().Msg("Failed retrieving startup packages.") + // } + // for _, pkg := range pkgs { + // InitializePackage(pkg) + // } + pkgs, err := registry.RetrieveAllPackages() if err != nil { global.Logger.Error().Err(err).Caller().Msg("Failed retrieving startup packages.") + return } for _, pkg := range pkgs { - if _, err := LaunchAppService(pkg); err != nil { - global.Logger.Error().Err(err).Caller().Msg("Failed launching app.") + config, err := app.RetrievePackage(pkg) + if err != nil { + global.Logger.Error().Err(err).Caller().Msg("Failed retrieving package " + pkg) + continue + } + // should we initialize the package? + if !config.ExportServices && + !config.Nodejs && + config.ActivityGroup.CustomPort == 0 { + continue + } + if err := InitializePackage(pkg); err != nil { + global.Logger.Error().Err(err).Caller().Msg("Failed initializing package " + pkg) } } } + +// initialize specific package +func InitializePackage(pki string) error { + app := GetAppConnect(pki, nil) + if app == nil { + return errors.New("Package " + pki + " was not found.") + } + if app.launched { + return nil + } + if err := app.init(); err != nil { + global.Logger.Error().Err(err).Caller().Msg("Failed launching app.") + return err + } + return nil +} diff --git a/internal/cwd/system/appman/initializer.go b/internal/cwd/system/appman/initializer.go index cbf2a47..25a6a52 100755 --- a/internal/cwd/system/appman/initializer.go +++ b/internal/cwd/system/appman/initializer.go @@ -5,7 +5,7 @@ import "github.com/cansulting/elabox-system-tools/internal/cwd/system/global" func Initialize(commandline bool) error { if !commandline { if global.RUN_STARTUPAPPS { - InitializeStartups() + InitializeAllPackages() } else { global.Logger.Debug().Msg("Startup apps was disabled.") } diff --git a/internal/cwd/system/servicecenter/serviceRequest.go b/internal/cwd/system/servicecenter/serviceRequest.go index 802d646..64bdc8c 100755 --- a/internal/cwd/system/servicecenter/serviceRequest.go +++ b/internal/cwd/system/servicecenter/serviceRequest.go @@ -68,6 +68,8 @@ func OnRecievedRequest( return onAppRestart(client, action) case constants.ACTION_APP_CLEAR_DATA: return onAppClearData(client, action) + case constants.ACTION_APP_INSTALLED: + return initPackage(action) case constants.SYSTEM_UPDATE_MODE: return activateUpdateMode(client, action) case constants.SYSTEM_TERMINATE: @@ -201,7 +203,7 @@ func startService(action data.Action) string { if pkgid == "" { return rpc.CreateResponse(rpc.INVALID_CODE, "package id shouldnt be empty") } - _, err := appman.LaunchAppService(pkgid) + err := appman.InitializePackage(pkgid) if err != nil { return rpc.CreateResponse(rpc.INVALID_CODE, err.Error()) } @@ -285,3 +287,14 @@ func terminate(seconds uint) string { }() return rpc.CreateSuccessResponse("Terminated") } + +// initialize and start newly instlled package +func initPackage(action data.Action) string { + global.Logger.Debug().Msg("Initializing package " + action.PackageId) + pki := action.PackageId + if err := appman.InitializePackage(pki); err != nil { + global.Logger.Error().Err(err).Msg("Failed to initialize package " + pki) + return rpc.CreateResponse(rpc.SYSTEMERR_CODE, err.Error()) + } + return rpc.CreateSuccessResponse("Initialized") +} diff --git a/internal/scripts/build.sh b/internal/scripts/build.sh index bdcc673..31cd205 100755 --- a/internal/scripts/build.sh +++ b/internal/scripts/build.sh @@ -213,12 +213,13 @@ if [ "$answerEla" == "y" ]; then mkdir -p $eidbin cp ${EID_SRC}/build/bin/geth $eidbin chmod +x $eidbin/geth + mv $escbin/geth $eidbin/ela.eid # esc escbin=$buildpath/esc/bin mkdir -p $escbin cp ${ESC_SRC}/build/bin/geth $escbin chmod +x $escbin/geth - mv $escbin/geth $escbin/esc + mv $escbin/geth $escbin/ela.esc # carrier carrierlib=$buildpath/carrier/bin mkdir -p $carrierlib diff --git a/internal/test/registry_test.go b/internal/test/registry_test.go index 821843d..a67faae 100755 --- a/internal/test/registry_test.go +++ b/internal/test/registry_test.go @@ -34,7 +34,7 @@ func TestRetrieveAllPackages(test *testing.T) { test.Error(err) } for _, pk := range pks { - log.Println(pk.ToJson()) + log.Println(pk) } } diff --git a/registry/app/app.go b/registry/app/app.go index 2e8c78a..12430a9 100755 --- a/registry/app/app.go +++ b/registry/app/app.go @@ -24,14 +24,21 @@ import ( */ // retrieve all packages -func RetrieveAllPackages() ([]*data.PackageConfig, error) { - row, err := retrievePackagesRaw("", []string{"id, source, version, name, location, nodejs, program, build"}) +func RetrieveAllPackages() ([]string, error) { + row, err := retrievePackagesRaw("", []string{"id"}) if err != nil { return nil, err } defer row.Close() - results := convertRawToPackageConfig(row) + results := make([]string, 0, 10) + for row.Next() { + pk := "" + row.Scan(&pk) + results = append(results, pk) + } + // results := convertRawToPackageConfig(row) return results, nil + } // add package data to db diff --git a/registry/app/components.go b/registry/app/components.go index 9413a88..ac0bca3 100755 --- a/registry/app/components.go +++ b/registry/app/components.go @@ -75,7 +75,7 @@ func registerActivities(pkg *data.PackageConfig) error { } query := "" //args := make([]interface{}, 0, 6) - for _, activity := range pkg.Activities { + for _, activity := range pkg.ActivityGroup.Activities { query = query + "insert into activities(packageId, action) values(?,?);" //args = append(args, pkg.PackageId, activity) if err := util.ExecuteQuery(query, pkg.PackageId, activity); err != nil {