module.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. package modules
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "path/filepath"
  6. "sort"
  7. "strings"
  8. "imuslab.com/arozos/mod/user"
  9. "imuslab.com/arozos/mod/utils"
  10. )
  11. type ModuleInfo struct {
  12. Name string //Name of this module. e.g. "Audio"
  13. Desc string //Description for this module
  14. Group string //Group of the module, e.g. "system" / "media" etc
  15. IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
  16. Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
  17. StartDir string //Default starting dir, e.g. "Audio/index.html"
  18. SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
  19. LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
  20. SupportEmb bool //Support embedded mode
  21. LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
  22. InitFWSize []int //Floatwindow init size. [0] => Width, [1] => Height
  23. InitEmbSize []int //Embedded mode init size. [0] => Width, [1] => Height
  24. SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
  25. //Folder this module was registered from, relative to the startup directory,
  26. //e.g. "web/Office" or "subservice/demo". AGI scripts in this folder are
  27. //accessible to users with permission to this module. Never read from or
  28. //written to JSON, so a module cannot claim another app's folder.
  29. SourceFolder string `json:"-"`
  30. //Hidden properties
  31. allowReload bool //Allow module reload by user
  32. }
  33. type ModuleHandler struct {
  34. LoadedModule []*ModuleInfo
  35. userHandler *user.UserHandler
  36. tmpDirectory string
  37. extIconRegistry map[string]string // lowercased ext (no dot) --> web-root-relative icon path
  38. // OnModuleUninstall is an optional hook called when a module is uninstalled.
  39. // The hook receives the module name and can be used to clean up associated resources
  40. // (e.g. remove scheduled cron jobs registered by that module).
  41. OnModuleUninstall func(moduleName string)
  42. }
  43. func NewModuleHandler(userHandler *user.UserHandler, tmpFolderPath string) *ModuleHandler {
  44. return &ModuleHandler{
  45. LoadedModule: []*ModuleInfo{},
  46. userHandler: userHandler,
  47. tmpDirectory: tmpFolderPath,
  48. extIconRegistry: map[string]string{},
  49. }
  50. }
  51. // RegisterExtIcon stores a web-root-relative icon path for the given file extension.
  52. // ext may include or omit a leading dot; it is normalised to lowercase without dot.
  53. func (m *ModuleHandler) RegisterExtIcon(ext, iconPath string) {
  54. ext = strings.ToLower(strings.TrimPrefix(ext, "."))
  55. m.extIconRegistry[ext] = iconPath
  56. }
  57. // HandleGetExtIcons serves the extension-to-icon registry as a JSON object.
  58. func (m *ModuleHandler) HandleGetExtIcons(w http.ResponseWriter, r *http.Request) {
  59. js, _ := json.Marshal(m.extIconRegistry)
  60. utils.SendJSONResponse(w, string(js))
  61. }
  62. // IsUniversalGroup reports whether modules of the given group are open to
  63. // every user without a permission group grant. Engineering apps are file
  64. // viewers and tools on the user's own files, so they stay as open as the
  65. // Utilities they were split from.
  66. func IsUniversalGroup(group string) bool {
  67. switch strings.ToLower(strings.TrimSpace(group)) {
  68. case "utilities", "system tools":
  69. return true
  70. }
  71. return false
  72. }
  73. // Register endpoint. Provide moduleInfo datastructure or unparsed json
  74. func (m *ModuleHandler) RegisterModule(module ModuleInfo) {
  75. m.LoadedModule = append(m.LoadedModule, &module)
  76. //Add the module into universal module if every user may use its group
  77. if IsUniversalGroup(module.Group) {
  78. m.userHandler.UniversalModules = append(m.userHandler.UniversalModules, module.Name)
  79. }
  80. }
  81. // Sort the module list
  82. func (m *ModuleHandler) ModuleSortList() {
  83. sort.Slice(m.LoadedModule, func(i, j int) bool {
  84. return m.LoadedModule[i].Name < m.LoadedModule[j].Name
  85. })
  86. }
  87. // Register a module from JSON string
  88. func (m *ModuleHandler) RegisterModuleFromJSON(jsonstring string, allowReload bool) error {
  89. var thisModuleInfo ModuleInfo
  90. err := json.Unmarshal([]byte(jsonstring), &thisModuleInfo)
  91. if err != nil {
  92. return err
  93. }
  94. thisModuleInfo.allowReload = allowReload
  95. m.RegisterModule(thisModuleInfo)
  96. return nil
  97. }
  98. // Register a module from AGI script. sourceFolder is the folder of the init.agi
  99. // that registered it (e.g. "web/Office"), empty if unknown.
  100. func (m *ModuleHandler) RegisterModuleFromAGI(sourceFolder string, jsonstring string) error {
  101. var thisModuleInfo ModuleInfo
  102. err := json.Unmarshal([]byte(jsonstring), &thisModuleInfo)
  103. if err != nil {
  104. return err
  105. }
  106. //AGI interface loaded module must allow runtime reload
  107. thisModuleInfo.allowReload = true
  108. thisModuleInfo.SourceFolder = sourceFolder
  109. m.RegisterModule(thisModuleInfo)
  110. return nil
  111. }
  112. func (m *ModuleHandler) DeregisterModule(moduleName string) {
  113. newLoadedModuleList := []*ModuleInfo{}
  114. for _, thisModule := range m.LoadedModule {
  115. if thisModule.Name != moduleName {
  116. newLoadedModuleList = append(newLoadedModuleList, thisModule)
  117. }
  118. }
  119. m.LoadedModule = newLoadedModuleList
  120. }
  121. // Get the names of the modules registered from the given source folder,
  122. // e.g. "web/Office" -> Docs, Sheets, Slides. The folder is matched ignoring
  123. // case, like module names in permission checks.
  124. func (m *ModuleHandler) GetModulesFromSourceFolder(folder string) []string {
  125. result := []string{}
  126. if folder == "" {
  127. return result
  128. }
  129. folder = filepath.ToSlash(filepath.Clean(folder))
  130. for _, module := range m.LoadedModule {
  131. if module.SourceFolder != "" && strings.EqualFold(module.SourceFolder, folder) {
  132. result = append(result, module.Name)
  133. }
  134. }
  135. return result
  136. }
  137. // Get a list of module names
  138. func (m *ModuleHandler) GetModuleNameList() []string {
  139. result := []string{}
  140. for _, module := range m.LoadedModule {
  141. result = append(result, module.Name)
  142. }
  143. return result
  144. }
  145. // GetModuleListJSONForUser returns a JSON string of all modules the given username can access
  146. func (m *ModuleHandler) GetModuleListJSONForUser(username string) string {
  147. userinfo, err := m.userHandler.GetUserInfoFromUsername(username)
  148. if err != nil {
  149. return "[]"
  150. }
  151. accessable := []*ModuleInfo{}
  152. for _, mod := range m.LoadedModule {
  153. if userinfo.GetModuleAccessPermission(mod.Name) {
  154. accessable = append(accessable, mod)
  155. }
  156. }
  157. js, _ := json.Marshal(accessable)
  158. return string(js)
  159. }
  160. // Handle Default Launcher
  161. func (m *ModuleHandler) HandleDefaultLauncher(w http.ResponseWriter, r *http.Request) {
  162. username, _ := m.userHandler.GetAuthAgent().GetUserName(w, r)
  163. opr, _ := utils.GetPara(r, "opr") //Operation, accept {get, set, launch}
  164. ext, _ := utils.GetPara(r, "ext")
  165. moduleName, _ := utils.GetPara(r, "module")
  166. ext = strings.ToLower(ext)
  167. //Check if the default folder exists.
  168. switch opr {
  169. case "get":
  170. //Get the opener for this file type
  171. value := ""
  172. err := m.userHandler.GetDatabase().Read("module", "default/"+username+"/"+ext, &value)
  173. if err != nil {
  174. utils.SendErrorResponse(w, "No default opener")
  175. return
  176. }
  177. js, _ := json.Marshal(value)
  178. utils.SendJSONResponse(w, string(js))
  179. return
  180. case "launch":
  181. //Get launch paramter for this extension
  182. value := ""
  183. err := m.userHandler.GetDatabase().Read("module", "default/"+username+"/"+ext, &value)
  184. if err != nil {
  185. utils.SendErrorResponse(w, "No default opener")
  186. return
  187. }
  188. //Get the launch paramter of this module
  189. var modInfo *ModuleInfo = nil
  190. modExists := false
  191. for _, mod := range m.LoadedModule {
  192. if mod.Name == value {
  193. modInfo = mod
  194. modExists = true
  195. }
  196. }
  197. if !modExists {
  198. //This module has been removed or not exists anymore
  199. utils.SendErrorResponse(w, "Default opener no longer exists.")
  200. return
  201. } else {
  202. //Return launch inforamtion
  203. jsonString, _ := json.Marshal(modInfo)
  204. utils.SendJSONResponse(w, string(jsonString))
  205. }
  206. case "set":
  207. //Set the opener for this filetype
  208. if moduleName == "" {
  209. utils.SendErrorResponse(w, "Missing paratmer 'module'")
  210. return
  211. }
  212. //Check if module name exists
  213. moduleValid := false
  214. for _, mod := range m.LoadedModule {
  215. if mod.Name == moduleName {
  216. moduleValid = true
  217. }
  218. }
  219. if moduleValid {
  220. m.userHandler.GetDatabase().Write("module", "default/"+username+"/"+ext, moduleName)
  221. utils.SendJSONResponse(w, "\"OK\"")
  222. } else {
  223. utils.SendErrorResponse(w, "Given module not exists.")
  224. }
  225. case "list":
  226. //List all the values that belongs to default opener
  227. dbDump, _ := m.userHandler.GetDatabase().ListTable("module")
  228. results := [][]string{}
  229. for _, entry := range dbDump {
  230. key := string(entry[0])
  231. if strings.Contains(key, "default/"+username+"/") {
  232. //This is a correct matched entry
  233. extInfo := strings.Split(key, "/")
  234. ext := extInfo[len(extInfo)-1:]
  235. moduleName := ""
  236. json.Unmarshal(entry[1], &moduleName)
  237. results = append(results, []string{ext[0], moduleName})
  238. }
  239. }
  240. jsonString, _ := json.Marshal(results)
  241. utils.SendJSONResponse(w, string(jsonString))
  242. return
  243. }
  244. }
  245. func (m *ModuleHandler) ListLoadedModules(w http.ResponseWriter, r *http.Request) {
  246. userinfo, _ := m.userHandler.GetUserInfoFromRequest(w, r)
  247. ///Parse a list of modules where the user has permission to access
  248. userAccessableModules := []*ModuleInfo{}
  249. for _, thisModule := range m.LoadedModule {
  250. thisModuleName := thisModule.Name
  251. if userinfo.GetModuleAccessPermission(thisModuleName) {
  252. userAccessableModules = append(userAccessableModules, thisModule)
  253. }
  254. }
  255. //Return the loaded modules as a list of JSON string
  256. jsonString, _ := json.Marshal(userAccessableModules)
  257. utils.SendJSONResponse(w, string(jsonString))
  258. }
  259. func (m *ModuleHandler) GetModuleInfoByID(moduleid string) *ModuleInfo {
  260. for _, module := range m.LoadedModule {
  261. if module.Name == moduleid {
  262. return module
  263. }
  264. }
  265. return nil
  266. }
  267. func (m *ModuleHandler) GetLaunchParameter(w http.ResponseWriter, r *http.Request) {
  268. moduleName, _ := utils.GetPara(r, "module")
  269. if moduleName == "" {
  270. utils.SendErrorResponse(w, "Missing paramter 'module'.")
  271. return
  272. }
  273. //Loop through the modules and see if the module exists.
  274. var targetLaunchInfo *ModuleInfo = nil
  275. found := false
  276. for _, module := range m.LoadedModule {
  277. thisModuleName := module.Name
  278. if thisModuleName == moduleName {
  279. targetLaunchInfo = module
  280. found = true
  281. }
  282. }
  283. if found {
  284. jsonString, _ := json.Marshal(targetLaunchInfo)
  285. utils.SendJSONResponse(w, string(jsonString))
  286. return
  287. } else {
  288. utils.SendErrorResponse(w, "Given module not exists.")
  289. return
  290. }
  291. }