diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3002c722..fb00b155 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,7 +10,8 @@ ], "options": { "cwd": "${workspaceFolder}" - } + }, + "problemMatcher": [] }, { "label": "build-launchpad", diff --git a/Launchpad.ahk b/Launchpad.ahk index 00552dc8..d34cd5e1 100644 --- a/Launchpad.ahk +++ b/Launchpad.ahk @@ -13,8 +13,7 @@ appVersion := "{{VERSION}}" Launchpad(Map( "appName", "Launchpad", - "developer", "Volantis Development", "version", appVersion, - "trayIcon", "Resources\Graphics\Launchpad.ico", + "trayIcon", "Resources\Graphics\launchpad.ico", "console", true )) diff --git a/Launchpad.services.json b/Launchpad.services.json index 6a725f60..7df3dbb0 100644 --- a/Launchpad.services.json +++ b/Launchpad.services.json @@ -1,9 +1,14 @@ { "parameters": { + "app.website_url": "https://launchpad.games", + "app.custom_tray_menu": true, + "app.developer": "Volantis Development", + "app.has_settings": true, + "app.settings_window": "SettingsWindow", + "app.show_about_menu_item": true, + "app.about_window": "AboutWindow", + "app.show_website_menu_item": true, "backups_config": {}, - "config.api_authentication": false, - "config.api_auto_login": false, - "config.api_endpoint": "", "config.assets_dir": "@@{data_dir}\\Launcher Assets", "config.auto_backup_config_files": true, "config.backups_to_keep": 5, @@ -12,7 +17,6 @@ "config.clean_launchers_on_build": false, "config.clean_launchers_on_exit": true, "config.create_desktop_shortcuts": true, - "config.data_source_key": "local", "config.default_launcher_theme": "", "config.destination_dir": "@@{data_dir}\\Launchers", "config.launcher_double_click_action": "Edit", @@ -22,7 +26,6 @@ "config.platforms_file": "@@{data_dir}\\Platforms.json", "config.platforms_view_mode": "Report", "config.tasks_view_mode": "Report", - "config.player_name": "", "config.rebuild_existing_launchers": false, "config.tasks_file": "@@{data_dir}\\Tasks.json", "config.use_advanced_launcher_editor": false, @@ -33,27 +36,29 @@ "storage_config_storage_parent_key": "Games", "storage_config_path_parameter": "config.launcher_file", "manager_view_mode_parameter": "config.launcher_view_mode", - "default_icon": "Game", + "default_icon": "game", "allow_add": true, "allow_edit": true, "allow_delete": true, "manager_gui": "MainWindow" }, - "entity_type.managed_game": { + "entity_type.game_process": { "name_singular": "Managed Game", "name_plural": "Managed Games", - "entity_class": "ManagedGameEntity", + "entity_class": "GameProcessEntity", "storage_config_storage_parent_key": "Games", "storage_config_path_parameter": "config.launcher_file", - "parent_entity_type": "launcher" + "parent_entity_type": "launcher", + "parent_entity_storage": true }, - "entity_type.managed_launcher": { + "entity_type.launcher_process": { "name_singular": "Managed Launcher", "name_plural": "Managed Launchers", - "entity_class": "ManagedLauncherEntity", + "entity_class": "LauncherProcessEntity", "storage_config_storage_parent_key": "Games", "storage_config_path_parameter": "config.launcher_file", - "parent_entity_type": "launcher" + "parent_entity_type": "launcher", + "parent_entity_storage": true }, "entity_type.platform": { "name_singular": "Platform", @@ -65,8 +70,8 @@ "storage_config_storage_parent_key": "Platforms", "storage_config_path_parameter": "config.platforms_file", "manager_view_mode_parameter": "config.platforms_view_mode", - "default_icon": "Platform", - "manager_gui": "ManagePlatformsWindow", + "default_icon": "platform", + "manager_gui": "PlatformsWindow", "manager_link_in_tools_menu": true }, "entity_type.task": { @@ -112,10 +117,6 @@ "class": "LaunchpadConfig", "arguments": ["@config_storage.app_config", "@{}", "@@config_key"] }, - "data_source.local": { - "class": "LocalDataSource", - "arguments": ["@manager.cache", "local"] - }, "ini_migrator": { "class": "LaunchpadIniMigrator", "arguments": ["@{App}", "@manager.gui"] @@ -132,10 +133,6 @@ "class": "BuilderManager", "arguments": ["@entity_manager.launcher", "@{}", "@manager.event", "@notifier"] }, - "manager.data_source": { - "class": "DataSourceManager", - "arguments": ["@{}", "@manager.event", "@notifier", "@@config.data_source_key"] - }, "state.app": { "class": "LaunchpadAppState", "arguments": ["@{App}", "@@state_path"] diff --git a/LaunchpadOverlay/LaunchpadOverlay.rc b/LaunchpadOverlay/LaunchpadOverlay.rc index a2535bad..2e1c9f22 100644 --- a/LaunchpadOverlay/LaunchpadOverlay.rc +++ b/LaunchpadOverlay/LaunchpadOverlay.rc @@ -52,7 +52,7 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -IDI_ICON1 ICON "E:\\Tools\\Launchpad\\Resources\\Graphics\\Launchpad.ico" +IDI_ICON1 ICON "E:\\Tools\\Launchpad\\Resources\\Graphics\\launchpad.ico" #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/LaunchpadTest.ahk b/LaunchpadTest.ahk index 304cb70f..c9720435 100644 --- a/LaunchpadTest.ahk +++ b/LaunchpadTest.ahk @@ -13,7 +13,7 @@ appVersion := "1.0.0" -TraySetIcon("Resources\Graphics\Launchpad.ico") +TraySetIcon("Resources\Graphics\launchpad.ico") HtmlResultViewer("Launchpad Test") .ViewResults(SimpleTestRunner(FilesystemTestLoader().GetTests()).RunTests()) diff --git a/Lib/Launchpad/App/Launchpad.ahk b/Lib/Launchpad/App/Launchpad.ahk index 73eb3320..8ab4a97d 100644 --- a/Lib/Launchpad/App/Launchpad.ahk +++ b/Lib/Launchpad/App/Launchpad.ahk @@ -1,30 +1,5 @@ class Launchpad extends AppBase { - customTrayMenu := true detectGames := false - isSetup := false - - CheckForUpdates(notify := true) { - updateAvailable := false - - if (this.Version != "{{VERSION}}" && this.Service("manager.data_source").GetDefaultDataSource()) { - dataSource := this.Service("manager.data_source").GetDefaultDataSource() - releaseInfoStr := dataSource.ReadItem("release-info") - - if (releaseInfoStr) { - data := JsonData() - releaseInfo := data.FromString(&releaseInfoStr) - - if (releaseInfo && releaseInfo["data"].Has("version") && releaseInfo["data"]["version"] && this.Service("version_checker").VersionIsOutdated(releaseInfo["data"]["version"], this.Version)) { - updateAvailable := true - this.Service("manager.gui").Dialog(Map("type", "UpdateAvailableWindow"), releaseInfo) - } - } - } - - if (!updateAvailable && notify) { - this.Service("notifier").Info("You're running the latest version of Launchpad. Shiny!") - } - } UpdateIncludes() { this.RunAhkScript(this.appDir . "\Scripts\UpdateIncludes.ahk") @@ -32,7 +7,7 @@ } InitializeApp(config) { - eventMgr := this.Service("manager.event") + eventMgr := this["manager.event"] eventMgr.Register(EntityEvents.ENTITY_CREATED, "LaunchpadEntityCreated", ObjBindMethod(this, "OnEntityCreated")) eventMgr.Register(EntityEvents.ENTITY_UPDATED, "LaunchpadEntityUpdated", ObjBindMethod(this, "OnEntityUpdated")) eventMgr.Register(EntityEvents.ENTITY_DELETED, "LaunchpadEntityDeleted", ObjBindMethod(this, "OnEntityDeleted")) @@ -109,35 +84,31 @@ RunApp(config) { this.MigrateConfiguration() - - if (this.Config["api_auto_login"] && this.Services.Has("Auth")) { - this.Service("Auth").Login() - } super.RunApp(config) - this.Service("entity_manager.platform").LoadComponents() - this.Service("entity_manager.launcher").LoadComponents() - this.Service("entity_manager.backup").LoadComponents() + this["entity_manager.platform"].LoadComponents() + this["entity_manager.launcher"].LoadComponents() + this["entity_manager.backup"].LoadComponents() this.OpenApp() if (this.detectGames) { - this.Service("entity_manager.platform").DetectGames() + this["entity_manager.platform"].DetectGames() } } MigrateConfiguration() { - configFile := this.Parameter("previous_config_file") + configFile := this.Parameter["previous_config_file"] if (configFile && FileExist(configFile)) { - response := this.Service("manager.gui").Dialog(Map( + response := this["manager.gui"].Dialog(Map( "title", "Migrate settings?", "text", this.appName . " uses a new configuration file format, and has detected that you have a previous configuration file.`n`nWould you like to automatically migrate your settings?`n`nChoose Yes to migrate your previous configuration. Choose no to simply delete it and start from scratch." )) if (response == "Yes") { - this.Service("ini_migrator").Migrate(configFile, this.Config) + this["ini_migrator"].Migrate(configFile, this.Config) } else { FileDelete(configFile) } @@ -145,7 +116,7 @@ } InitialSetup(config) { - result := this.Service("manager.gui").Dialog(Map("type", "SetupWindow")) + result := this["manager.gui"].Dialog(Map("type", "SetupWindow")) if (result == "Exit") { this.ExitApp() @@ -153,45 +124,87 @@ this.detectGames := true } - this.isSetup := true super.InitialSetup(config) } UpdateStatusIndicators() { - if (this.Service("manager.gui").Has("MainWindow")) { - this.Service("manager.gui")["MainWindow"].UpdateStatusIndicator() + if (this["manager.gui"].Has("MainWindow")) { + serviceMgr := this.container["entity_manager.web_service"] + webServices := serviceMgr.EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Condition(IsTrueCondition(), "StatusIndicator") + .Execute() + + windowObj := this["manager.gui"]["MainWindow"] + + for serviceId, webService in webServices { + windowObj.UpdateStatusIndicator(webService) + } } } ExitApp() { if (this.isSetup && this.Config["clean_launchers_on_exit"]) { - this.Service("manager.builder").CleanLaunchers() + this["manager.builder"].CleanLaunchers() } if (this.isSetup && this.Config["flush_cache_on_exit"]) { - this.Service("manager.cache").FlushCaches(false, false) + this["manager.cache"].FlushCaches(false, false) } super.ExitApp() } - OpenWebsite() { - Run("https://launchpad.games") + RestartApp() { + if (this.Services.Has("manager.gui")) { + guiMgr := this["manager.gui"] + + if (guiMgr.Has("MainWindow")) { + guiMgr.StoreWindowState(this["manager.gui"]["MainWindow"]) + } + } + + super.RestartApp() } - ProvideFeedback() { - this.Service("manager.gui").Dialog(Map("type", "FeedbackWindow")) + AddMainMenuEarlyItems(menuItems, showOpenItem := false) { + menuItems := super.AddMainMenuEarlyItems(menuItems, showOpenItem) + + launchersItems := [] + launchersItems.Push(Map("label", "&Clean Launchers", "name", "CleanLaunchers")) + launchersItems.Push(Map("label", "&Reload Launchers", "name", "ReloadLaunchers")) + + menuItems.Push(Map("label", "&Launchers", "name", "LaunchersMenu", "childItems", launchersItems)) + + return menuItems } - RestartApp() { - if (this.Services.Has("manager.gui")) { - guiMgr := this.Service("manager.gui") + HandleMainMenuClick(result) { + if (result == "CleanLaunchers") { + this["manager.builder"].CleanLaunchers() + } else if (result == "ReloadLaunchers") { + this["entity_manager.launcher"].LoadComponents(true) + guiMgr := this["manager.gui"] if (guiMgr.Has("MainWindow")) { - guiMgr.StoreWindowState(this.Service("manager.gui")["MainWindow"]) + guiMgr["MainWindow"].UpdateListView() } + } else if (result == "ManageModules") { + this["manager.gui"].OpenWindow("ManageModulesWindow") + } else if (result == "FlushCache") { + this["manager.cache"].FlushCaches(true, true) + } else { + super.HandleMainMenuClick(result) } - super.RestartApp() + return result + } + + GetToolsMenuItems() { + toolsItems := super.GetToolsMenuItems() + toolsItems.Push(Map("label", "&Modules", "name", "ManageModules")) + toolsItems.Push(Map("label", "&Flush Cache", "name", "FlushCache")) + + return toolsItems } } diff --git a/Lib/Launchpad/Builder/BuildFile/CopyableBuildFile.ahk b/Lib/Launchpad/Builder/BuildFile/CopyableBuildFile.ahk index 2bd1146e..aaa77f98 100644 --- a/Lib/Launchpad/Builder/BuildFile/CopyableBuildFile.ahk +++ b/Lib/Launchpad/Builder/BuildFile/CopyableBuildFile.ahk @@ -56,7 +56,7 @@ class CopyableBuildFile extends BuildFileBase { filePath := FileSelect(1,, this.launcherEntityObj.Id . ": " . this.RequestMessage, this.SelectFilter) if (filePath == "") { - this.app.Service("notifier").Warning("No file selected. Skipping build file.") + this.app["notifier"].Warning("No file selected. Skipping build file.") } return filePath diff --git a/Lib/Launchpad/Builder/BuildFile/GameAhkFile.ahk b/Lib/Launchpad/Builder/BuildFile/GameAhkFile.ahk index ed0eaedc..d1cbe8cd 100644 --- a/Lib/Launchpad/Builder/BuildFile/GameAhkFile.ahk +++ b/Lib/Launchpad/Builder/BuildFile/GameAhkFile.ahk @@ -14,9 +14,9 @@ class GameAhkFile extends ComposableBuildFile { "launcherName", this.launcherEntityObj.Id . " - Launchpad", "appVersion", appVersion, "appDir", this.appDir, - "gameConfig", this.launcherEntityObj["ManagedGame"].FieldData, + "gameConfig", this.launcherEntityObj["GameProcess"].FieldData, "launchpadLauncherConfig", this.launcherEntityObj.FieldData, - "launcherConfig", this.launcherEntityObj["ManagedLauncher"].FieldData, + "launcherConfig", this.launcherEntityObj["LauncherProcess"].FieldData, "launcherId", this.launcherEntityObj.Id, "themesDir", this.launcherEntityObj["ThemesDir"], "resourcesDir", this.launcherEntityObj["ResourcesDir"], @@ -35,7 +35,7 @@ class GameAhkFile extends ComposableBuildFile { GetPlatforms() { platforms := Map() - for key, platform in this.app.Service("entity_manager.platform").GetActivePlatforms(EntityQuery.RESULT_TYPE_ENTITIES) { + for key, platform in this.app["entity_manager.platform"].GetActivePlatforms(EntityQuery.RESULT_TYPE_ENTITIES) { platforms[key] := platform.FieldData } diff --git a/Lib/Launchpad/Builder/BuildFile/IconFile.ahk b/Lib/Launchpad/Builder/BuildFile/IconFile.ahk index 333333a4..729d4c01 100644 --- a/Lib/Launchpad/Builder/BuildFile/IconFile.ahk +++ b/Lib/Launchpad/Builder/BuildFile/IconFile.ahk @@ -53,7 +53,7 @@ class IconFile extends CopyableBuildFile { } if (iconsCount == 0) { - this.app.Service("notifier").Warning("No icons could be extracted from %exeFile%. Please try another file.") + this.app["notifier"].Warning("No icons could be extracted from %exeFile%. Please try another file.") iconFilePath := "" this.Cleanup() } else { @@ -61,7 +61,7 @@ class IconFile extends CopyableBuildFile { iconFilePath := FileSelect(, iconsDir, "Select the correct icon from the extracted files", "Icons (*.ico)") if (iconFilePath == "") { - this.app.Service("notifier").Warning("Canceled icon selection. Please try again.") + this.app["notifier"].Warning("Canceled icon selection. Please try again.") this.Cleanup() } } diff --git a/Lib/Launchpad/Builder/BuildFile/ShortcutFile.ahk b/Lib/Launchpad/Builder/BuildFile/ShortcutFile.ahk index 6f61ab18..d434a996 100644 --- a/Lib/Launchpad/Builder/BuildFile/ShortcutFile.ahk +++ b/Lib/Launchpad/Builder/BuildFile/ShortcutFile.ahk @@ -6,14 +6,14 @@ class ShortcutFile extends CopyableBuildFile { if (destPath == "") { ext := ".lnk" - if (launcherEntityObj["ManagedGame"]["ShortcutSrc"] != "" && SubStr(launcherEntityObj["ManagedGame"]["ShortcutSrc"], -4) == ".url") { + if (launcherEntityObj["GameProcess"]["ShortcutSrc"] != "" && SubStr(launcherEntityObj["GameProcess"]["ShortcutSrc"], -4) == ".url") { ext := ".url" } destPath := launcherEntityObj["AssetsDir"] . "\" . launcherEntityObj.Id . ext } - super.__New(launcherEntityObj, launcherEntityObj["ManagedGame"]["ShortcutSrc"], destPath) + super.__New(launcherEntityObj, launcherEntityObj["GameProcess"]["ShortcutSrc"], destPath) } Locate() { diff --git a/Lib/Launchpad/Builder/BuilderBase.ahk b/Lib/Launchpad/Builder/BuilderBase.ahk index b9609894..d4f77787 100644 --- a/Lib/Launchpad/Builder/BuilderBase.ahk +++ b/Lib/Launchpad/Builder/BuilderBase.ahk @@ -32,7 +32,7 @@ class BuilderBase { } NeedsShortcutFile(launcherEntityObj) { - return (launcherEntityObj["ManagedGame"]["UsesShortcut"]) + return (launcherEntityObj["GameProcess"]["UsesShortcut"]) } BuildAction(launcherEntityObj, launcherDir, assetsDir) { diff --git a/Lib/Launchpad/BulkOperation/LauncherBuilderOp/LauncherBuilderOpBase.ahk b/Lib/Launchpad/BulkOperation/LauncherBuilderOp/LauncherBuilderOpBase.ahk index f1194602..f9f976b3 100644 --- a/Lib/Launchpad/BulkOperation/LauncherBuilderOp/LauncherBuilderOpBase.ahk +++ b/Lib/Launchpad/BulkOperation/LauncherBuilderOp/LauncherBuilderOpBase.ahk @@ -11,7 +11,7 @@ class LauncherBuilderOpBase extends LauncherGameOpBase { } if (Type(builder) == "String" && builder != "") { - builder := app.Service("manager.builder")[builder] + builder := app["manager.builder"][builder] } InvalidParameterException.CheckTypes("LauncherBuilderOpBase", "builder", builder, "BuilderBase") diff --git a/Lib/Launchpad/BulkOperation/LauncherGameOp/LauncherGameOpBase.ahk b/Lib/Launchpad/BulkOperation/LauncherGameOp/LauncherGameOpBase.ahk index 558ee665..f9193b3d 100644 --- a/Lib/Launchpad/BulkOperation/LauncherGameOp/LauncherGameOpBase.ahk +++ b/Lib/Launchpad/BulkOperation/LauncherGameOp/LauncherGameOpBase.ahk @@ -14,7 +14,7 @@ class LauncherGameOpBase extends BulkOperationBase { itemFailedText := "Failed." __New(app, launcherEntities := "", owner := "") { - this.launcherManager := app.Service("entity_manager.launcher") + this.launcherManager := app["entity_manager.launcher"] if (launcherEntities == "") { launcherEntities := this.launcherManager.All() @@ -63,12 +63,12 @@ class LauncherGameOpBase extends BulkOperationBase { VerifyRequirements() { if (this.app.Config["destination_dir"] == "") { - this.app.Service("notifier").Error("Launcher directory is not set.") + this.app["notifier"].Error("Launcher directory is not set.") return false } if (this.app.Config["assets_dir"] == "") { - this.app.Service("notifier").Error("Assets directory is not set.") + this.app["notifier"].Error("Assets directory is not set.") return false } diff --git a/Lib/Launchpad/BulkOperation/LoadOp/LoadEntitiesOp.ahk b/Lib/Launchpad/BulkOperation/LoadOp/LoadEntitiesOp.ahk index fab21b04..c51002ac 100644 --- a/Lib/Launchpad/BulkOperation/LoadOp/LoadEntitiesOp.ahk +++ b/Lib/Launchpad/BulkOperation/LoadOp/LoadEntitiesOp.ahk @@ -10,7 +10,7 @@ class LoadEntitiesOp extends BulkOperationBase { failedMessage := "{n} launcher(s) could not be loaded due to errors." __New(app, launcherConfigObj := "", owner := "") { - this.launcherManager := app.Service("entity_manager.launcher") + this.launcherManager := app["entity_manager.launcher"] if (launcherConfigObj == "") { launcherConfigObj := this.launcherManager.GetConfig() @@ -29,7 +29,7 @@ class LoadEntitiesOp extends BulkOperationBase { } ; @todo replace this since EntityFactory is no longer used - factory := this.app.Service("EntityFactory") + factory := this.app["EntityFactory"] for key, config in this.launcherConfigObj { this.StartItem(key, key) diff --git a/Lib/Launchpad/DataSource/LocalDataSource.ahk b/Lib/Launchpad/DataSource/LocalDataSource.ahk deleted file mode 100644 index 7ac01421..00000000 --- a/Lib/Launchpad/DataSource/LocalDataSource.ahk +++ /dev/null @@ -1,2 +0,0 @@ -class LocalDataSource extends DataSourceBase { -} diff --git a/Lib/Launchpad/DetectedGame/DetectedGame.ahk b/Lib/Launchpad/DetectedGame/DetectedGame.ahk index 02851d7c..e92e87a7 100644 --- a/Lib/Launchpad/DetectedGame/DetectedGame.ahk +++ b/Lib/Launchpad/DetectedGame/DetectedGame.ahk @@ -8,7 +8,7 @@ class DetectedGame { installDir := "" launcherInstallDir := "" exeName := "" - launcherSpecificId := "" + platformRef := "" possibleExeNames := [] keyMap := Map() ; @todo Move this to properties or config or allow it to be extended @@ -17,16 +17,16 @@ class DetectedGame { prioritySuffixes := ["-Win64-Shipping", "-Win32-Shipping"] filterExes := [] - __New(key, platform, launcherType, gameType := "Default", installDir := "", exeName := "", launcherSpecificId := "", possibleExeNames := "") { + __New(key, platform, launcherType, gameType := "Default", installDir := "", exeName := "", platformRef := "", possibleExeNames := "", displayName := "") { this.key := key - this.displayName := key + this.displayName := displayName ? displayName : key this.platform := platform this.detectedKey := key this.launcherType := launcherType this.gameType := gameType this.installDir := installDir this.exeName := exeName - this.launcherSpecificId := launcherSpecificId + this.platformRef := platformRef if (possibleExeNames) { if (Type(possibleExeNames) == "String") { @@ -44,12 +44,10 @@ class DetectedGame { if ( this.displayName != launcher["name"] - || this.launcherType != launcher["ManagedLauncher"].EntityTypeId - || this.gameType != launcher["ManagedGame"].EntityTypeId - || this.installDir != launcher["ManagedGame"]["InstallDir"] - || this.launcherInstallDir != launcher["ManagedLauncher"]["InstallDir"] - || this.exeName != launcher["ManagedGame"]["Exe"] - || this.launcherSpecificId != launcher["ManagedGame"]["LauncherSpecificId"] + || this.installDir != launcher["GameProcess"]["InstallDir"] + || this.launcherInstallDir != launcher["LauncherProcess"]["InstallDir"] + || this.exeName != launcher["GameProcess"]["Exe"] + || this.platformRef != launcher["GameProcess"]["PlatformRef"] ) { hasChanges := true } @@ -70,40 +68,28 @@ class DetectedGame { modified := true } - if (this.launcherType && launcher["ManagedLauncher"].EntityTypeId != this.launcherType) { - launcher["ManagedLauncher"].EntityType := this.launcherType - modified := true + if (this.launcherInstallDir && launcher["LauncherProcess"]["InstallDir"] != this.launcherInstallDir) { + launcher["LauncherProcess"]["InstallDir"] := this.launcherInstallDir } - if (this.gameType && launcher["ManagedGame"].EntityTypeId != this.gameType) { - launcher["ManagedGame"].EntityType := this.gameType + if (this.installDir && launcher["GameProcess"]["InstallDir"] != this.installDir) { + launcher["GameProcess"]["InstallDir"] := this.installDir modified := true } - if (this.launcherInstallDir && launcher["ManagedLauncher"]["InstallDir"] != this.launcherInstallDir) { - launcher["ManagedLauncher"]["InstallDir"] := this.launcherInstallDir - } - - if (this.installDir && launcher["ManagedGame"]["InstallDir"] != this.installDir) { - launcher["ManagedGame"]["InstallDir"] := this.installDir - modified := true - } - - if (this.exeName && launcher["ManagedGame"]["Exe"] != this.exeName) { - launcher["ManagedGame"]["Exe"] := this.exeName + if (this.exeName && launcher["GameProcess"]["Exe"] != this.exeName) { + launcher["GameProcess"]["Exe"] := this.exeName modified := true } if (modified) { - launcher.SaveModifiedData() + launcher.SaveEntity(true) } } CreateLauncher(launcherManager) { config := Map( - "Platform", this.platform.key, - "LauncherType", this.launcherType, - "GameType", this.gameType + "Platform", this.platform.key ) if (this.displayName && this.displayName != this.key) { @@ -122,8 +108,8 @@ class DetectedGame { config["GameExe"] := this.exeName } - if (this.launcherSpecificId) { - config["GameLauncherSpecificId"] := this.launcherSpecificId + if (this.platformRef) { + config["GamePlatformRef"] := this.platformRef } entityObj := launcherManager.GetFactory().CreateEntity(this.key, config) @@ -153,15 +139,19 @@ class DetectedGame { } } - key := StrReplace(key, ": ", " - ") - key := StrReplace(key, ":", "") - key := StrReplace(key, "\", "") - key := StrReplace(key, "/", "") - key := StrReplace(key, "*", "") - key := StrReplace(key, "?", "") - key := StrReplace(key, "`"", "") - key := StrReplace(key, "®", "") - key := StrReplace(key, "â„¢", "") + replacements := [ + [" : ", " - "], + [": ", " - "], + [":", "-"], + ["®", ""], + ["â„¢", ""] + ] + + for , vals in replacements { + key := StrReplace(key, vals[1], vals[2]) + } + + key := RegExReplace(key, "[\\/:*?`"<>|]'") return key } diff --git a/Lib/Launchpad/Entity/ManagedGameEntity.ahk b/Lib/Launchpad/Entity/GameProcessEntity.ahk similarity index 70% rename from Lib/Launchpad/Entity/ManagedGameEntity.ahk rename to Lib/Launchpad/Entity/GameProcessEntity.ahk index 907991e6..da93710b 100644 --- a/Lib/Launchpad/Entity/ManagedGameEntity.ahk +++ b/Lib/Launchpad/Entity/GameProcessEntity.ahk @@ -1,8 +1,5 @@ -class ManagedGameEntity extends ManagedEntityBase { - configPrefix := "Game" - defaultType := "Default" +class GameProcessEntity extends LaunchProcessEntity { defaultClass := "SimpleGame" - dataSourcePath := "game-types" BaseFieldDefinitions() { definitions := super.BaseFieldDefinitions() @@ -10,7 +7,7 @@ class ManagedGameEntity extends ManagedEntityBase { definitions["HasLoadingWindow"] := Map( "type", "boolean", "description", "Whether or not the game has a loading window to watch for.", - "storageKey", this.configPrefix . "HasLoadingWindow", + "storageKey", "HasLoadingWindow", "default", false ) @@ -20,7 +17,7 @@ class ManagedGameEntity extends ManagedEntityBase { definitions["LoadingWindowProcessType"] := Map( "description", "Which method to use to wait for the game's loading window to open.", "help", "This lets Launchpad know when the game is loading. Only used if a LoadingWindowProcessId is set", - "storageKey", this.configPrefix . "LoadingWindowProcessType", + "storageKey", "LoadingWindowProcessType", "default", "Exe", "widget", "select", "selectOptionsCallback", ObjBindMethod(this, "ListProcessTypes") @@ -30,7 +27,7 @@ class ManagedGameEntity extends ManagedEntityBase { ; - Title - This value will default to the game's Key unless overridden ; - Class - This value should be set to the game's window class definitions["LoadingWindowProcessId"] := Map( - "storageKey", this.configPrefix . "LoadingWindowProcessId", + "storageKey", "LoadingWindowProcessId", "help", "This value's type is dependent on the GameProcessType above. It can often be detected from other values, and is not needed if the GameRunType is RunWait.", "modes", Map( "simple", Map("formField", false) @@ -40,16 +37,6 @@ class ManagedGameEntity extends ManagedEntityBase { return definitions } - GetBlizzardProductKey() { - productKey := this["LauncherSpecificId"] - - if (this.HasConfigValue("BlizzardProductId", true, false)) { - productKey := this.GetConfigValue("BlizzardProductId") - } - - return productKey - } - ShouldDetectShortcutSrc(extraConfig) { detectShortcut := false @@ -78,28 +65,28 @@ class ManagedGameEntity extends ManagedEntityBase { return detectShortcut } - AutoDetectValues(recurse := true) { - detectedValues := super.AutoDetectValues(recurse) - exeKey := this.configPrefix . "Exe" + AutoDetectValues() { + detectedValues := super.AutoDetectValues() + exeKey := "Exe" if (!detectedValues.Has(exeKey)) { detectedValues[exeKey] := this["Exe"] ? this["Exe"] : this.Id . ".exe" } - if (!detectedValues.Has(this.configPrefix . "ProcessId") || !detectedValues[this.configPrefix . "ProcessId"]) { - detectedValues[this.configPrefix . "ProcessId"] := detectedValues[exeKey] + if (!detectedValues.Has("ProcessId") || !detectedValues["ProcessId"]) { + detectedValues["ProcessId"] := detectedValues[exeKey] } - if (detectedValues.Has(this.configPrefix . "ProcessType")) { - detectedValues[this.configPrefix . "LoadingWindowProcessType"] := detectedValues[this.configPrefix . "ProcessType"] + if (detectedValues.Has("ProcessType")) { + detectedValues["LoadingWindowProcessType"] := detectedValues["ProcessType"] } if (!this["LoadingWindowProcessId"]) { - detectedValues[this.configPrefix . "LoadingWindowProcessId"] := detectedValues[exeKey] + detectedValues["LoadingWindowProcessId"] := detectedValues[exeKey] } if (this.ShouldDetectShortcutSrc(detectedValues)) { - basePath := this["AssetsDir"] . "\" . this.Id + basePath := this.ParentEntity["AssetsDir"] . "\" . this.Id shortcutSrc := "" if (FileExist(basePath . ".lnk")) { @@ -111,7 +98,7 @@ class ManagedGameEntity extends ManagedEntityBase { } if (shortcutSrc != "") { - detectedValues[this.configPrefix . "ShortcutSrc"] := shortcutSrc + detectedValues["ShortcutSrc"] := shortcutSrc } } diff --git a/Lib/Launchpad/Entity/ManagedEntityBase.ahk b/Lib/Launchpad/Entity/LaunchProcessEntity.ahk similarity index 76% rename from Lib/Launchpad/Entity/ManagedEntityBase.ahk rename to Lib/Launchpad/Entity/LaunchProcessEntity.ahk index 976c6489..a102f336 100644 --- a/Lib/Launchpad/Entity/ManagedEntityBase.ahk +++ b/Lib/Launchpad/Entity/LaunchProcessEntity.ahk @@ -1,9 +1,10 @@ -class ManagedEntityBase extends AppEntityBase { - defaultType := "Default" +class LaunchProcessEntity extends FieldableEntity { defaultClass := "Default" - DiscoverParentEntity(container, eventMgr, id, storageObj, idSanitizer) { - return container.Get("entity_manager.launcher")[id] + DiscoverParentEntity(container, eventMgr, id, storageObj, idSanitizer, parentEntity := "") { + this.parentEntityObj := parentEntity + ? parentEntity + : container.Get("entity_manager.launcher")[id] } GetDefaultFieldGroups() { @@ -48,21 +49,10 @@ class ManagedEntityBase extends AppEntityBase { ) ) - definitions["EntityType"] := Map( - "default", this.defaultType, - "description", "The key of the managed type to load settings and defaults from.", - "required", true, - "storageKey", this.configPrefix . "Type", - "widget", "select", - "selectOptionsCallback", ObjBindMethod(this, "ListEntityTypes"), - "group", "general" - ) - - definitions["EntityClass"] := Map( + definitions["ProcessClass"] := Map( "default", this.defaultClass, - "description", "The name of the AHK class that will be used to control the managed entity.", + "description", "The name of the AHK class that will be used to control the process.", "formField", false, - "storageKey", this.configPrefix . "Class", "required", true, "group", "advanced", "modes", Map( @@ -73,11 +63,10 @@ class ManagedEntityBase extends AppEntityBase { definitions["SearchDirs"] := Map( "type", "directory", "mustExist", false, - "storageKey", this.configPrefix . "SearchDirs", "default", [A_ProgramFiles], "description", "Possible parent directories where the game's launcher might exist, to be used for auto-detection.", "help", "These should be as specific as possible to reduce detection time.", - "multiple", true, + "cardinality", 1, ; Change to another number once widgets for multiple values are worked out "group", "locations", "modes", Map( "simple", Map("formField", false) @@ -87,7 +76,6 @@ class ManagedEntityBase extends AppEntityBase { definitions["InstallDir"] := Map( "type", "directory", "mustExist", false, - "storageKey", this.configPrefix . "InstallDir", "group", "locations", "modes", Map( "simple", Map("group", "general") @@ -99,7 +87,6 @@ class ManagedEntityBase extends AppEntityBase { "type", "file", "fileMask", "*.exe", "mustExist", false, - "storageKey", this.configPrefix . "Exe", "description", "This can be the full path on the system to the launcher's .exe file, or simply the name of the .exe file itself.", "help", "If the .exe doesn't include the absolute path, auto-detection will be used by searching the DestinationDirs.", "group", "locations", @@ -113,7 +100,6 @@ class ManagedEntityBase extends AppEntityBase { ; - BlizzardProductDb (will search Battle.net's product.db file if it can be located for the installation directory, and the file will be found from there ; - Registry (will get a directory from the registry key specified by LocateRegKey and search for the file within it) definitions["LocateMethod"] := Map( - "storageKey", this.configPrefix . "LocateMethod", "default", "SearchDirs", "description", "How to search for the .exe if it isn't a full path already", "group", "general", @@ -122,16 +108,14 @@ class ManagedEntityBase extends AppEntityBase { ), "widget", "select", "selectOptionsCallback", ObjBindMethod(this, "ListLocateMethods"), - "help", "Search: Searches a list of possible directories (Defaulting to some common possibilities) for the .exe file and uses that directory`nRegistry: Looks for the provided registry key and uses its value as the install path if present`nBlizzardProductDb: Searches for LauncherSpecificId within the Blizzard product.db file if present" + "help", "Search: Searches a list of possible directories (Defaulting to some common possibilities) for the .exe file and uses that directory`nRegistry: Looks for the provided registry key and uses its value as the install path if present`nBlizzardProductDb: Searches for PlatformRef within the Blizzard product.db file if present" ) definitions["WindowTitle"] := Map( - "storageKey", this.configPrefix . "WindowTitle", "group", "process" ) definitions["LocateRegView"] := Map( - "storageKey", this.configPrefix . "LocateRegView", "default", 64, "group", "registry", "widget", "select", @@ -143,7 +127,7 @@ class ManagedEntityBase extends AppEntityBase { ) definitions["LocateRegKey"] := Map( - "storageKey", this.configPrefix . "LocateRegKey", + "title", "Registry Locator - Key", "group", "registry", "description", "The registry key to look up the install dir within.", "help", "Path parts should be separated with backslashes and must start with one of: HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_USER, HKEY_CLASSES_ROOT, HKEY_CURRENT_CONFIG, or the abbreviation of one of those. To read from a remote registry, prefix the root path with two backslashes and the computer name.`n`nSimple example: HKLM\Path\To\Key`nRemote example: \\OTHERPC\HKLM\Path\To\Key", @@ -153,7 +137,7 @@ class ManagedEntityBase extends AppEntityBase { ) definitions["LocateRegValue"] := Map( - "storageKey", this.configPrefix . "LocateRegValue", + "title", "Registry Locator - Value", "group", "registry", "description", "The name of the registry value to look up within the specified key.", "help", "Example: InstallPath", @@ -163,7 +147,7 @@ class ManagedEntityBase extends AppEntityBase { ) definitions["LocateRegRemovePrefix"] := Map( - "storageKey", this.configPrefix . "LocateRegRemovePrefix", + "title", "Registry Locator - Remove Prefix", "group", "registry", "modes", Map( "simple", Map("formField", false) @@ -171,7 +155,7 @@ class ManagedEntityBase extends AppEntityBase { ) definitions["LocateRegRemoveSuffix"] := Map( - "storageKey", this.configPrefix . "LocateRegRemoveSuffix", + "title", "Registry Locator - Remove Suffix", "group", "registry", "modes", Map( "simple", Map("formField", false) @@ -179,7 +163,7 @@ class ManagedEntityBase extends AppEntityBase { ) definitions["LocateRegStripQuotes"] := Map( - "storageKey", this.configPrefix . "LocateRegStripQuotes", + "title", "Registry Locator - Strip Quotes", "default", false, "group", "registry", "description", "Strip quotes from registry value", @@ -188,17 +172,17 @@ class ManagedEntityBase extends AppEntityBase { ) ) - definitions["LauncherSpecificId"] := Map( - "storageKey", this.configPrefix . "LauncherSpecificId", + definitions["PlatformRef"] := Map( + "title", "Platform Reference", "description", "If the item is known to the launcher by a specific ID, it should be stored here.", "group", "general" ) definitions["WorkingDir"] := Map( + "title", "Working Directory", "type", "directory", "description", "The directory that the launcher should be run from.", "help", "If not set, it will be run without setting an explicit working directory, which is usually sufficient.", - "storageKey", this.configPrefix . "WorkingDir", "group", "locations", "modes", Map( "simple", Map("formField", false) @@ -210,7 +194,6 @@ class ManagedEntityBase extends AppEntityBase { definitions["RunType"] := Map( "description", "Which method to use for launching this item.", "help", "This is only needed for launchers that have to manage their own process.", - "storageKey", this.configPrefix . "RunType", "default", "Command", "group", "process", "widget", "select", @@ -220,15 +203,13 @@ class ManagedEntityBase extends AppEntityBase { definitions["UsesShortcut"] := Map( "type", "boolean", "description", "Whether a shortcut file will be used when starting the internally-managed game launcher", - "formField", false, - "storageKey", this.configPrefix . "UsesShortcut" + "formField", false ) definitions["ReplaceProcess"] := Map( "type", "boolean", "description", "Kill and re-launch the game process immediately after it is detected.", "help", "This can be used to force Launchpad to own the game process, but won't for for every game.", - "storageKey", this.configPrefix . "ReplaceProcess", "default", false, "group", "process" ) @@ -238,9 +219,9 @@ class ManagedEntityBase extends AppEntityBase { ; - The path of an .exe file on the system to which a shortcut will be created in AssetsDir if it doesn't already exist. Using this option ; is usually not necessary, since you can run the .exe directly instead. definitions["ShortcutSrc"] := Map( + "title", "Shortcut Source", "description", "The shortcut file used to launch the game launcher itself.", "help", "This is typically only needed if the Shortcut LauncherRunType is selected.", - "storageKey", this.configPrefix . "ShortcutSrc", "group", "locations", "modes", Map( "simple", Map("group", "general") @@ -252,7 +233,6 @@ class ManagedEntityBase extends AppEntityBase { ; - Scheduled (Creates an immediate scheduled task that runs the game, then waits until the window opens (if needed) and then closes) definitions["RunMethod"] := Map( "description", "Which method to use to run the RunCmd", - "storageKey", this.configPrefix . "RunMethod", "default", "Run", "group", "process", "widget", "select", @@ -265,7 +245,6 @@ class ManagedEntityBase extends AppEntityBase { definitions["ProcessType"] := Map( "description", "Which method to use to wait for the game to close.", "help", "This is not needed if the GameRunType is RunWait", - "storageKey", this.configPrefix . "ProcessType", "default", "Exe", "group", "process", "widget", "select", @@ -277,7 +256,6 @@ class ManagedEntityBase extends AppEntityBase { ; - Class - This value should be set to the game's window class definitions["ProcessId"] := Map( "help", "This value's type is dependent on the ProcessType above. It can often be detected from other values, and is not needed if the GameRunType is RunWait.", - "storageKey", this.configPrefix . "ProcessId", "group", "process", "modes", Map( "simple", Map("formField", false) @@ -286,7 +264,6 @@ class ManagedEntityBase extends AppEntityBase { definitions["ProcessTimeout"] := Map( "description", "The number of seconds to wait before giving up when waiting for a process.", - "storageKey", this.configPrefix . "ProcessTimeout", "default", 30, "group", "process", "modes", Map( @@ -295,45 +272,26 @@ class ManagedEntityBase extends AppEntityBase { ) definitions["RunCmd"] := Map( + "title", "Run Command", "description", "The command that will be used to run the game's launcher.", "help", "Typically only used if LauncherRunType is Command.", - "storageKey", this.configPrefix . "RunCmd", "group", "process" ) return definitions } - GetData() { - if (!this.ParentEntity) { - throw EntityException("A parent entity is required on type " . Type(this)) - } - - return this.ParentEntity.GetData() - } - - _createEntityData() { - return "" - } - - DiscoverDataSourceItemKey() { - return this["EntityType"] - } - - AutoDetectValues(recurse := true) { - detectedValues := super.AutoDetectValues(recurse) + AutoDetectValues() { + detectedValues := super.AutoDetectValues() processId := "" - usesShortcut := false - if (this.GetData().HasValue(this.configPrefix . "UsesShortcut")) { - usesShortcut := this.GetData().GetValue(this.configPrefix . "UsesShortcut") - } else { - usesShortcut := (this["RunType"] == "Shortcut" || this["ShortcutSrc"] != "" || this["RunCmd"] == "") - } + usesShortcut := (this.GetData().HasValue("UsesShortcut")) + ? this.GetData().GetValue("UsesShortcut") + : (this["RunType"] == "Shortcut" || this["ShortcutSrc"] != "" || this["RunCmd"] == "") - detectedValues[this.configPrefix . "UsesShortcut"] := usesShortcut - detectedValues[this.configPrefix . "RunType"] := usesShortcut ? "Shortcut" : "Command" - detectedValues[this.configPrefix . "InstallDir"] := this.LocateInstallDir() ; This needs to run to expand exes without a dir + detectedValues["UsesShortcut"] := usesShortcut + detectedValues["RunType"] := usesShortcut ? "Shortcut" : "Command" + detectedValues["InstallDir"] := this.LocateInstallDir() ; This needs to run to expand exes without a dir if (this["ProcessType"] == "Exe") { SplitPath(this["Exe"], &processId) @@ -341,37 +299,12 @@ class ManagedEntityBase extends AppEntityBase { processId := this["WindowTitle"] ? this["WindowTitle"] : this.Id } - detectedValues[this.configPrefix . "ProcessId"] := processId - detectedValues[this.configPrefix . "WorkingDir"] := this["InstallDir"] + detectedValues["ProcessId"] := processId + detectedValues["WorkingDir"] := this["InstallDir"] return detectedValues } - ListEntityTypes() { - types := [] - dataSources := this.GetAllDataSources() - dsPath := this.GetDataSourceItemPath() - - for index, dataSource in dataSources { - for listingIndex, listingItem in dataSource.ReadListing(dsPath) { - exists := false - - for index, item in types { - if (item == listingItem) { - exists := true - break - } - } - - if (!exists) { - types.Push(listingItem) - } - } - } - - return types - } - ListRunTypes() { return [ "Command", @@ -381,19 +314,25 @@ class ManagedEntityBase extends AppEntityBase { ListProcessTypes() { return [ - "Exe", "Title", "Class" + "Exe", + "Title", + "Class" ] } ListRunMethods() { return [ - "Run", "Scheduled", "RunWait" + "Run", + "Scheduled", + "RunWait" ] } ListLocateMethods() { return [ - "Search", "Registry", "BlizzardProductDb" + "Search", + "Registry", + "BlizzardProductDb" ; TODO Move this to the Blizzard module ] } @@ -422,18 +361,20 @@ class ManagedEntityBase extends AppEntityBase { validateResult["invalidFields"].push("RunCmd") } - ; TODO: Perform more launcher and game type validation here + ; TODO: Perform more validation here return validateResult } ShortcutFileExists() { - shortcutSrc := this["ShortcutSrc"] != "" ? this["ShortcutSrc"] : this.GetAssetPath(this.Id . ".lnk") + shortcutSrc := (this["ShortcutSrc"] != "") + ? this["ShortcutSrc"] + : this["AssetsDir"] . "\" . this.Id . ".lnk" exists := FileExist(shortcutSrc) if (!exists) { - shortcutSrc := this.GetAssetPath(this.Id . ".url") + shortcutSrc := this["AssetsDir"] . "\" . this.Id . ".url" exists := FileExist(shortcutSrc) } @@ -443,8 +384,7 @@ class ManagedEntityBase extends AppEntityBase { LocateInstallDir() { installDir := "" - ; TODO: Add additional methods to detect the install dir - + ; TODO Move BlizzardProductDb method to an event handled by the Blizzard module if (this["LocateMethod"] == "BlizzardProductDb") { blizzardDir := this.GetBlizzardProductDir() @@ -453,6 +393,8 @@ class ManagedEntityBase extends AppEntityBase { } } + ; TODO: Add additional methods to detect the install dir + return installDir } @@ -504,6 +446,7 @@ class ManagedEntityBase extends AppEntityBase { } } } else if (this["LocateMethod"] == "BlizzardProductDb") { + ; TODO Move BlizzardProductDb method to an event handled by the Blizzard module blizzardDir := this.GetBlizzardProductDir() if (blizzardDir != "") { @@ -543,18 +486,14 @@ class ManagedEntityBase extends AppEntityBase { return path } - GetBlizzardProductKey() { - return "bna" ; Default to the Battle.net client itself - } - + ; TODO Move this method to the Blizzard module and call it from an eevent GetBlizzardProductDir() { - path := "" - productCode := this.GetBlizzardProductKey() - - if (productCode != "" && this.app.Services.Has("BlizzardProductDb")) { - path := this.app.Service("BlizzardProductDb").GetProductInstallPath(productCode) - } + productCode (HasBase(this, GameProcessEntity.Prototype)) + ? productCode := this["PlatformRef"] + : "bna" ; Default to the Battle.net client itself - return path + return (productCode != "" && this.app.Services.Has("BlizzardProductDb")) + ? this.app["BlizzardProductDb"].GetProductInstallPath(productCode) + : "" } } diff --git a/Lib/Launchpad/Entity/LauncherEntity.ahk b/Lib/Launchpad/Entity/LauncherEntity.ahk index edaec377..b5d4c554 100644 --- a/Lib/Launchpad/Entity/LauncherEntity.ahk +++ b/Lib/Launchpad/Entity/LauncherEntity.ahk @@ -1,7 +1,5 @@ -class LauncherEntity extends AppEntityBase { - dataSourcePath := "games" - configPrefix := "Launcher" - additionalManagedLauncherDefaults := Map() +class LauncherEntity extends FieldableEntity { + additionalLauncherProcessDefaults := Map() IsBuilt { get => this.LauncherExists(false) @@ -29,6 +27,11 @@ class LauncherEntity extends AppEntityBase { "weight", 80 ) + groups["advanced"] := Map( + "name", "Advanced", + "weight", 100 + ) + return groups } @@ -47,18 +50,12 @@ class LauncherEntity extends AppEntityBase { definitions["id"]["modes"]["wizard"]["formField"] := true definitions["id"]["modes"]["wizard"]["widget"] := "combo" - definitions["id"]["modes"]["wizard"]["selectOptionsCallback"] := ObjBindMethod(this, "ListKnownGames") + definitions["id"]["modes"]["wizard"]["selectOptionsCallback"] := ObjBindMethod(this, "ListEntities", false, true) definitions["id"]["modes"]["wizard"]["description"] := "Select an existing game from the API, or enter a custom game key to create your own." definitions["name"]["description"] := "You can change the display name of the game if it differs from the key." definitions["name"]["help"] := "The launcher filename will still be created using the key." - if (definitions.Has("DataSourceItemKey")) { - definitions["DataSourceItemKey"]["default"] := "" - definitions["DataSourceItemKey"]["description"] := "The key to use when looking this item up in its data source(s)." - definitions["DataSourceItemKey"]["help"] := "By default, this is the same as the main key." - } - definitions["Platform"] := Map( "type", "entity_reference", "entityType", "platform", @@ -68,10 +65,10 @@ class LauncherEntity extends AppEntityBase { "showDefaultCheckbox", false ) - definitions["ManagedLauncher"] := Map( + definitions["LauncherProcess"] := Map( "type", "entity_reference", "required", true, - "entityType", "managed_launcher", + "entityType", "launcher_process", "child", true, "weight", -25, "widget", "entity_select", @@ -87,13 +84,13 @@ class LauncherEntity extends AppEntityBase { ), "default", this.idVal, "showDefaultCheckbox", false, - "valueType", EntityFieldBase.VALUE_TYPE_DEFAULT + "storeEntityData", true ) - definitions["ManagedGame"] := Map( + definitions["GameProcess"] := Map( "type", "entity_reference", "required", true, - "entityType", "managed_game", + "entityType", "game_process", "child", true, "weight", -20, "widget", "entity_select", @@ -109,7 +106,7 @@ class LauncherEntity extends AppEntityBase { ), "default", this.idVal, "showDefaultCheckbox", false, - "valueType", EntityFieldBase.VALUE_TYPE_DEFAULT + "storeEntityData", true ) definitions["DestinationDir"] := Map( @@ -189,7 +186,7 @@ class LauncherEntity extends AppEntityBase { definitions["RunBefore"] := Map( "type", "entity_reference", "entityType", "task", - "multiple", true, + "cardinality", 1, ; Change to another number once widgets for multiple values are worked out "group", "tasks", "modes", Map( "simple", Map("formField", false) @@ -201,7 +198,7 @@ class LauncherEntity extends AppEntityBase { definitions["CloseBefore"] := Map( "type", "entity_reference", "entityType", "task", - "multiple", true, + "cardinality", 1, ; Change to another number once widgets for multiple values are worked out "group", "tasks", "modes", Map( "simple", Map("formField", false) @@ -213,7 +210,7 @@ class LauncherEntity extends AppEntityBase { definitions["RunAfter"] := Map( "type", "entity_reference", "entityType", "task", - "multiple", true, + "cardinality", 1, ; Change to another number once widgets for multiple values are worked out "group", "tasks", "modes", Map( "simple", Map("formField", false) @@ -225,7 +222,7 @@ class LauncherEntity extends AppEntityBase { definitions["CloseAfter"] := Map( "type", "entity_reference", "entityType", "task", - "multiple", true, + "cardinality", 1, ; Change to another number once widgets for multiple values are worked out "group", "tasks", "modes", Map( "simple", Map("formField", false) @@ -295,6 +292,15 @@ class LauncherEntity extends AppEntityBase { "help", "If the Steam Overlay attaches within this time, and the Force option is not active, then the Launchpad Overlay will not be used." ) + definitions["AssetsDir"] := Map( + "type", "directory", + "description", "The directory where any required assets for this launcher will be saved.", + "default", this.app.Config["assets_dir"] . "\" . this.Id, + "group", "advanced", + "formField", false, + "modes", Map("simple", Map("formField", false)) + ) + return definitions } @@ -305,13 +311,6 @@ class LauncherEntity extends AppEntityBase { return (FileExist(this.GetLauncherFile(this.Id, checkSourceFile)) != "") } - ListKnownGames() { - return this.container - .Get("manager.data_source") - .GetDefaultDataSource() - .ReadListing("game-keys") - } - LauncherIsOutdated() { outdated := true @@ -320,7 +319,7 @@ class LauncherEntity extends AppEntityBase { if (filePath && FileExist(filePath)) { launcherVersion := FileGetVersion(this.GetLauncherFile(this.Id)) - if (launcherVersion && !this.app.Service("version_checker").VersionIsOutdated(this.app.Version, launcherVersion)) { + if (launcherVersion && !this.app["version_checker"].VersionIsOutdated(this.app.Version, launcherVersion)) { outdated := false } @@ -330,7 +329,7 @@ class LauncherEntity extends AppEntityBase { if (!buildInfo["Version"] || !buildInfo["Timestamp"]) { outdated := true } else { - if (configInfo["Version"] && this.app.Service("version_checker").VersionIsOutdated(configInfo["Version"], buildInfo["Version"])) { + if (configInfo["Version"] && this.app["version_checker"].VersionIsOutdated(configInfo["Version"], buildInfo["Version"])) { outdated := true } else if (configInfo["Timestamp"] && DateDiff(configInfo["Timestamp"], buildInfo["Timestamp"], "S") > 0) { outdated := true @@ -379,76 +378,17 @@ class LauncherEntity extends AppEntityBase { return ValidateResult } - SaveModifiedData() { - super.SaveModifiedData() + SaveEntity(recurse := true) { + super.SaveEntity(recurse) this.app.State.SetLauncherConfigInfo(this.Id) } - DiscoverDataSourceItemKey() { - if (!this["DataSourceItemKey"]) { - dataSources := this.GetAllDataSources() - - for index, dataSource in dataSources { - platform := this["Platform"] ? this["Platform"]["id"] : "" - apiPath := "lookup/" this.Id - - if (platform) { - apiPath .= "/" . platform - } - - dsData := dataSource.ReadJson(apiPath) - - if (dsData != "" && dsData.Has("id") && dsData["id"]) { - this["DataSourceItemKey"] := dsData["id"] - break - } - } - } - - if (this["DataSourceItemKey"]) { - return this["DataSourceItemKey"] - } else { - return "" - } - } - IconFileExists() { - iconSrc := this["IconSrc"] != "" ? this["IconSrc"] : this.GetAssetPath(this.Id . ".ico") - return FileExist(iconSrc) - } - - MergeAdditionalDataSourceDefaults(defaults, dataSourceData) { - launcherType := this.DetectLauncherType(defaults, dataSourceData) - - checkType := (launcherType == "") ? "Default" : launcherType - if (dataSourceData.Has("Launchers") && dataSourceData["Launchers"].Has(checkType) && HasBase(dataSourceData["Launchers"][checkType], Map.Prototype)) { - this.additionalManagedLauncherDefaults := this.merger.Merge(dataSourceData["Launchers"][checkType], this.additionalManagedLauncherDefaults) - defaults := this.merger.Merge(defaults, dataSourceData["Launchers"][checkType]) - } - - defaults["ManagedLauncher"] := launcherType + iconSrc := (this["IconSrc"] != "") + ? this["IconSrc"] + : this["AssetsDir"] . "\" . this.Id . ".ico" - return defaults - } - - DetectLauncherType(defaults, dataSourceData := "") { - launcherType := "" - - if (this.UnmergedFieldData.Has("LauncherType")) { - launcherType := this.UnmergedFieldData["LauncherType"] - } else if (defaults.Has("LauncherType")) { - launcherType := defaults["LauncherType"] - } - - if (launcherType == "") { - launcherType := "Default" - } - - if (dataSourceData != "" && dataSourceData.Has("Launchers")) { - launcherType := this._dereferenceKey(launcherType, dataSourceData["Launchers"]) - } - - return launcherType + return FileExist(iconSrc) } _dereferenceKey(key, map) { @@ -459,19 +399,19 @@ class LauncherEntity extends AppEntityBase { return key } - AutoDetectValues(recurse := true) { - detectedValues := super.AutoDetectValues(recurse) + AutoDetectValues() { + detectedValues := super.AutoDetectValues() if (!detectedValues.Has("IconSrc")) { checkPath := this["AssetsDir"] . "\" . this.Id . ".ico" if (FileExist(checkPath)) { detectedValues["IconSrc"] := checkPath - } else if (this.Has("ManagedGame", false) && this["ManagedGame"].Has("Exe", false)) { - detectedValues["IconSrc"] := this["ManagedGame"].LocateExe() + } else if (this.Has("GameProcess", false) && this["GameProcess"].Has("Exe", false)) { + detectedValues["IconSrc"] := this["GameProcess"].LocateExe() } else { theme := this.container.Get("manager.theme").GetComponent() - detectedValues["IconSrc"] := theme.GetIconPath("Game") + detectedValues["IconSrc"] := theme.GetIconPath("game") } } diff --git a/Lib/Launchpad/Entity/ManagedLauncherEntity.ahk b/Lib/Launchpad/Entity/LauncherProcessEntity.ahk similarity index 79% rename from Lib/Launchpad/Entity/ManagedLauncherEntity.ahk rename to Lib/Launchpad/Entity/LauncherProcessEntity.ahk index c060cb91..ee21fcc1 100644 --- a/Lib/Launchpad/Entity/ManagedLauncherEntity.ahk +++ b/Lib/Launchpad/Entity/LauncherProcessEntity.ahk @@ -1,16 +1,13 @@ -class ManagedLauncherEntity extends ManagedEntityBase { - configPrefix := "Launcher" - defaultType := "Default" +class LauncherProcessEntity extends LaunchProcessEntity { defaultClass := "SimpleLauncher" - dataSourcePath := "launcher-types" BaseFieldDefinitions() { definitions := super.BaseFieldDefinitions() - definitions["ManagedGame"] := Map( + definitions["GameProcess"] := Map( "type", "entity_reference", "required", true, - "entityType", "managed_game", + "entityType", "game_process", "child", false, "formField", false, "editable", false @@ -18,14 +15,14 @@ class ManagedLauncherEntity extends ManagedEntityBase { definitions["CloseBeforeRun"] := Map( "type", "boolean", - "storageKey", this.configPrefix . "CloseBeforeRun", + "storageKey", "CloseBeforeRun", "default", false, "description", "whether or not the launcher should be closed (if it is running) before starting the game" ) definitions["CloseAfterRun"] := Map( "type", "boolean", - "storageKey", this.configPrefix . "CloseAfterRun", + "storageKey", "CloseAfterRun", "default", false, "description", "Indicates whether the launcher should be closed after the game stops" ) @@ -34,7 +31,7 @@ class ManagedLauncherEntity extends ManagedEntityBase { "type", "time_offset", "timeUnits", "s", "min", 0, - "storageKey", this.configPrefix . "ClosePreDelay", + "storageKey", "ClosePreDelay", "default", 0, "required", true, "group", "advanced", @@ -48,7 +45,7 @@ class ManagedLauncherEntity extends ManagedEntityBase { "type", "time_offset", "timeUnits", "s", "min", 0, - "storageKey", this.configPrefix . "ClosePostDelay", + "storageKey", "ClosePostDelay", "default", 0, "required", true, "group", "advanced", @@ -65,7 +62,7 @@ class ManagedLauncherEntity extends ManagedEntityBase { ; - "AutoPolite" - Automatically attempt to politely close the launcher, or fail if it can't be closed politely ; - "AutoKill" - Automatically attempts to end the launcher process, forcefully if needed definitions["CloseMethod"] := Map( - "storageKey", this.configPrefix . "CloseMethod", + "storageKey", "CloseMethod", "default", "Prompt", "description", "How to attempt to close the launcher if running", "widget", "select", @@ -77,7 +74,7 @@ class ManagedLauncherEntity extends ManagedEntityBase { "type", "time_offset", "timeUnits", "s", "min", 0, - "storageKey", this.configPrefix . "RecheckDelay", + "storageKey", "RecheckDelay", "default", 10, "required", true, "group", "advanced", @@ -91,7 +88,7 @@ class ManagedLauncherEntity extends ManagedEntityBase { "type", "time_offset", "timeUnits", "s", "min", 0, - "storageKey", this.configPrefix . "WaitTimeout", + "storageKey", "WaitTimeout", "default", 30, "required", true, "group", "advanced", @@ -105,11 +102,11 @@ class ManagedLauncherEntity extends ManagedEntityBase { "type", "time_offset", "timeUnits", "ms", "min", 0, - "storageKey", this.configPrefix . "KillPreDelay", + "storageKey", "KillPreDelay", "default", 10, "required", true, "group", "advanced", - "description", "If killing a managed launcher forcefully, ending the process will be delayed by this number of seconds.", + "description", "If killing a launch process forcefully, ending the process will be delayed by this number of seconds.", "modes", Map( "simple", Map("formField", false) ) @@ -119,11 +116,11 @@ class ManagedLauncherEntity extends ManagedEntityBase { "type", "time_offset", "timeUnits", "ms", "min", 0, - "storageKey", this.configPrefix . "KillPostDelay", + "storageKey", "KillPostDelay", "default", 5, "required", true, "group", "advanced", - "description", "If killing a managed launcher forcefully, the launcher will wait this number of seconds after trying to end the process before reporting success.", + "description", "If killing a launch process forcefully, the launcher will wait this number of seconds after trying to end the process before reporting success.", "modes", Map( "simple", Map("formField", false) ) @@ -133,7 +130,7 @@ class ManagedLauncherEntity extends ManagedEntityBase { "type", "time_offset", "timeUnits", "s", "min", 0, - "storageKey", this.configPrefix . "PoliteCloseWait", + "storageKey", "PoliteCloseWait", "required", true, "default", 10, "group", "advanced", diff --git a/Lib/Launchpad/Entity/PlatformEntity.ahk b/Lib/Launchpad/Entity/PlatformEntity.ahk index 43a4a1b8..77060a1a 100644 --- a/Lib/Launchpad/Entity/PlatformEntity.ahk +++ b/Lib/Launchpad/Entity/PlatformEntity.ahk @@ -1,7 +1,5 @@ -class PlatformEntity extends AppEntityBase { +class PlatformEntity extends FieldableEntity { platformObj := "" - configPrefix := "" - dataSourcePath := "platforms" Platform { get => this.GetPlatform() @@ -136,8 +134,8 @@ class PlatformEntity extends AppEntityBase { } } - AutoDetectValues(recurse := true) { - detectedValues := super.AutoDetectValues(recurse) + AutoDetectValues() { + detectedValues := super.AutoDetectValues() detectedValues["IsInstalled"] := this.Platform.IsInstalled() detectedValues["InstalledVersion"] := this.Platform.GetInstalledVersion() detectedValues["InstallDir"] := this.Platform.GetInstallDir() diff --git a/Lib/Launchpad/GamePlatform/GamePlatformBase.ahk b/Lib/Launchpad/GamePlatform/GamePlatformBase.ahk index d5b3f944..7c25316b 100644 --- a/Lib/Launchpad/GamePlatform/GamePlatformBase.ahk +++ b/Lib/Launchpad/GamePlatform/GamePlatformBase.ahk @@ -87,7 +87,7 @@ class GamePlatformBase { } NeedsUpdate() { - return this.app.Service("version_checker").VersionIsOutdated(this.GetLatestVersion(), this.GetInstalledVersion()) + return this.app["version_checker"].VersionIsOutdated(this.GetLatestVersion(), this.GetInstalledVersion()) } GetInstalledVersion() { @@ -138,7 +138,7 @@ class GamePlatformBase { return [] } - GetLauncherSpecificId(key) { + GetPlatformRef(key) { return key } @@ -164,8 +164,8 @@ class GamePlatformBase { locator := GameExeLocator(installDir) possibleExes := locator.Locate("") exeName := this.DetermineMainExe(key, possibleExes) - launcherSpecificId := this.GetLauncherSpecificId(key) - detectedGameObj := DetectedGame(key, this, this.launcherType, this.gameType, installDir, exeName, launcherSpecificId, possibleExes) + platformRef := this.GetPlatformRef(key) + detectedGameObj := DetectedGame(key, this, this.launcherType, this.gameType, installDir, exeName, platformRef, possibleExes) if (this.installDir) { detectedGameObj.launcherInstallDir := this["InstallDir"] @@ -179,35 +179,48 @@ class GamePlatformBase { } DetermineMainExe(key, possibleExes) { - dataSource := this.app.Service("manager.data_source").GetDefaultDataSource() - dsData := this.GetDataSourceDefaults(dataSource, key) - mainExe := "" if (possibleExes.Length == 1) { mainExe := possibleExes[1] - } else if (possibleExes.Length > 1 && dsData.Has("GameExe")) { - for index, possibleExe in possibleExes { - SplitPath(possibleExe, &fileName) - - if (dsData["GameExe"] == fileName) { - mainExe := possibleExe - break + } else if (possibleExes.Length > 1) { + ; @todo move the API functionality into an event in an event in the WebServicesEventSubscriber + if (this.app.Services.Has("web_services.adapter_manager")) { + resultData := this.app["web_services.adapter_manager"].AdapterRequest( + Map("id", key), + Map( + "dataType", "entity_data", + "entityType", "launcher", + "tags", "defaults" + ), + "read", + true + ) + + for key, data in resultData { + if ( + data + && HasBase(data, Map.Prototype) + && data.Has("defaults") + && data["defaults"] + && data["defaults"].Has("GameExe") + && data["defaults"]["GameExe"] + ) { + for index, possibleExe in possibleExes { + SplitPath(possibleExe, &fileName) + + if (data["defaults"]["GameExe"] == fileName) { + mainExe := possibleExe + break 2 + } + } + } } } - } - return mainExe - } - - GetDataSourceDefaults(dataSource, key) { - defaults := Map() - dsData := dataSource.ReadJson(key, "Games") - - if (dsData != "" && dsData.Has("data") && dsData["data"].Has("defaults")) { - defaults := this.merger.Merge(dsData["data"]["defaults"], defaults) + } - return defaults + return mainExe } } diff --git a/Lib/Launchpad/Gui/Dialog/AccountInfoWindow.ahk b/Lib/Launchpad/Gui/Dialog/AccountInfoWindow.ahk index 79b752ba..11299d17 100644 --- a/Lib/Launchpad/Gui/Dialog/AccountInfoWindow.ahk +++ b/Lib/Launchpad/Gui/Dialog/AccountInfoWindow.ahk @@ -3,26 +3,31 @@ defaults := super.GetDefaultConfig(container, config) defaults["title"] := "Account Info" defaults["buttons"] := "*&Save|&Cancel|&Logout" + defaults["webService"] := "" return defaults } Controls() { super.Controls() - if (this.app.Services.Has("Auth")) { - info := this.app.Service("Auth").GetStatusInfo() + if (this.app.Services.Has("entity_manager.web_service")) { + entityMgr := this.app.Services["entity_manager.web_service"] + + webService := this.config["webService"] + + if (!webService) { + throw AppException("Opened AccountInfoWindow without a webService specified.") + } + + info := webService.GetStatusInfo() if (info) { opts := "w" . this.windowSettings["contentWidth"] . " x" . this.margin . " y+" . this.margin this.guiObj.AddPicture("x" . this.margin . " y+" . this.margin, info["photo"]) - this.guiObj.AddText(opts, "Email: " . info["email"]) + this.guiObj.AddText(opts, "Account: " . info["account"]) } } - this.AddHeading("Player Name") - this.AddEdit("PlayerName", this.app.Config["player_name"], "", 250) - this.guiObj.AddText("w" . this.windowSettings["contentWidth"], "Note: Player name is stored locally and not synced with your online Launchpad account yet.") - position := "Wrap x" . this.margin . " y+" . this.margin options := position . " w" . this.windowSettings["contentWidth"] . " +0x200 c" . this.themeObj.GetColor("textLink") this.guiObj.AddLink(options, 'Manage your account at launchpad.games.') @@ -30,11 +35,17 @@ ProcessResult(result, submittedData := "") { if (result == "Logout") { - if (this.app.Services.Has("Auth")) { - this.app.Service("Auth").Logout() + webService := this.config["webService"] + + if (webService) { + webService.Logout() + } + } else if (result == "Login") { + webService := this.config["webService"] + + if (webService) { + webService.Login() } - } else if (result == "Save" && submittedData) { - this.app.Config["player_name"] := submittedData.PlayerName } return result diff --git a/Lib/Launchpad/Gui/Dialog/LoginWindow.ahk b/Lib/Launchpad/Gui/Dialog/LoginWindow.ahk index 66b256e0..7edc0d12 100644 --- a/Lib/Launchpad/Gui/Dialog/LoginWindow.ahk +++ b/Lib/Launchpad/Gui/Dialog/LoginWindow.ahk @@ -2,7 +2,6 @@ entityObj := "" entityManager := "" missingFields := Map() - dataSource := "" GetDefaultConfig(container, config) { defaults := super.GetDefaultConfig(container, config) diff --git a/Lib/Launchpad/Gui/Form/DetectedGameEditor.ahk b/Lib/Launchpad/Gui/Form/DetectedGameEditor.ahk index 04119844..3aee2ba9 100644 --- a/Lib/Launchpad/Gui/Form/DetectedGameEditor.ahk +++ b/Lib/Launchpad/Gui/Form/DetectedGameEditor.ahk @@ -2,10 +2,7 @@ detectedGameObj := "" newValues := Map() missingFields := Map() - dataSource := "" knownGames := "" - launcherTypes := "" - gameTypes := "" __New(container, themeObj, config, detectedGameObj) { this.detectedGameObj := detectedGameObj @@ -22,11 +19,45 @@ Create() { super.Create() - this.dataSource := this.app.Service("manager.data_source").GetDefaultDataSource() - this.knownPlatforms := this.dataSource.ReadListing("platforms") - this.knownGames := this.dataSource.ReadListing("game-keys") - this.launcherTypes := this.dataSource.ReadListing("launcher-types") - this.gameTypes := this.dataSource.ReadListing("game-types") + + this.knownPlatforms := [] + this.knownGames := [] + + ; @todo replace this, or at least refactor it to live somewhere else + if (this.container.Has("web_services.adapter_manager")) { + knownMap := Map( + "platform", "knownPlatforms", + "launcher", "knownGames", + ) + + for entityTypeId, varName in knownMap { + results := this.container["web_services.adapter_manager"].AdapterRequest("", Map( + "dataType", "entity_list", + "entityType", entityTypeId + ), "read", true) + + if (results) { + for , idList in results { + if (idList) { + for , id in idList { + exists := false + + for , item in %varName% { + if (item == id) { + exists := true + break + } + } + + if (!exists) { + this.%varName%.Push(id) + } + } + } + } + } + } + } } GetTitle() { @@ -35,12 +66,10 @@ Controls() { super.Controls() - this.Add("ComboBoxControl", "vId", "Id", this.detectedGameObj.Id, this.knownGames, "OnIdChange", "You can change the detected game key here, which will become the name of your launcher. Your existing launchers, and all launchers known about via the API, can be selected to match this game up with one of those items.") - this.Add("SelectControl", "vLauncherType", "Launcher Type", this.detectedGameObj.launcherType, this.launcherTypes, "OnLauncherTypeChange", "This tells " . this.app.appName . " how to interact with any launcher your game might require. If your game's launcher isn't listed, or your game doesn't have a launcher, start with `"Default`".`n`nYou can customize the details of the launcher type after it is added.") - this.Add("SelectControl", "vGameType", "Game Type", this.detectedGameObj.gameType, this.gameTypes, "OnGameTypeChange", "This tells " . this.app.appName . " how to launch your game. Most games can use 'default', but launchers can support different game types.`n`nYou can customize the details of the game type after it is added.") + this.Add("ComboBoxControl", "vId", "Id", this.detectedGameObj.key, this.knownGames, "OnIdChange", "You can change the detected game key here, which will become the name of your launcher. Your existing launchers, and all launchers known about via the API, can be selected to match this game up with one of those items.") this.Add("LocationBlock", "", "Install Dir", this.detectedGameObj.installDir, "InstallDir", "", true, "This is the directory that the game is installed in, if it could be detected.") this.Add("ComboBoxControl", "vExe", "Exe", this.detectedGameObj.exeName, this.detectedGameObj.possibleExeNames, "OnExeChange", "The main Exe, if detected, should be pre-selected. You may change it to be the name (or path) of another exe, or select another one of the detected .exe files from the list (if more than one was found).") - this.AddTextBlock("Launcher-Specific ID", "LauncherSpecificId", this.detectedGameObj.launcherSpecificId, "This is typically the ID which the game platform or launcher uses when referring to the game internally. Changing this value could cause issues with game launching.") + this.AddTextBlock("Launcher-Specific ID", "PlatformRef", this.detectedGameObj.platformRef, "This is typically the ID which the game platform or launcher uses when referring to the game internally. Changing this value could cause issues with game launching.") } AddTextBlock(heading, field, existingVal := "", helpText := "") { @@ -61,16 +90,6 @@ this.newValues["key"] := ctl.Text } - OnLauncherTypeChange(ctl, info) { - this.guiObj.Submit(false) - this.newValues["launcherType"] := ctl.Text - } - - OnGameTypeChange(ctl, info) { - this.guiObj.Submit(false) - this.newValues["gameType"] := ctl.Text - } - GetValue(key) { val := this.detectedGameObj.%key% @@ -117,9 +136,9 @@ this.newValues["exeName"] := ctl.Text } - OnLauncherSpecificIdChange(ctl, info) { + OnPlatformRefChange(ctl, info) { this.guiObj.Submit(false) - this.newValues["launcherSpecificId"] := ctl.Text + this.newValues["platformRef"] := ctl.Text } ProcessResult(result, submittedData := "") { diff --git a/Lib/Launchpad/Gui/Form/ImportShortcutForm.ahk b/Lib/Launchpad/Gui/Form/ImportShortcutForm.ahk index 82c7b008..de956383 100644 --- a/Lib/Launchpad/Gui/Form/ImportShortcutForm.ahk +++ b/Lib/Launchpad/Gui/Form/ImportShortcutForm.ahk @@ -29,7 +29,7 @@ GetLauncherConfig() { platformKey := Trim(this.guiObj["Platform"].Text) config := Map("Platform", platformKey) - platform := this.app.Service("entity_manager.platform")[platformKey] + platform := this.app["entity_manager.platform"][platformKey] if (platform) { config["LauncherType"] := platform.Platform.launcherType diff --git a/Lib/Launchpad/Gui/Form/LauncherCreateFormBase.ahk b/Lib/Launchpad/Gui/Form/LauncherCreateFormBase.ahk index dccde664..78d55ef9 100644 --- a/Lib/Launchpad/Gui/Form/LauncherCreateFormBase.ahk +++ b/Lib/Launchpad/Gui/Form/LauncherCreateFormBase.ahk @@ -1,10 +1,12 @@ class LauncherCreateFormBase extends FormGuiBase { knownGames := "" knownPlatforms := "" - dataSource := "" + launcherMgr := "" + platformMgr := "" __New(container, themeObj, config) { - this.dataSource := container.Get("manager.data_source").GetDefaultDataSource() + this.launcherMgr := container.Get("entity_manager.launcher") + this.platformMgr := container.Get("entity_manager.platform") super.__New(container, themeObj, config) } @@ -17,8 +19,8 @@ Create() { super.Create() - this.knownGames := this.dataSource.ReadListing("game-keys") - this.knownPlatforms := this.dataSource.ReadListing("platforms") + this.knownGames := this.launcherMgr.ListEntities(false, true) + this.knownPlatforms := this.platformMgr.ListEntities(false, true) } ProcessResult(result, submittedData := "") { diff --git a/Lib/Launchpad/Gui/Form/LauncherWizard.ahk b/Lib/Launchpad/Gui/Form/LauncherWizard.ahk index 1f1889b7..97bcd11a 100644 --- a/Lib/Launchpad/Gui/Form/LauncherWizard.ahk +++ b/Lib/Launchpad/Gui/Form/LauncherWizard.ahk @@ -25,7 +25,7 @@ GetLauncherConfig() { platformKey := Trim(this.guiObj["Platform"].Text) config := Map("Platform", platformKey, "GameInstallDir", this.installDir, "GameExe", this.exe) - platform := this.app.Service("entity_manager.platform")[platformKey] + platform := this.app["entity_manager.platform"][platformKey] if (platform) { config["LauncherType"] := platform.Platform.launcherType diff --git a/Lib/Launchpad/Gui/Form/SettingsWindow.ahk b/Lib/Launchpad/Gui/Form/SettingsWindow.ahk index e6482347..ecf2c0cd 100644 --- a/Lib/Launchpad/Gui/Form/SettingsWindow.ahk +++ b/Lib/Launchpad/Gui/Form/SettingsWindow.ahk @@ -122,14 +122,6 @@ ctl := this.guiObj.AddDDL("vlogging_level xs y+m Choose" . chosen . " w200 c" . this.themeObj.GetColor("editText"), this.container.Get("logger").GetLogLevels()) ctl.OnEvent("Change", "OnLoggingLevelChange") - this.AddConfigLocationBlock("API Endpoint", "api_endpoint") - - this.AddHeading("API Settings") - ctl := this.AddConfigCheckBox("Enable API login for enhanced functionality", "api_authentication") - ctl.ctl.NeedsRestart := true - ctl := this.AddConfigCheckBox("Automatically initiate API login when needed", "api_auto_login") - ctl.ctl.NeedsRestart := true - tabs.UseTab() closeW := 100 @@ -137,11 +129,11 @@ } OnManageBackups(btn, info) { - this.app.Service("entity_type.backup").OpenManageWindow() + this.app["entity_type.backup"].OpenManageWindow() } OnManagePlatforms(btn, info) { - this.app.Service("entity_type.platform").OpenManageWindow() + this.app["entity_type.platform"].OpenManageWindow() } AddConfigLocationBlock(heading, settingName, extraButton := "", helpText := "") { @@ -194,7 +186,7 @@ } else if (btn == "OpenLauncherFile") { this.app.Config.OpenLauncherFile() } else if (btn == "ReloadLauncherFile") { - this.app.Service("entity_manager.launcher").LoadComponents(true) + this.app["entity_manager.launcher"].LoadComponents(true) } } @@ -205,7 +197,7 @@ } else if (btn == "OpenBackupsFile") { this.app.Config.OpenBackupsFile() } else if (btn == "ReloadBackupsFile") { - this.app.Service("entity_manager.backup").LoadComponents(true) + this.app["entity_manager.backup"].LoadComponents(true) } } @@ -216,7 +208,7 @@ } else if (btn == "OpenPlatformsFile") { this.app.Config.OpenPlatformsFile() } else if (btn == "ReloadPlatformsFile") { - this.app.Service("entity_manager.platform").LoadComponents(true) + this.app["entity_manager.platform"].LoadComponents(true) } } @@ -246,34 +238,24 @@ } } - OnApiEndpointMenuClick(btn) { - if (btn == "ChangeApiEndpoint") { - this.app.Service("manager.data_source").GetDefaultDataSource().ChangeApiEndpoint("", "") - this.SetText("ApiEndpoint", this.app.Config["api_endpoint"], "Bold") - this.needsRestart := true - } else if (btn == "OpenApiEndpoint") { - this.app.Service("manager.data_source").GetDefaultDataSource().Open() - } - } - OnCacheDirMenuClick(btn) { if (btn == "ChangeCacheDir") { - this.app.Service("manager.cache").ChangeCacheDir() + this.app["manager.cache"].ChangeCacheDir() this.SetText("CacheDir", this.app.Config["cache_dir"], "Bold") } else if (btn == "OpenCacheDir") { - this.app.Service("manager.cache").OpenCacheDir() + this.app["manager.cache"].OpenCacheDir() } else if (btn == "FlushCacheDir") { - this.app.Service("manager.cache").FlushCaches(true, true) + this.app["manager.cache"].FlushCaches(true, true) } } OnBackupDirMenuClick(btn) { if (btn == "ChangeBackupDir") { - this.app.Service("entity_manager.backup").ChangeBackupDir() + this.app["entity_manager.backup"].ChangeBackupDir() this.SetText("BackupDir", this.app.Config["backup_dir"], "Bold") this.needsRestart := true } else if (btn == "OpenBackupDir") { - this.app.Service("entity_manager.backup").OpenBackupDir() + this.app["entity_manager.backup"].OpenBackupDir() } } @@ -318,7 +300,7 @@ this.app.Config.SaveConfig() if (this.needsRestart) { - response := this.app.Service("manager.gui").Dialog(Map( + response := this.app["manager.gui"].Dialog(Map( "title", "Restart " . this.app.appName . "?", "text", "One or more settings that have been changed require restarting " . this.app.appName . " to fully take effect.`n`nWould you like to restart " . this.app.appName . " now?" )) @@ -328,8 +310,8 @@ } } - if (this.app.Service("manager.gui").Has("MainWindow")) { - this.app.Service("manager.gui")["MainWindow"].UpdateListView() + if (this.app["manager.gui"].Has("MainWindow")) { + this.app["manager.gui"]["MainWindow"].UpdateListView() } return result diff --git a/Lib/Launchpad/Gui/Form/SetupWindow.ahk b/Lib/Launchpad/Gui/Form/SetupWindow.ahk index 1ebd5784..0be2ca76 100644 --- a/Lib/Launchpad/Gui/Form/SetupWindow.ahk +++ b/Lib/Launchpad/Gui/Form/SetupWindow.ahk @@ -16,7 +16,7 @@ Create() { super.Create() - this.availableThemes := this.app.Service("manager.theme").GetAvailableThemes() + this.availableThemes := this.app["manager.theme"].GetAvailableThemes() } AddDescription(text) { @@ -58,7 +58,7 @@ } GetInstalledPlatforms() { - platformMgr := this.app.Service("entity_manager.platform") + platformMgr := this.app["entity_manager.platform"] platformQuery := platformMgr.EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) .Condition(IsTrueCondition(), "IsInstalled") return platformQuery.Execute() @@ -80,7 +80,7 @@ this.guiObj.Submit(false) len := StrLen("PlatformToggle") name := SubStr(chk.Name, len + 1) - platformMgr := this.app.Service("entity_manager.platform") + platformMgr := this.app["entity_manager.platform"] if (platformMgr.Has(name)) { platform := platformMgr[name] @@ -115,8 +115,8 @@ ProcessResult(result, submittedData := "") { if (result == "Start") { - this.app.Service("config.app").SaveConfig() - this.app.Service("entity_manager.platform").SaveModifiedEntities() + this.app["config.app"].SaveConfig() + this.app["entity_manager.platform"].SaveModifiedEntities() if (submittedData.DetectGames) { result := "Detect" diff --git a/Lib/Launchpad/Gui/ManageWindow/DetectedGamesWindow.ahk b/Lib/Launchpad/Gui/ManageWindow/DetectedGamesWindow.ahk index e78ab041..6f8aa4ba 100644 --- a/Lib/Launchpad/Gui/ManageWindow/DetectedGamesWindow.ahk +++ b/Lib/Launchpad/Gui/ManageWindow/DetectedGamesWindow.ahk @@ -12,9 +12,7 @@ this.detectedGames := detectedGames this.state := container.Get("state.app") this.launcherManager := container.Get("entity_manager.launcher") - this.knownGames := container.Get("manager.data_source") - .GetDefaultDataSource() - .ReadListing("game-keys") + this.knownGames := this.launcherManager.ListEntities(false, true) super.__New(container, themeObj, config) } @@ -83,7 +81,7 @@ GetListViewImgList(lv, large := false) { IL := IL_Create(this.detectedGames.Count, 1, large) - defaultIcon := this.themeObj.GetIconPath("Game") + defaultIcon := this.themeObj.GetIconPath("game") iconNum := 1 for key, detectedGameObj in this.detectedGames { @@ -200,7 +198,7 @@ detectedGameObj := this.detectedGames[key] - result := this.app.Service("manager.gui").Dialog(Map( + result := this.app["manager.gui"].Dialog(Map( "type", "DetectedGameEditor", "ownerOrParent", this.guiId, "child", true @@ -233,7 +231,7 @@ menuItems := [] menuItems.Push(Map("label", "Edit", "name", "EditDetectedGame")) - result := this.app.Service("manager.gui").Menu(menuItems, this) + result := this.app["manager.gui"].Menu(menuItems, this) if (result == "EditDetectedGame") { this.EditDetectedGame(key) diff --git a/Lib/Launchpad/Gui/ManageWindow/MainWindow.ahk b/Lib/Launchpad/Gui/ManageWindow/MainWindow.ahk index 25dfa92e..3f897337 100644 --- a/Lib/Launchpad/Gui/ManageWindow/MainWindow.ahk +++ b/Lib/Launchpad/Gui/ManageWindow/MainWindow.ahk @@ -3,6 +3,7 @@ launcherManager := "" platformManager := "" showDetailsPane := true + lvResizeOpts := "h" __New(container, themeObj, config) { this.launcherManager := container.Get("entity_manager.launcher") @@ -17,7 +18,7 @@ defaults["child"] := false defaults["title"] := container.GetApp().appName defaults["titleIsMenu"] := true - defaults["showStatusIndicator"] := !!(container.Get("config.app").Has("api_authentication") && container.Get("config.app")["api_authentication"]) + defaults["showStatusIndicator"] := container.Has("entity_manager.web_service") return defaults } @@ -90,144 +91,12 @@ } } - _getToolsMenuEntityTypes() { - entityTypes := Map() - - for key, entityType in this.container["manager.entity_type"] { - if (entityType.definition["manager_link_in_tools_menu"]) { - entityTypes[key] := entityType - } - } - - return entityTypes - } - ShowTitleMenu() { - menuEntityTypes := this._getToolsMenuEntityTypes() - toolsItems := [] - - for key, entityType in menuEntityTypes { - menuLinkText := entityType.definition["manager_menu_link_text"] - - if (!menuLinkText) { - menuLinkText := "&" . entityType.definition["name_plural"] - } - - toolsItems.Push(Map("label", menuLinkText, "name", "manage_" . key)) - } - - toolsItems.Push(Map("label", "&Modules", "name", "ManageModules")) - toolsItems.Push(Map("label", "&Flush Cache", "name", "FlushCache")) - - launchersItems := [] - launchersItems.Push(Map("label", "&Clean Launchers", "name", "CleanLaunchers")) - launchersItems.Push(Map("label", "&Reload Launchers", "name", "ReloadLaunchers")) - - aboutItems := [] - aboutItems.Push(Map("label", "&About Launchpad", "name", "About")) - aboutItems.Push(Map("label", "&Open Website", "name", "OpenWebsite")) - - menuItems := [] - menuItems.Push(Map("label", "&Tools", "name", "ToolsMenu", "childItems", toolsItems)) - menuItems.Push(Map("label", "&Launchers", "name", "LaunchersMenu", "childItems", launchersItems)) - menuItems.Push("") - menuItems.Push(Map("label", "&About", "name", "About", "childItems", aboutItems)) - menuItems.Push("") - menuItems.Push(Map("label", "&Settings", "name", "Settings")) - menuItems.Push(Map("label", "Check for &Updates", "name", "CheckForUpdates")) - menuItems.Push("") - menuItems.Push(Map("label", "Provide &Feedback", "name", "ProvideFeedback")) - menuItems.Push("") - menuItems.Push(Map("label", "&Restart", "name", "Reload")) - menuItems.Push(Map("label", "E&xit", "name", "Exit")) - - result := this.container["manager.gui"].Menu(menuItems, this, this.guiObj["WindowTitleText"]) - - if (result == "ManageModules") { - this.container["manager.gui"].OpenWindow("ManageModulesWindow") - } else if (result == "FlushCache") { - this.container["manager.cache"].FlushCaches(true, true) - } else if (result == "CleanLaunchers") { - this.container["manager.builder"].CleanLaunchers() - } else if (result == "ReloadLaunchers") { - this.launcherManager.LoadComponents(true) - this.UpdateListView() - } else if (result == "About") { - this.container["manager.gui"].Dialog(Map("type", "AboutWindow")) - } else if (result == "OpenWebsite") { - this.app.OpenWebsite() - } else if (result == "ProvideFeedback") { - this.app.ProvideFeedback() - } else if (result == "Settings") { - this.container["manager.gui"].Dialog(Map("type", "SettingsWindow", "unique", false)) - } else if (result == "CheckForUpdates") { - this.app.CheckForUpdates() - } else if (result == "Reload") { - this.app.restartApp() - } else if (result == "Exit") { - this.app.ExitApp() - } else { - for key, entityType in menuEntityTypes { - if (result == "manage_" . key) { - this.container["entity_type." . key].OpenManageWindow() - break - } - } - } - } - - GetStatusInfo() { - info := "" - - if (this.container.Has("Auth")) { - info := this.container["Auth"].GetStatusInfo() - } - - return info - } - - OnStatusIndicatorClick(btn, info) { - menuItems := [] - - if (this.container.Has("Auth")) { - if (this.container["Auth"].IsAuthenticated()) { - menuItems.Push(Map("label", "Account Details", "name", "AccountDetails")) - menuItems.Push(Map("label", "Logout", "name", "Logout")) - } else { - menuItems.Push(Map("label", "Login", "name", "Login")) - } - } - - result := this.container["manager.gui"].Menu(menuItems, this, btn) - - if (result == "AccountDetails") { - accountResult := this.container["manager.gui"].Dialog(Map( - "type", "AccountInfoWindow", - "ownerOrParent", this.guiId, - "child", true - )) - - if (accountResult == "OK") { - this.UpdateStatusIndicator() - } - } else if (result == "Logout") { - if (this.container.Has("Auth")) { - this.container["Auth"].Logout() - } - } else if (result == "Login") { - if (this.container.Has("Auth")) { - this.container["Auth"].Login() - } - } - } - - StatusWindowIsOnline() { - isOnline := false - - if (this.container.Has("Auth")) { - isOnline := this.container["Auth"].IsAuthenticated() - } - return isOnline + this.app.MainMenu( + this, + this.guiObj["WindowTitleText"], + false + ) } FormatDate(timestamp) { @@ -262,7 +131,9 @@ } status := launcher.GetStatus() - apiStatus := launcher["DataSourceItemKey"] ? "Linked" : "Not linked" + + ; @todo Move the API data to an event in the LaunchpadApi module + apiStatus := (launcher.HasField["web_service_launchpad_api_ref"] && launcher["web_service_launchpad_api_ref"]) ? "Linked" : "Not linked" created := this.FormatDate(this.app.State.GetLauncherCreated(key)) updated := this.FormatDate(this.app.State.GetLauncherInfo("Config")["Timestamp"]) built := this.FormatDate(this.app.State.GetLauncherInfo("Build")["Timestamp"]) @@ -438,7 +309,9 @@ } status := launcher.GetStatus() - apiStatus := launcher["DataSourceItemKey"] ? "Linked" : "Not linked" + + ; @todo Move the API code to the LaunchpadApi module + apiStatus := (launcher.HasField("web_service_launchpad_api_ref") && launcher["web_service_launchpad_api_ref"]) ? "Linked" : "Not linked" created := this.FormatDate(this.app.State.GetLauncherCreated(key)) updated := this.FormatDate(this.app.State.GetLauncherInfo(key, "Config")["Timestamp"]) built := this.FormatDate(this.app.State.GetLauncherInfo(key, "Build")["Timestamp"]) @@ -513,7 +386,7 @@ GetListViewImgList(lv, large := false) { IL := IL_Create(this.launcherManager.Count(true), 1, large) - defaultIcon := this.themeObj.GetIconPath("Game") + defaultIcon := this.themeObj.GetIconPath("game") iconNum := 1 for key, launcher in this.launcherManager { @@ -533,7 +406,7 @@ GetItemImage(launcher) { iconSrc := launcher["IconSrc"] assetIcon := launcher["AssetsDir"] . "\" . launcher.Id . ".ico" - defaultIcon := this.themeObj.GetIconPath("Game") + defaultIcon := this.themeObj.GetIconPath("game") if ((!iconSrc || !FileExist(iconSrc)) && FileExist(assetIcon)) { iconSrc := assetIcon @@ -581,7 +454,7 @@ } entity.SaveEntity() - entity.UpdateDataSourceDefaults() + entity.UpdateDefaults() this.UpdateListView() } } diff --git a/Lib/Launchpad/Gui/ManageWindow/ManageBackupsWindow.ahk b/Lib/Launchpad/Gui/ManageWindow/ManageBackupsWindow.ahk index a73baba9..be595c3e 100644 --- a/Lib/Launchpad/Gui/ManageWindow/ManageBackupsWindow.ahk +++ b/Lib/Launchpad/Gui/ManageWindow/ManageBackupsWindow.ahk @@ -40,7 +40,7 @@ class ManageBackupsWindow extends ManageWindowBase { GetListViewImgList(lv, large := false) { IL := IL_Create(this.backupManager.Count(true), 1, large) - defaultIcon := this.themeObj.GetIconPath("Backup") + defaultIcon := this.themeObj.GetIconPath("backup") iconNum := 1 for key, backup in this.backupManager { @@ -109,7 +109,7 @@ class ManageBackupsWindow extends ManageWindowBase { menuItems.Push(Map("label", "Restore", "name", "RestoreBackup")) menuItems.Push(Map("label", "Delete", "name", "DeleteBackup")) - result := this.app.Service("manager.gui").Menu(menuItems, this) + result := this.app["manager.gui"].Menu(menuItems, this) if (result == "EditBackup") { this.EditBackup(key) diff --git a/Lib/Launchpad/Gui/ManageWindow/ManageModulesWindow.ahk b/Lib/Launchpad/Gui/ManageWindow/ManageModulesWindow.ahk index bc961de2..7597c34b 100644 --- a/Lib/Launchpad/Gui/ManageWindow/ManageModulesWindow.ahk +++ b/Lib/Launchpad/Gui/ManageWindow/ManageModulesWindow.ahk @@ -1,5 +1,5 @@ class ManageModulesWindow extends ManageWindowBase { - listViewColumns := Array("NAME", "ENABLED", "SOURCE", "VERSION") + listViewColumns := Array("NAME", "CATEGORY", "ENABLED", "SOURCE", "VERSION") moduleManager := "" needsRestart := false @@ -26,9 +26,8 @@ class ManageModulesWindow extends ManageWindowBase { for name, module in this.moduleManager.All("", false, true) { enabledText := module.IsEnabled() ? "Yes" : "No" - ; TODO Define source - source := "" - data[name] := [name, enabledText, source, module.GetVersion()] + source := module.IsCore() ? "Built-in" : "Third-party" + data[name] := [name, module.moduleInfo["category"], enabledText, source, module.GetVersion()] } return data @@ -44,7 +43,7 @@ class ManageModulesWindow extends ManageWindowBase { GetListViewImgList(lv, large := false) { IL := IL_Create(this.lvCount, 1, large) - defaultIcon := this.themeObj.GetIconPath("Module") + defaultIcon := this.themeObj.GetIconPath("module") iconNum := 1 for key, module in this.moduleManager.All("", false, true) { @@ -85,7 +84,7 @@ class ManageModulesWindow extends ManageWindowBase { this.Submit(false) } - response := this.app.Service("manager.gui").Dialog(Map( + response := this.app["manager.gui"].Dialog(Map( "title", "Restart " . this.app.appName . "?", "text", "One or more module changes require restarting " . this.app.appName . " to fully take effect.`n`nWould you like to restart " . this.app.appName . " now?" )) @@ -154,7 +153,7 @@ class ManageModulesWindow extends ManageWindowBase { menuItems.Push(Map("label", "Delete", "name", "DeleteModule")) } - result := this.app.Service("manager.gui").Menu(menuItems, this) + result := this.app["manager.gui"].Menu(menuItems, this) if (result == "EnableModule") { this.EnableModule(key) diff --git a/Lib/Launchpad/Gui/ManageWindow/PlatformsWindow.ahk b/Lib/Launchpad/Gui/ManageWindow/PlatformsWindow.ahk index 51a526af..b3b54fe7 100644 --- a/Lib/Launchpad/Gui/ManageWindow/PlatformsWindow.ahk +++ b/Lib/Launchpad/Gui/ManageWindow/PlatformsWindow.ahk @@ -4,7 +4,7 @@ class PlatformsWindow extends ManageWindowBase { __New(container, themeObj, config) { this.platformManager := container.Get("entity_manager.platform") - this.lvCount := this.platformManager.Count(true) + this.lvCount := this.platformManager.Count(true) - 1 super.__New(container, themeObj, config) } @@ -24,10 +24,12 @@ class PlatformsWindow extends ManageWindowBase { data := Map() for key, platform in this.platformManager { - enabledText := platform["IsEnabled"] ? "Yes" : "No" - detectGamesText := platform["DetectGames"] ? "Yes" : "No" - installedText := platform["IsInstalled"] ? "Yes" : "No" - data[key] := [platform.GetName(), enabledText, detectGamesText, installedText, platform["InstalledVersion"]] + if (key != "Basic") { + enabledText := platform["IsEnabled"] ? "Yes" : "No" + detectGamesText := platform["DetectGames"] ? "Yes" : "No" + installedText := platform["IsInstalled"] ? "Yes" : "No" + data[key] := [platform.GetName(), enabledText, detectGamesText, installedText, platform["InstalledVersion"]] + } } return data @@ -43,18 +45,21 @@ class PlatformsWindow extends ManageWindowBase { GetListViewImgList(lv, large := false) { IL := IL_Create(this.platformManager.Count(true), 1, large) - defaultIcon := this.themeObj.GetIconPath("Platform") + defaultIcon := this.themeObj.GetIconPath("platform") iconNum := 1 for key, platform in this.platformManager { - iconSrc := platform["IconSrc"] - if (!iconSrc or !FileExist(iconSrc)) { - iconSrc := defaultIcon - } + if (key != "Basic") { + iconSrc := platform["IconSrc"] - IL_Add(IL, iconSrc) - iconNum++ + if (!iconSrc or !FileExist(iconSrc)) { + iconSrc := defaultIcon + } + + IL_Add(IL, iconSrc) + iconNum++ + } } return IL @@ -110,7 +115,7 @@ class PlatformsWindow extends ManageWindowBase { menuItems.Push(Map("label", "Install", "name", "InstallPlatform")) } - result := this.app.Service("manager.gui").Menu(menuItems, this) + result := this.app["manager.gui"].Menu(menuItems, this) if (result == "EditPlatform") { this.EditPlatform(key) diff --git a/Lib/Launchpad/Includes.ahk b/Lib/Launchpad/Includes.ahk index cfacabc5..cd69a6ec 100644 --- a/Lib/Launchpad/Includes.ahk +++ b/Lib/Launchpad/Includes.ahk @@ -21,12 +21,11 @@ #Include Config\LaunchpadConfig.ahk #Include Config\PlatformsConfig.ahk #Include ConfigMigrator\LaunchpadIniMigrator.ahk -#Include DataSource\LocalDataSource.ahk #Include DetectedGame\DetectedGame.ahk +#Include Entity\GameProcessEntity.ahk #Include Entity\LauncherEntity.ahk -#Include Entity\ManagedEntityBase.ahk -#Include Entity\ManagedGameEntity.ahk -#Include Entity\ManagedLauncherEntity.ahk +#Include Entity\LauncherProcessEntity.ahk +#Include Entity\LaunchProcessEntity.ahk #Include Entity\PlatformEntity.ahk #Include GamePlatform\BasicPlatform.ahk #Include GamePlatform\GamePlatformBase.ahk diff --git a/Lib/Launchpad/Modules/Blizzard/GamePlatform/BlizzardPlatform.ahk b/Lib/Launchpad/Modules/Blizzard/GamePlatform/BlizzardPlatform.ahk index 5d326086..097d4a3e 100644 --- a/Lib/Launchpad/Modules/Blizzard/GamePlatform/BlizzardPlatform.ahk +++ b/Lib/Launchpad/Modules/Blizzard/GamePlatform/BlizzardPlatform.ahk @@ -22,22 +22,22 @@ class BlizzardPlatform extends RegistryLookupGamePlatformBase { productInstalls := [] if (this.app.Services.Has("BlizzardProductDb")) { - productInstalls := this.app.Service("BlizzardProductDb").GetProductInstalls() + productInstalls := this.app["BlizzardProductDb"].GetProductInstalls() } games := [] for index, productData in productInstalls { - launcherSpecificId := productData["productCode"] + platformRef := productData["productCode"] - if (launcherSpecificId != "agent" && launcherSpecificId != "bna" && productData.Has("settings") && productData["settings"].Has("installPath")) { + if (platformRef != "agent" && platformRef != "bna" && productData.Has("settings") && productData["settings"].Has("installPath")) { installPath := productData["settings"]["installPath"] installPath := StrReplace(installPath, "/", "\") SplitPath(installPath, &key) locator := GameExeLocator(installPath) possibleExes := locator.Locate("") mainExe := this.DetermineMainExe(key, possibleExes) - games.Push(DetectedGame(key, this, this.launcherType, this.gameType, installPath, mainExe, launcherSpecificId, possibleExes)) + games.Push(DetectedGame(key, this, this.launcherType, this.gameType, installPath, mainExe, platformRef, possibleExes)) } } diff --git a/Lib/Launchpad/Modules/Epic/GamePlatform/EpicPlatform.ahk b/Lib/Launchpad/Modules/Epic/GamePlatform/EpicPlatform.ahk index 06e768d5..1cdb2845 100644 --- a/Lib/Launchpad/Modules/Epic/GamePlatform/EpicPlatform.ahk +++ b/Lib/Launchpad/Modules/Epic/GamePlatform/EpicPlatform.ahk @@ -3,12 +3,12 @@ class EpicPlatform extends RegistryLookupGamePlatformBase { displayName := "Epic Store" launcherType := "Epic" gameType := "Epic" - installDirRegView := 32 - installDirRegKey := "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{0E63B233-DC24-442C-BD38-0B91D90FEC5B}" - versionRegView := 32 - versionRegKey := "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{0E63B233-DC24-442C-BD38-0B91D90FEC5B}" - uninstallCmdRegView := 32 - uninstallCmdRegKey := "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{0E63B233-DC24-442C-BD38-0B91D90FEC5B}" + installDirRegView := 64 + installDirRegKey := "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\B4B4F9022FD3528499604D6D8AE00CE9\InstallProperties" + versionRegView := 64 + versionRegKey := "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\B4B4F9022FD3528499604D6D8AE00CE9\InstallProperties" + uninstallCmdRegView := 64 + uninstallCmdRegKey := "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\B4B4F9022FD3528499604D6D8AE00CE9\InstallProperties" Install() { Run("https://www.epicgames.com/store/en-US/download") @@ -41,15 +41,29 @@ class EpicPlatform extends RegistryLookupGamePlatformBase { } if (isGame) { - key := obj["Name"] + key := obj.Has("Name") ? obj["Name"] : "" + + if (!key && obj.Has("DisplayName")) { + key := obj["DisplayName"] + } + + if (!key && obj.Has("MandatoryAppFolderName")) { + key := obj["MandatoryAppFolderName"] + } + + if (!key) { + throw AppException("Could not determine detected game key.") + } + + displayName := obj.Has("DisplayName") ? obj["DisplayName"] : "" installDir := obj["InstallLocation"] - launcherSpecificId := obj["AppName"] + platformRef := obj["AppName"] ;exeName := obj["LaunchExecutable"] ;possibleExes := [obj["LaunchExecutable"]] locator := GameExeLocator(installDir) possibleExes := locator.Locate("") mainExe := this.DetermineMainExe(key, possibleExes) - games.Push(DetectedGame(key, this, this.launcherType, this.gameType, installDir, mainExe, launcherSpecificId, possibleExes)) + games.Push(DetectedGame(key, this, this.launcherType, this.gameType, installDir, mainExe, platformRef, possibleExes, displayName)) } } } diff --git a/Lib/Launchpad/Modules/Riot/GamePlatform/RiotPlatform.ahk b/Lib/Launchpad/Modules/Riot/GamePlatform/RiotPlatform.ahk index ef37ab9e..091b1bf5 100644 --- a/Lib/Launchpad/Modules/Riot/GamePlatform/RiotPlatform.ahk +++ b/Lib/Launchpad/Modules/Riot/GamePlatform/RiotPlatform.ahk @@ -66,7 +66,7 @@ class RiotPlatform extends RegistryLookupGamePlatformBase { return dirs } - GetLauncherSpecificId(key) { + GetPlatformRef(key) { if (key == "VALORANT") { key := "valorant" } else if (key == "Legends of Runeterra") { diff --git a/Lib/Launchpad/Modules/Steam/GamePlatform/SteamPlatform.ahk b/Lib/Launchpad/Modules/Steam/GamePlatform/SteamPlatform.ahk index 3d88dfad..644f96ff 100644 --- a/Lib/Launchpad/Modules/Steam/GamePlatform/SteamPlatform.ahk +++ b/Lib/Launchpad/Modules/Steam/GamePlatform/SteamPlatform.ahk @@ -64,7 +64,7 @@ class SteamPlatform extends RegistryLookupGamePlatformBase { if (IsObject(obj) && obj.Has("AppState")) { gameState := obj["AppState"] - launcherSpecificId := gameState["appid"] + platformRef := gameState["appid"] key := gameState["name"] installDir := dir . "\common\" . gameState["installdir"] installDir := StrReplace(installDir, "/", "\") @@ -76,7 +76,7 @@ class SteamPlatform extends RegistryLookupGamePlatformBase { } mainExe := this.DetermineMainExe(key, possibleExes) - games.Push(DetectedGame(key, this, this.launcherType, this.gameType, installDir, mainExe, launcherSpecificId, possibleExes)) + games.Push(DetectedGame(key, this, this.launcherType, this.gameType, installDir, mainExe, platformRef, possibleExes)) } } } diff --git a/Lib/LaunchpadBuilder/App/LaunchpadBuilder.ahk b/Lib/LaunchpadBuilder/App/LaunchpadBuilder.ahk index 7397b273..6dffed31 100644 --- a/Lib/LaunchpadBuilder/App/LaunchpadBuilder.ahk +++ b/Lib/LaunchpadBuilder/App/LaunchpadBuilder.ahk @@ -2,12 +2,9 @@ class LaunchpadBuilder extends AppBase { GetParameterDefinitions(config) { parameters := super.GetParameterDefinitions(config) parameters["config_path"] := this.appDir . "\Launchpad.build.json" - parameters["config.api_endpoint"] := "https://api.launchpad.games/v1" - parameters["config.data_source_key"] := "" - parameters["config.api_authentication"] := true parameters["config.dist_dir"] := this.appDir . "\Dist" parameters["config.build_dir"] := this.appDir . "\Build" - parameters["config.icon_file"] := this.appDir . "\Resources\Graphics\Launchpad.ico" + parameters["config.icon_file"] := this.appDir . "\Resources\Graphics\launchpad.ico" parameters["config.github_username"] := "" parameters["config.github_token"] := "" parameters["config.github_repo"] := "VolantisDev/Launchpad" @@ -33,11 +30,6 @@ class LaunchpadBuilder extends AppBase { "arguments", [AppRef(), this.appDir . "\" . this.appName . ".ini"] ) - services["manager.data_source"] := Map( - "class", "DataSourceManager", - "arguments", [ContainerRef(), ServiceRef("manager.event"), ServiceRef("notifier"), ParameterRef("config.data_source_key")] - ) - services["FileHasher"] := "FileHasher" services["GitTagVersionIdentifier"] := Map( @@ -50,8 +42,8 @@ class LaunchpadBuilder extends AppBase { RunApp(config) { super.RunApp(config) - version := this.Service("GitTagVersionIdentifier").IdentifyVersion() - buildInfo := this.Service("manager.gui").Dialog(Map( + version := this["GitTagVersionIdentifier"].IdentifyVersion() + buildInfo := this["manager.gui"].Dialog(Map( "type", "BuildSettingsForm", "version", version )) @@ -60,8 +52,12 @@ class LaunchpadBuilder extends AppBase { this.ExitApp() } - if (buildInfo.DeployToApi && this.Services.Has("Auth")) { - this.Service("Auth").Login() + if (buildInfo.DeployToApi && this.Services.Has("entity_manager.web_service")) { + entityMgr := this.Services["entity_manager.web_service"] + + if (entityMgr.Has("launchpad_api") && entityMgr["launchpad_api"]["Enabled"]) { + entityMgr["launchpad_api"].Login() + } } version := buildInfo.Version @@ -80,7 +76,7 @@ class LaunchpadBuilder extends AppBase { } if (buildInfo.DeployToGitHub || buildInfo.DeployToApi || buildInfo.DeployToChocolatey) { - releaseInfo := this.Service("manager.gui").Dialog(Map("type", "ReleaseInfoForm")) + releaseInfo := this["manager.gui"].Dialog(Map("type", "ReleaseInfoForm")) if (!releaseInfo) { this.ExitApp() @@ -145,7 +141,7 @@ class LaunchpadBuilder extends AppBase { if (!this.GetCmdOutput("git show-ref " . version)) { RunWait("git tag " . version, this.appDir) - response := this.Service("manager.gui").Dialog(Map( + response := this["manager.gui"].Dialog(Map( "title", "Push git tag?", "text", "Would you like to push the git tag that was just created (" . version . ") to origin?" )) diff --git a/Lib/LaunchpadBuilder/AppBuilder/ChocoPkgBuilder.ahk b/Lib/LaunchpadBuilder/AppBuilder/ChocoPkgBuilder.ahk index 8ea2644b..a855d436 100644 --- a/Lib/LaunchpadBuilder/AppBuilder/ChocoPkgBuilder.ahk +++ b/Lib/LaunchpadBuilder/AppBuilder/ChocoPkgBuilder.ahk @@ -9,7 +9,7 @@ class ChocoPkgBuilder extends AppBuilderBase { throw AppException("Installer file doesn't exist, cannot build chocolatey package.") } - hash := this.app.Service("FileHasher").Hash(installer, FileHasher.HASH_TYPE_SHA256) + hash := this.app["FileHasher"].Hash(installer, FileHasher.HASH_TYPE_SHA256) if (!hash) { throw AppException("Failed to create an SHA256 hash of the installer file.") diff --git a/Lib/LaunchpadBuilder/BuildDeployer/ApiBuildDeployer.ahk b/Lib/LaunchpadBuilder/BuildDeployer/ApiBuildDeployer.ahk index 330209b9..5173dc95 100644 --- a/Lib/LaunchpadBuilder/BuildDeployer/ApiBuildDeployer.ahk +++ b/Lib/LaunchpadBuilder/BuildDeployer/ApiBuildDeployer.ahk @@ -2,7 +2,7 @@ class ApiBuildDeployer extends BuildDeployerBase { Deploy(deployInfo) { apiUrl := "https://api.launchpad.games/v1/release-info" - this.app.Service("manager.gui").Dialog(Map( + this.app["manager.gui"].Dialog(Map( "title", "Not Yet Available", "text", "Release info pushing is not yet available. Please update release info manually." )) diff --git a/Lib/LaunchpadLauncher/App/LaunchpadLauncher.ahk b/Lib/LaunchpadLauncher/App/LaunchpadLauncher.ahk index ec563367..7bc3a131 100644 --- a/Lib/LaunchpadLauncher/App/LaunchpadLauncher.ahk +++ b/Lib/LaunchpadLauncher/App/LaunchpadLauncher.ahk @@ -39,7 +39,7 @@ class LaunchpadLauncher extends AppBase { ) services["Game"] := Map( - "class", config["gameConfig"]["GameClass"], + "class", config["gameConfig"]["ProcessClass"], "arguments", [ AppRef(), ParameterRef("launcher_key"), @@ -48,7 +48,7 @@ class LaunchpadLauncher extends AppBase { ) services["Launcher"] := Map( - "class", config["launcherConfig"]["LauncherClass"], + "class", config["launcherConfig"]["ProcessClass"], "arguments", [ ParameterRef("launcher_key"), ServiceRef("manager.gui"), @@ -64,11 +64,11 @@ class LaunchpadLauncher extends AppBase { RunApp(config) { super.RunApp(config) - this.Service("Launcher").LaunchGame() + this["Launcher"].LaunchGame() } RestartApp() { - game := this.Service("Game") + game := this["Game"] if (game) { game.StopOverlay() @@ -78,7 +78,7 @@ class LaunchpadLauncher extends AppBase { } ExitApp() { - game := this.Service("Game") + game := this["Game"] if (game) { game.StopOverlay() diff --git a/Lib/LaunchpadLauncher/Condition/SteamConditionBase.ahk b/Lib/LaunchpadLauncher/Condition/SteamConditionBase.ahk index 43f0ebbe..765a5369 100644 --- a/Lib/LaunchpadLauncher/Condition/SteamConditionBase.ahk +++ b/Lib/LaunchpadLauncher/Condition/SteamConditionBase.ahk @@ -11,7 +11,7 @@ class SteamConditionBase extends ConditionBase { GetSteamPath(app) { steamPath := "" - platforms := app.Parameter("platforms") + platforms := app.Parameter["platforms"] if (platforms.Has("Steam") && platforms["Steam"].Has("InstallDir")) { steamPath := platforms["Steam"]["InstallDir"] diff --git a/Lib/LaunchpadLauncher/Game/BlizzardGame.ahk b/Lib/LaunchpadLauncher/Game/BlizzardGame.ahk index 15a0fc83..c88ed59b 100644 --- a/Lib/LaunchpadLauncher/Game/BlizzardGame.ahk +++ b/Lib/LaunchpadLauncher/Game/BlizzardGame.ahk @@ -4,10 +4,10 @@ class BlizzardGame extends SimpleGame { playButtonColors := ["0074E0", "148EFF"] GetRunCmd() { - launcherPath := this.app.Service("Launcher").config["LauncherInstallDir"] . "\" . this.app.Service("Launcher").config["LauncherExe"] + launcherPath := this.app["Launcher"].config["LauncherInstallDir"] . "\" . this.app["Launcher"].config["LauncherExe"] if (launcherPath != "") { - gameKey := this.config["GameLauncherSpecificId"] + gameKey := this.config["GamePlatformRef"] launcherPath .= " --game=" . gameKey . " --gamepath=`"" . this.config["GameInstallDir"] . "`" --productcode=" . gameKey } @@ -16,7 +16,7 @@ class BlizzardGame extends SimpleGame { RunGameRun() { pid := super.RunGameRun() - winTitle := this.app.Service("Launcher").config["LauncherWindowTitle"] + winTitle := this.app["Launcher"].config["LauncherWindowTitle"] if (!WinExist(winTitle)) { WinWait(winTitle) @@ -53,7 +53,7 @@ class BlizzardGame extends SimpleGame { } CleanupAfterRun(progress := "") { - winTitle := this.app.Service("Launcher").config["LauncherWindowTitle"] + winTitle := this.app["Launcher"].config["LauncherWindowTitle"] if (WinExist(winTitle)) { WinClose("ahk_id" . WinGetID(winTitle)) } diff --git a/Lib/LaunchpadLauncher/Game/GameBase.ahk b/Lib/LaunchpadLauncher/Game/GameBase.ahk index f50dd053..e943ab16 100644 --- a/Lib/LaunchpadLauncher/Game/GameBase.ahk +++ b/Lib/LaunchpadLauncher/Game/GameBase.ahk @@ -23,7 +23,7 @@ class GameBase { config := Map() } - this.launcherConfig := app.Service("config.app") + this.launcherConfig := app["config.app"] InvalidParameterException.CheckTypes("GameBase", "app", app, "AppBase", "key", key, "", "config", config, "Map") this.app := app this.key := key @@ -33,7 +33,7 @@ class GameBase { Log(message, level := "Debug") { if (this.app.Services.Has("logger") && this.launcherConfig["LoggingLevel"] != "None") { - this.app.Service("logger").Log(this.key . ": " . message, level) + this.app["logger"].Log(this.key . ": " . message, level) } } @@ -180,14 +180,14 @@ class GameBase { StartOverlay() { SetTimer(this.overlayCallbackObj, 0) this.Log("Starting Launchpad Overlay...") - this.app.Service("manager.overlay").Start(this.launcherConfig["OverlayHotkey"]) + this.app["manager.overlay"].Start(this.launcherConfig["OverlayHotkey"]) this.overlayStarted := true } StopOverlay() { if (this.overlayStarted) { this.Log("Shutting down Launchpad Overlay...") - this.app.Service("manager.overlay").Close() + this.app["manager.overlay"].Close() } } @@ -198,7 +198,7 @@ class GameBase { } this.Log("Closing overlay if running...") - this.app.Service("manager.overlay").Close() + this.app["manager.overlay"].Close() this.Log("Cleaning up scheduled task(s)...") this.CleanupScheduledTask() } diff --git a/Lib/LaunchpadLauncher/Game/RiotGame.ahk b/Lib/LaunchpadLauncher/Game/RiotGame.ahk index 0a63e8ec..1d63a6d2 100644 --- a/Lib/LaunchpadLauncher/Game/RiotGame.ahk +++ b/Lib/LaunchpadLauncher/Game/RiotGame.ahk @@ -1,9 +1,9 @@ class RiotGame extends SimpleGame { GetRunCmd() { - launcherPath := "`"" . this.app.Service("Launcher").config["LauncherInstallDir"] . "\" . this.app.Service("Launcher").config["LauncherExe"] . "`"" + launcherPath := "`"" . this.app["Launcher"].config["LauncherInstallDir"] . "\" . this.app["Launcher"].config["LauncherExe"] . "`"" if (launcherPath != "") { - gameKey := this.config["GameLauncherSpecificId"] + gameKey := this.config["GamePlatformRef"] launcherPath .= " --launch-product=" . gameKey . " --launch-patchline=live" } diff --git a/Lib/LaunchpadLauncher/Launcher/LauncherBase.ahk b/Lib/LaunchpadLauncher/Launcher/LauncherBase.ahk index 21754d91..900a7adb 100644 --- a/Lib/LaunchpadLauncher/Launcher/LauncherBase.ahk +++ b/Lib/LaunchpadLauncher/Launcher/LauncherBase.ahk @@ -189,7 +189,7 @@ class LauncherBase { } LaunchGameAction() { - this.Log("Calling managed game's RunGame action") + this.Log("Calling game process's RunGame action") return this.game.RunGame(this.progress) } diff --git a/Lib/Shared/Includes.ahk b/Lib/Shared/Includes.ahk index 9cfe560e..faff2db3 100644 --- a/Lib/Shared/Includes.ahk +++ b/Lib/Shared/Includes.ahk @@ -1,10 +1,28 @@ ; Automatically-generated file. Manual edits will be overwritten. -#Include Modules\Auth\AuthInfo\AuthInfo.ahk -#Include Modules\Auth\AuthInfo\JwtAuthInfo.ahk -#Include Modules\Auth\AuthProvider\AuthProviderBase.ahk -#Include Modules\Auth\AuthProvider\JwtAuthProvider.ahk -#Include Modules\LaunchpadApi\AuthProvider\LaunchpadApiAuthProvider.ahk -#Include Modules\LaunchpadApi\DataSource\ApiDataSource.ahk +#Include Modules\LaunchpadApi\EventSubscriber\LaunchpadApiSubscriber.ahk +#Include Modules\WebServices\ComponentManager\WebServiceAdapterManager.ahk +#Include Modules\WebServices\Entity\WebServiceEntity.ahk +#Include Modules\WebServices\Entity\WebServiceProviderEntity.ahk +#Include Modules\WebServices\Event\WebServicesEntityDataParamsEvent.ahk +#Include Modules\WebServices\Event\WebServicesRequestEvent.ahk +#Include Modules\WebServices\Event\WebServicesResponseEvent.ahk +#Include Modules\WebServices\Events\WebServicesEvents.ahk +#Include Modules\WebServices\EventSubscriber\WebServicesEventSubscriber.ahk +#Include Modules\WebServices\Factory\WebServiceAdapterFactory.ahk +#Include Modules\WebServices\Gui\AuthenticationGui\LaunchpadLoginWindow.ahk +#Include Modules\WebServices\Gui\Form\FeedbackWindow.ahk +#Include Modules\WebServices\Gui\ManageWindow\ManageWebServicesWindow.ahk +#Include Modules\WebServices\LayerSource\WebServiceAdapterLayerSource.ahk +#Include Modules\WebServices\WebServiceAdapter\FileWebServiceAdapter.ahk +#Include Modules\WebServices\WebServiceAdapter\JsonWebServiceAdapter.ahk +#Include Modules\WebServices\WebServiceAdapter\WebServiceAdapterBase.ahk +#Include Modules\WebServices\WebServiceAuthenticator\JwtWebServiceAuthenticator.ahk +#Include Modules\WebServices\WebServiceAuthenticator\WebServiceAuthenticatorBase.ahk +#Include Modules\WebServices\WebServiceRequest\BasicWebServiceRequest.ahk +#Include Modules\WebServices\WebServiceRequest\WebServiceRequestBase.ahk +#Include Modules\WebServices\WebServiceResponse\CachedWebServiceResponse.ahk +#Include Modules\WebServices\WebServiceResponse\HttpReqWebServiceResponse.ahk +#Include Modules\WebServices\WebServiceResponse\WebServiceResponseBase.ahk #Include Vendor\Gdip_All.ahk #Include Vendor\LV_Constants.ahk #Include Volantis.App\App\AppBase.ahk @@ -23,13 +41,6 @@ #Include Volantis.App\Config\AppConfig.ahk #Include Volantis.App\Container\ServiceComponentContainer.ahk #Include Volantis.App\Container\WindowContainer.ahk -#Include Volantis.App\DataSource\DataSourceBase.ahk -#Include Volantis.App\DataSourceItem\DataSourceItemBase.ahk -#Include Volantis.App\DataSourceItem\DSAssetFile.ahk -#Include Volantis.App\DataSourceItem\DSFile.ahk -#Include Volantis.App\DataSourceItem\DSJson.ahk -#Include Volantis.App\DataSourceItem\DSListing.ahk -#Include Volantis.App\Entity\AppEntityBase.ahk #Include Volantis.App\Entity\BackupEntity.ahk #Include Volantis.App\Entity\TaskEntity.ahk #Include Volantis.App\Event\AlterComponentsEvent.ahk @@ -38,7 +49,10 @@ #Include Volantis.App\Event\ComponentInfoEvent.ahk #Include Volantis.App\Event\DefineComponentsEvent.ahk #Include Volantis.App\Event\LoadComponentEvent.ahk +#Include Volantis.App\Event\MenuItemsEvent.ahk +#Include Volantis.App\Event\MenuResultEvent.ahk #Include Volantis.App\Event\RegisterComponentsEvent.ahk +#Include Volantis.App\Event\ReleaseInfoEvent.ahk #Include Volantis.App\Event\ServiceDefinitionsEvent.ahk #Include Volantis.App\Events\Events.ahk #Include Volantis.App\Exception\AppException.ahk @@ -53,7 +67,6 @@ #Include Volantis.App\Gui\Dialog\UpdateAvailableWindow.ahk #Include Volantis.App\Gui\EntityEditor\EntityEditorBase.ahk #Include Volantis.App\Gui\EntityEditor\SimpleEntityEditor.ahk -#Include Volantis.App\Gui\Form\FeedbackWindow.ahk #Include Volantis.App\Gui\Form\FormGuiBase.ahk #Include Volantis.App\Gui\Form\IconSelector.ahk #Include Volantis.App\Gui\ManageWindow\ManageEntitiesWindow.ahk @@ -83,12 +96,10 @@ #Include Volantis.App\Installer\InstallerComponent\GitHubReleaseInstallerComponent.ahk #Include Volantis.App\Installer\InstallerComponent\InstallerComponentBase.ahk #Include Volantis.App\Service\AppServiceBase.ahk -#Include Volantis.App\Service\AuthService.ahk #Include Volantis.App\Service\EventManager.ahk #Include Volantis.App\Service\LoggerService.ahk #Include Volantis.App\Service\NotificationService.ahk #Include Volantis.App\Service\ComponentManager\CacheManager.ahk -#Include Volantis.App\Service\ComponentManager\DataSourceManager.ahk #Include Volantis.App\Service\ComponentManager\GuiManager.ahk #Include Volantis.App\Service\ComponentManager\InstallerManager.ahk #Include Volantis.App\Service\ComponentManager\ThemeManager.ahk @@ -96,6 +107,7 @@ #Include Volantis.App\State\AppState.ahk #Include Volantis.App\State\CacheState.ahk #Include Volantis.App\State\JsonState.ahk +#Include Volantis.App\State\ParameterState.ahk #Include Volantis.App\State\StateBase.ahk #Include Volantis.Base\CLR\CLR.ahk #Include Volantis.Base\Event\EventBase.ahk @@ -210,16 +222,20 @@ #Include Volantis.Entity\EntityManager\EntityManagerBase.ahk #Include Volantis.Entity\EntityStorage\ConfigEntityStorage.ahk #Include Volantis.Entity\EntityStorage\EntityStorageBase.ahk +#Include Volantis.Entity\EntityStorage\NullEntityStorage.ahk #Include Volantis.Entity\EntityType\BasicEntityType.ahk #Include Volantis.Entity\EntityType\EntityTypeBase.ahk #Include Volantis.Entity\Event\EntityDataProcessorsEvent.ahk +#Include Volantis.Entity\Event\EntityDetectValuesEvent.ahk #Include Volantis.Entity\Event\EntityEvent.ahk #Include Volantis.Entity\Event\EntityFieldDefinitionsEvent.ahk #Include Volantis.Entity\Event\EntityFieldGroupsEvent.ahk #Include Volantis.Entity\Event\EntityLayersEvent.ahk +#Include Volantis.Entity\Event\EntityLayerSourcesEvent.ahk +#Include Volantis.Entity\Event\EntityListEvent.ahk +#Include Volantis.Entity\Event\EntityParentEvent.ahk #Include Volantis.Entity\Event\EntityReferenceEvent.ahk #Include Volantis.Entity\Event\EntityRefreshEvent.ahk -#Include Volantis.Entity\Event\EntityStorageEvent.ahk #Include Volantis.Entity\Event\EntityValidateEvent.ahk #Include Volantis.Entity\Events\EntityEvents.ahk #Include Volantis.Entity\Exception\EntityException.ahk @@ -230,6 +246,7 @@ #Include Volantis.Entity\Factory\EntityTypeFactory.ahk #Include Volantis.Entity\LayeredData\EntityData.ahk #Include Volantis.Entity\LayerSource\EntityStorageLayerSource.ahk +#Include Volantis.Entity\LayerSource\ParentEntityLayerSource.ahk #Include Volantis.Entity\Query\EntityQuery.ahk #Include Volantis.Entity\Validator\BasicValidator.ahk #Include Volantis.Entity\Validator\ValidatorBase.ahk diff --git a/Lib/Shared/Includes.test.ahk b/Lib/Shared/Includes.test.ahk index 2fe8682e..f157d4f8 100644 --- a/Lib/Shared/Includes.test.ahk +++ b/Lib/Shared/Includes.test.ahk @@ -1,5 +1,4 @@ ; Automatically-generated file. Manual edits will be overwritten. -#Include Modules\Auth\AuthInfo\AuthInfo.test.ahk #Include Volantis.App\App\AppBase.test.ahk #Include Volantis.Base\Event\EventBase.test.ahk #Include Volantis.Utility\Debugger\Debugger.test.ahk diff --git a/Lib/Shared/Modules/Auth/Auth.module.json b/Lib/Shared/Modules/Auth/Auth.module.json deleted file mode 100644 index 27e65909..00000000 --- a/Lib/Shared/Modules/Auth/Auth.module.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "module": { - "name": "Authentication", - "type": "AppModule", - "icon": "", - "category": "Web Services", - "tags": ["Launchpad", "LaunchpadBuilder"], - "description": "Authenticate with remote accounts, such as the Launchpad API", - "author": { - "name": "Ben McClure, Volantis Dev", - "url": "https://volantisdev.com" - }, - "website": "https://launchpad.games", - "version": "{{VERSION}}", - "appVersion": "", - "dependencies": [] - }, - "parameters": { - "config.auth_service": "" - }, - "services": { - "Auth": { - "class": "AuthService", - "arguments": ["@{App}", "@@config.auth_service", "@state.app"] - } - } -} diff --git a/Lib/Shared/Modules/Auth/AuthInfo/AuthInfo.ahk b/Lib/Shared/Modules/Auth/AuthInfo/AuthInfo.ahk deleted file mode 100644 index 3428b9e0..00000000 --- a/Lib/Shared/Modules/Auth/AuthInfo/AuthInfo.ahk +++ /dev/null @@ -1,40 +0,0 @@ -class AuthInfo { - isAuthenticated := false - secureData := Map() - persistentData := Map() - userIdField := "userId" - - Authenticated { - get => this.isAuthenticated - set => this.isAuthenticated := !!(value) - } - - UserId { - get => this.Get(this.userIdField) - } - - __New() { - - } - - Get(key) { - value := "" - - if (this.secureData.Has(key)) { - value := this.secureData[key] - } else if (this.persistentData.Has(key)) { - value := this.persistentData[key] - } - - return value - } - - Set(key, value, persist := false) { - mapObj := persist ? this.persistentData : this.secureData - mapObj[key] := value - } - - GetPersistentData() { - return this.persistentData - } -} diff --git a/Lib/Shared/Modules/Auth/AuthInfo/AuthInfo.test.ahk b/Lib/Shared/Modules/Auth/AuthInfo/AuthInfo.test.ahk deleted file mode 100644 index dfbcde79..00000000 --- a/Lib/Shared/Modules/Auth/AuthInfo/AuthInfo.test.ahk +++ /dev/null @@ -1,103 +0,0 @@ -class AuthInfoTest extends AppTestBase { - TestAuthenticated() { - authInfoObj := AuthInfo() - - authInfoObj.isAuthenticated := false - - this.AssertFalse( - authInfoObj.Authenticated, - "Test setting isAuthenticated to false" - ) - - authInfoObj.Authenticated := true - - this.AssertTrue( - authInfoObj.Authenticated, - "Test changing Authenticated to true" - ) - } - - TestUserId() { - authInfoObj := AuthInfo() - - this.AssertEquals( - "", - authInfoObj.UserId, - "Assert that user ID is blank initially" - ) - - userIds := [ - 123, - "456", - "1234-5678-9012-3456", - "r@nd0m" - ] - - for userId in userIds { - authInfoObj.Set(authInfoObj.userIdField, userId) - - this.AssertEquals( - userId, - authInfoObj.UserId, - "Test changing user ID to " . userId - ) - } - } - - TestGet() { - authInfoObj := AuthInfo() - - this.AssertEmpty( - authInfoObj.Get("nonexistantValue"), - "Getting a non-existent value returns an empty string" - ) - - authInfoObj.Set("testValue", "persistent", true) - - this.AssertEquals( - "persistent", - authInfoObj.Get("testValue"), - "Getting a persistent value is possible" - ) - - authInfoObj.Set("testValue", "overridden", false) - - this.AssertEquals( - "overridden", - authInfoObj.Get("testValue"), - "Getting a secure value overrides a persistent one" - ) - } - - TestSet() { - authInfoObj := AuthInfo() - - authInfoObj.Set("testValue", "persistent", true) - - this.AssertEquals( - "persistent", - authInfoObj.Get("testValue"), - "Setting a persistent value is possible" - ) - - authInfoObj.Set("testValue", "overridden", false) - - this.AssertEquals( - "overridden", - authInfoObj.Get("testValue"), - "Setting a secure value overrides a persistent one" - ) - } - - TestGetPersistentData() { - persistentData := Map("testValue", "persistent") - authInfoObj := AuthInfo() - authInfoObj.persistentData := persistentData - - this.AssertEquals( - persistentData, - authInfoObj.GetPersistentData(), - "GetPersistentData returns all persistent data" - ) - } -} diff --git a/Lib/Shared/Modules/Auth/AuthInfo/JwtAuthInfo.ahk b/Lib/Shared/Modules/Auth/AuthInfo/JwtAuthInfo.ahk deleted file mode 100644 index 57b560ca..00000000 --- a/Lib/Shared/Modules/Auth/AuthInfo/JwtAuthInfo.ahk +++ /dev/null @@ -1,42 +0,0 @@ -class JwtAuthInfo extends AuthInfo { - __New(userInfo) { - super.__New() - - this.Authenticated := false - - added := Map() - - if (userInfo.Has("user_id")) { - this.Authenticated := !!(userInfo["user_id"]) - this.Set("userId", userInfo["user_id"], true) - added["user_id"] := true - } - - if (userInfo.Has("refresh_token")) { - this.Set("refresh", userInfo["refresh_token"], true) - added["refresh_token"] := true - } - - if (userInfo.Has("expires_in")) { - timestamp := DateAdd(A_Now, userInfo["expires_in"], "S") - this.Set("expires", timestamp, true) - added["expires_in"] := true - } - - if (userInfo.Has("id_token")) { - this.Set("authToken", userInfo["id_token"]) - added["id_token"] := true - } - - if (userInfo.Has("access_token")) { - this.Set("accessToken", userInfo["access_token"]) - added["authToken"] := true - } - - for key, value in userInfo { - if (!added.Has(key) || !added[key]) { - this.Set(key, value) - } - } - } -} diff --git a/Lib/Shared/Modules/Auth/AuthProvider/AuthProviderBase.ahk b/Lib/Shared/Modules/Auth/AuthProvider/AuthProviderBase.ahk deleted file mode 100644 index 475bc81f..00000000 --- a/Lib/Shared/Modules/Auth/AuthProvider/AuthProviderBase.ahk +++ /dev/null @@ -1,37 +0,0 @@ -class AuthProviderBase { - __New(persistentData := "") { - - } - - Login() { - - } - - Logout(authInfoObj) { - - } - - NeedsRefresh(authInfoObj) { - - } - - RefreshAuthentication(authInfoObj) { - - } - - AddRefreshInfoToRequest(authInfoObj, httpReqObj) { - ; Add refresh token to the request - } - - AddLoginInfoToRequest(authToken, httpReqObj) { - ; Add auth info needed to complete login to the request - } - - AddAuthInfoToRequest(authInfoObj, httpReqObj) { - ; Add auth info from authInfoObj to httpReqObj as needed - } - - ExtractAuthInfoFromResponse(httpReqObj) { - - } -} diff --git a/Lib/Shared/Modules/Auth/AuthProvider/JwtAuthProvider.ahk b/Lib/Shared/Modules/Auth/AuthProvider/JwtAuthProvider.ahk deleted file mode 100644 index 5fde6ad2..00000000 --- a/Lib/Shared/Modules/Auth/AuthProvider/JwtAuthProvider.ahk +++ /dev/null @@ -1,150 +0,0 @@ -class JwtAuthProvider extends AuthProviderBase { - dataSourceObj := "" - authEndpointUrl := "" - webApiKey := "" - refreshPath := "token" - authToken := "" - refreshToken := "" - - __New(dataSourceObj, authEndpointUrl, webApiKey, persistentData := "") { - this.dataSourceObj := dataSourceObj - this.authEndpointUrl := authEndpointUrl - this.webApiKey := webApiKey - - if (persistentData != "" and persistentData.Has("authToken")) { - this.authToken := persistentData["authToken"] - } - } - - Login(isRetry := false) { - refreshToken := this.refreshToken - - if (!refreshToken) { - refreshToken := this.ShowLoginWindow() - - if (refreshToken) { - this.refreshToken := refreshToken - } - } - - userInfo := "" - - if (refreshToken) { - url := this.GetAuthUrl(this.refreshPath) - request := WinHttpReq(url) - payload := Map("grant_type", "refresh_token", "refresh_token", refreshToken) - response := request.Send("POST", payload) - - if (request.GetStatusCode() == 200) { - userInfo := this.ExtractAuthInfoFromResponse(request) - } else { - this.refreshToken := "" - - if (isRetry) { - ; TODO: Log user out instead of throwing an exception - throw OperationFailedException("Login failed.") - } else { - return this.Login(true) - } - } - } - - return userInfo - } - - GetAuthUrl(path) { - return this.authEndpointUrl . "/" . path . "?key=" . this.webApiKey - } - - ShowLoginWindow() { - return "" - } - - Logout(authInfoObj) { - this.authToken := "" - this.refreshToken := "" - return true - } - - RefreshAuthentication(authInfoObj) { - refreshToken := authInfoObj.Get("refresh") - - if (refreshToken) { - this.refreshToken := refreshToken - } - - return this.Login() - } - - IsAuthenticationValid(authInfoObj) { - isValid := false - - if (authInfoObj && authInfoObj.Authenticated) { - isValid := !this.IsAuthenticationExpired(authInfoObj) - } - - return isValid - } - - AddAuthInfoToRequest(authInfoObj, httpReqObj) { - authToken := authInfoObj.Get("authToken") - - if (authToken) { - this.AddAuthorizationHeader(authToken, httpReqObj) - } - } - - AddAuthorizationHeader(bearerToken, httpReqObj) { - if (bearerToken) { - httpReqObj.requestHeaders["Authorization"] := "Bearer " . bearerToken - } - } - - ExtractAuthInfoFromResponse(httpReqObj) { - responseData := Trim(httpReqObj.GetResponseData()) - - userInfo := Map() - - if (responseData) { - data := JsonData() - userInfo := data.FromString(&responseData) - } - - return JwtAuthInfo(userInfo) - } - - IsAuthenticationExpired(authInfoObj) { - expired := true - - if (authInfoObj and authInfoObj.Authenticated) { - expires := authInfoObj.Get("expires") - - if (expires) { - diff := DateDiff(A_Now, expires, "S") - expired := (diff >= 0) - } - } - - return expired - } - - NeedsRefresh(authInfoObj) { - needsRefresh := false - thresholdSeconds := -600 - - if (!this.IsAuthenticationValid(authInfoObj)) { - needsRefresh := true - } else { - expires := authInfoObj.Get("expires") - - if (expires) { - diff := DateDiff(A_Now, expires, "S") - needsRefresh := (diff >= thresholdSeconds) - } else { - needsRefresh := true - } - } - - return needsRefresh - } -} diff --git a/Lib/Shared/Modules/LaunchpadApi/AuthProvider/LaunchpadApiAuthProvider.ahk b/Lib/Shared/Modules/LaunchpadApi/AuthProvider/LaunchpadApiAuthProvider.ahk deleted file mode 100644 index ff44127d..00000000 --- a/Lib/Shared/Modules/LaunchpadApi/AuthProvider/LaunchpadApiAuthProvider.ahk +++ /dev/null @@ -1,30 +0,0 @@ -class LaunchpadApiAuthProvider extends JwtAuthProvider { - app := "" - - __New(app, stateObj) { - this.app := app - - persistentData := "" - - if (stateObj) { - state := stateObj.GetState() - - if (state.Has("Authentication")) { - persistentData := state["Authentication"] - } - } - - authEndpointUrl := "https://securetoken.googleapis.com/v1" - webApiKey := "AIzaSyCbwzOWJjTft77P96dV5VB3dAx9TjdDowQ" - super.__New(app.Service("manager.data_source").GetDefaultDataSource(), authEndpointUrl, webApiKey, persistentData) - } - - ShowLoginWindow() { - return this.app.Service("manager.gui").Dialog(Map("type", "LoginWindow")) - } - - ExtractAuthInfoFromResponse(httpReqObj) { - authInfoObj := super.ExtractAuthInfoFromResponse(httpReqObj) - return authInfoObj - } -} diff --git a/Lib/Shared/Modules/LaunchpadApi/DataSource/ApiDataSource.ahk b/Lib/Shared/Modules/LaunchpadApi/DataSource/ApiDataSource.ahk deleted file mode 100644 index bf0cf3aa..00000000 --- a/Lib/Shared/Modules/LaunchpadApi/DataSource/ApiDataSource.ahk +++ /dev/null @@ -1,142 +0,0 @@ -class ApiDataSource extends DataSourceBase { - endpointUrl := "" - app := "" - - __New(app, cacheManager, cacheName, endpointUrl) { - this.app := app - InvalidParameterException.CheckTypes("ApiDataSource", "endpointUrl", endpointUrl, "", "cacheManager", cacheManager, "CacheManager") - this.endpointUrl := endpointUrl - super.__New(cacheManager, cacheName) - } - - ItemExists(path) { - return super.ItemExists(path) || this.ItemExistsInApi(path) - } - - ItemExistsInApi(path) { - exists := (this.cache.ItemExists(path) && !this.cache.ItemNeedsUpdate(path)) - - if (!exists) { - request := this.SendHttpReq(path, "HEAD") - - exists := (request.GetStatusCode() == 200) - - if (!exists) { - this.cache.SetNotFound(path) - } - } - - return exists - } - - GetHttpReq(path, private := false) { - request := WinHttpReq(this.GetRemoteLocation(path)) - - if (private) { - request.requestHeaders["Cache-Control"] := "no-cache" - - if (this.app.Config["api_authentication"] && this.app.Services.Has("Api")) { - this.app.Service("Auth").AlterApiRequest(request) - } - } - - return request - } - - SendHttpReq(path, method := "GET", data := "", private := false) { - request := this.GetHttpReq(path, private) - returnCode := request.Send(method, data) - return request - } - - GetRemoteLocation(path) { - return this.endpointUrl . "/" . path - } - - RetrieveItem(path, private := false, maxCacheAge := "") { - if (maxCacheAge == "") { - maxCacheAge := this.maxCacheAge - } - - exists := (!private && this.cache.ItemExists(path) && !this.cache.ItemNeedsUpdate(path, maxCacheAge)) - - if (!exists) { - request := this.SendHttpReq(path, "GET", "", private) - - if (request.GetStatusCode() != 200) { - return "" - } - - responseBody := Trim(request.GetResponseData()) - - if (responseBody == "") { - return "" - } - - this.cache.WriteItem(path, responseBody) - } - - return this.cache.ItemExists(path) ? this.cache.ReadItem(path) : "" - } - - GetStatus() { - path := "status" - statusExpire := 5 ;60 - - status := Map("authenticated", false, "email", "", "photo", "") - - if (this.app.Config["api_authentication"] && this.app.Service("Auth").IsAuthenticated()) { - statusResult := this.ReadItem(path, true) - - if (statusResult) { - json := JsonData() - status := json.FromString(&statusResult) - } - } - - return status - } - - GetExt(path) { - - } - - Open() { - Run(this.endpointUrl) - } - - ChangeApiEndpoint(existingEndpoint := "", owner := "", parent := "") { - if (existingEndpoint == "") { - existingEndpoint := this.app.Config["api_endpoint"] - } - - ownerOrParent := "" - - if (parent) { - ownerOrParent := parent - } else if (owner) { - ownerOrParent := owner - } - - apiEndpointUrl := this.app.Service("manager.gui").Dialog(Map( - "type", "SingleInputBox", - "title", "API Endpoint URL", - "text", "Enter the base URL of the API endpoint you would like Launchpad to connect to. Leave blank to revert to the default.", - "defaultValue", existingEndpoint, - "ownerOrParent", ownerOrParent, - "child", !!(parent) - )) - - if (apiEndpointUrl != existingEndpoint) { - this.app.Config["api_endpoint"] := apiEndpointUrl - apiEndpointUrl := this.app.Config["api_endpoint"] - - if (apiEndpointUrl != existingEndpoint) { - this.endpointUrl := apiEndpointUrl - this.cache.FlushCache() - } - } - - return apiEndpointUrl - } -} diff --git a/Lib/Shared/Modules/LaunchpadApi/EventSubscriber/LaunchpadApiSubscriber.ahk b/Lib/Shared/Modules/LaunchpadApi/EventSubscriber/LaunchpadApiSubscriber.ahk new file mode 100644 index 00000000..1d349909 --- /dev/null +++ b/Lib/Shared/Modules/LaunchpadApi/EventSubscriber/LaunchpadApiSubscriber.ahk @@ -0,0 +1,24 @@ +class LaunchpadApiSubscriber extends EventSubscriberBase { + GetEventSubscribers() { + return Map( + WebServicesEvents.ENTITY_DATA_PARAMS, [ + ObjBindMethod(this, "EntityDataParams") + ] + ) + } + + EntityDataParams(event, extra, eventName, hwnd) { + if (HasProp(event.Entity, "isWebServiceEntity") && event.Entity.isWebServiceEntity) { + return + } + + ; TODO figure out how to access these values while the data layers are still being loaded + if (event.WebService["id"] == "launchpad_api") { + if (HasBase(event.Entity, LauncherEntity.Prototype)) { + event.Params["platformId"] := "Blizzard" ;event.Entity["Platform"]["id"] + } else if (HasBase(event.Entity, LaunchProcessEntity.Prototype)) { + event.Params["platformId"] := "Blizzard" ;event.Entity.ParentEntity["Platform"]["id"] + } + } + } +} diff --git a/Lib/Shared/Modules/LaunchpadApi/LaunchpadApi.module.json b/Lib/Shared/Modules/LaunchpadApi/LaunchpadApi.module.json index bd3b74bc..3bb1307d 100644 --- a/Lib/Shared/Modules/LaunchpadApi/LaunchpadApi.module.json +++ b/Lib/Shared/Modules/LaunchpadApi/LaunchpadApi.module.json @@ -12,30 +12,153 @@ "website": "https://launchpad.games", "version": "{{VERSION}}", "appVersion": "", - "dependencies": ["Auth"] + "dependencies": ["WebServices"] }, - "services": { - "data_source.api": { - "class": "ApiDataSource", - "arguments": ["@{App}", "@manager.cache", "api", "@@config.api_endpoint"] + "parameters": { + "app.supports_update_check": true, + "web_services.adapters.launchpad_api.account_info": { + "dataType": "account_info", + "requestPath": "/status", + "cacheResponse": false, + "readAuth": true, + "dataMap": { + "email": "account" + } + }, + "web_services.adapters.launchpad_api.hello": { + "dataType": "availability_check", + "requestPath": "/hello", + "cacheResponse": false + }, + "web_services.adapters.launchpad_api.game_process_launcher_defaults": { + "dataType": "entity_data", + "requestPath": "/games/{id}", + "dataSelector": "gameProcessDefaults", + "entityType": "game_process", + "requiredParams": ["id"], + "tags": ["defaults"], + "weight": 5 + }, + "web_services.adapters.launchpad_api.game_process_platform_defaults": { + "dataType": "entity_data", + "requestPath": "/platforms/{platformId}", + "dataSelector": "data.gameProcessDefaults", + "entityType": "game_process", + "requiredParams": ["platformId"], + "tags": ["defaults"], + "weight": 2 + }, + "web_services.adapters.launchpad_api.launcher_defaults": { + "dataType": "entity_data", + "requestPath": "/games/{id}", + "dataSelector": "defaults", + "entityType": "launcher", + "requiredParams": ["id"], + "tags": ["defaults"], + "weight": 5 + }, + "web_services.adapters.launchpad_api.launcher_list": { + "dataType": "entity_list", + "requestPath": "/game-keys", + "entityType": "launcher" + }, + "web_services.adapters.launchpad_api.launcher_lookup": { + "dataType": "entity_lookup", + "requestPath": "/lookup/{id}/{platformId}", + "dataSelector": "id", + "entityType": "launcher", + "requiredParams": ["id"] + }, + "web_services.adapters.launchpad_api.launcher_platform_defaults": { + "dataType": "entity_data", + "requestPath": "/platforms/{platformId}", + "dataSelector": "data.launcherDefaults", + "entityType": "launcher", + "requiredParams": ["platformId"], + "tags": ["defaults"], + "weight": 2 + }, + "web_services.adapters.launchpad_api.launcher_process_launcher_defaults": { + "dataType": "entity_data", + "requestPath": "/games/{id}", + "dataSelector": "launcherProcessDefaults", + "entityType": "launcher_process", + "requiredParams": ["id"], + "tags": ["defaults"], + "weight": 5 + }, + "web_services.adapters.launchpad_api.launcher_process_platform_defaults": { + "dataType": "entity_data", + "requestPath": "/platforms/{platformId}", + "dataSelector": "data.launcherProcessDefaults", + "entityType": "launcher_process", + "requiredParams": ["platformId"], + "tags": ["defaults"], + "weight": 2 }, - "auth_provider.launchpad_api": { - "class": "LaunchpadApiAuthProvider", - "arguments": ["@{App}", "@state.app"] + "web_services.adapters.launchpad_api.platform_defaults": { + "dataType": "entity_data", + "requestPath": "/platforms/{id}", + "dataSelector": "data.defaults", + "entityType": "platform", + "requiredParams": ["id"], + "tags": ["defaults"] }, - "cache_state.api": { + "web_services.adapters.launchpad_api.platform_list": { + "dataType": "entity_list", + "requestPath": "/platforms", + "entityType": "platform" + }, + "web_services.adapters.launchpad_api.release_info": { + "dataType": "release_info", + "requestPath": "/release-info/{version}", + "cacheMaxAge": 1800 + }, + "web_services.adapters.launchpad_api.submit_error": { + "dataType": "error_submission", + "requestPath": "/submit-error", + "cacheResponse": false, + "readAllow": false, + "createAllow": true, + "createAuth": false + }, + "web_services.adapters.launchpad_api.submit_feedback": { + "dataType": "feedback_submission", + "requestPath": "/submit-feedback", + "cacheResponse": false, + "readAllow": false, + "createAllow": true, + "createAuth": false + }, + "web_services.providers.launchpad_api": { + "name": "Launchpad API", + "EndpointUrl": "https://api.launchpad.games/v1", + "AuthenticationEndpointUrl": "https://securetoken.googleapis.com/v1", + "AuthenticationRefreshPath": "token", + "IconSrc": "logo", + "SupportsAuthentication": true, + "Authenticator": "jwt", + "AppKey": "AIzaSyCbwzOWJjTft77P96dV5VB3dAx9TjdDowQ", + "LoginWindow": "LaunchpadLoginWindow" + }, + "web_services.services.launchpad_api": { + "name": "Launchpad API", + "Provider": "launchpad_api" + } + }, + "services": { + "cache_state.launchpad_api": { "class": "CacheState", "arguments": ["@{App}", "@@config.cache_dir", "API.json"] }, - "cache.api": { + "cache.launchpad_api": { "class": "FileCache", - "arguments": ["@{App}", "@cache_state.api", "@@config.cache_dir", "API"] + "arguments": ["@{App}", "@cache_state.launchpad_api", "@@config.cache_dir", "API"] + }, + "event_subscriber.launchpad_api": { + "class": "LaunchpadApiSubscriber", + "arguments": ["@{}"], + "tags": ["event_subscriber"] } - }, - "parameters": { - "config.data_source_key": "api", - "config.api_endpoint": "https://api.launchpad.games/v1", - "config.api_authentication": true, - "config.api_auto_login": false } } diff --git a/Lib/Shared/Modules/WebServices/ComponentManager/WebServiceAdapterManager.ahk b/Lib/Shared/Modules/WebServices/ComponentManager/WebServiceAdapterManager.ahk new file mode 100644 index 00000000..b4e81fd3 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/ComponentManager/WebServiceAdapterManager.ahk @@ -0,0 +1,287 @@ +class WebServiceAdapterManager { + container := "" + parameterPrefix := "" + adapterFactory := "" + entityTypeMgr := "" + eventMgr := "" + adapters := Map() + + __New(container, parameterPrefix, adapterFactory, entityTypeMgr, eventMgr) { + this.container := container + this.parameterPrefix := parameterPrefix + this.adapterFactory := adapterFactory + this.entityTypeMgr := entityTypeMgr + this.eventMgr := eventMgr + } + + AdapterRequest(params, filters, operation := "read", multiple := false, webService := "") { + if (!params) { + params := Map() + } + + if (!filters) { + filters := Map() + } + + if (Type(filters) == "String") { + filters := Map("dataType", filters) + } + + results := Map() + + for adapterKey, adapter in this.GetAdapters(filters, operation, 0, webService) { + result := adapter.SendRequest(operation, params) + + if (result) { + if (!multiple) { + results := result + + break + } + + results[adapterKey] := result + } + + if (IsNumber(multiple) && results.Count >= multiple) { + break + } + } + + return results + } + + HasAdapters(filters := "", operation := "", webService := "") { + return !!(this.GetAdapterIds(filters, operation, 1, webService).Length) + } + + GetAdapters(filters := "", operation := "", limit := 0, webService := "") { + adapterIds := this.GetAdapterIds(filters, operation, limit, webService) + + adapters := Map() + + for , adapterId in adapterIds { + adapters[adapterId] := this.GetAdapter(adapterId) + } + + return adapters + } + + GetAdapter(id) { + adapter := "" + + if (this.adapters.Has(id)) { + adapter := this.adapters[id] + } + + if (!adapter && InStr(id, ".")) { + idParts := StrSplit(id, ".") + webServiceId := idParts[1] + adapterKey := idParts[2] + + webService := this.entityTypeMgr.GetManager("web_service")[webServiceId] + + if (webService["Enabled"]) { + param := this.parameterPrefix . webService["Provider"]["id"] . "." . adapterKey + + if (this.container.HasParameter(param)) { + adapter := this.adapterFactory.CreateWebServiceAdapter(webService, this.container.GetParameter(param)) + this.adapters[id] := adapter + } + } + } + + return adapter + } + + HasAdapter(id) { + exists := this.adapters.Has(id) + + if (!exists) { + idParts := StrSplit(id, ".") + webServiceId := idParts[1] + adapterKey := idParts[2] + webService := this.entityTypeMgr.GetManager("web_service")[webServiceId] + param := this.parameterPrefix . webService["Provider"]["id"] . "." . id + exists := this.container.HasParameter(param) + } + + return exists + } + + GetAdapterIds(filters := "", operation := "", limit := 0, webService := "") { + if (!filters) { + filters := Map() + } + + if (Type(filters) == "String") { + filters := Map("dataType", filters) + } + + adapterIds := [] + weights := this._getFilterWeights(filters) + + for webServiceId, webService in this._getWebServicesForOperation(webService) { + providerId := webService["Provider"]["id"] + paramKey := "web_services.adapters." . providerId + + if (this.container.HasParameter(paramKey)) { + adapterData := this.container.GetParameter(this.parameterPrefix . providerId) + + for weightIndex, weight in weights { + filters["weight"] := weight + + for key, definition in adapterData { + adapterId := webServiceId . "." . key + adapter := this.GetAdapter(adapterId) + definition := adapter.definition + include := (!operation || adapter.SupportsOperation(operation)) + + if (include) { + for filterKey, filterVal in filters { + if (!definition.Has(filterKey)) { + include := false + + break + } + + include := this._filterValue(definition[filterKey], filterVal) + + if (!include) { + break + } + } + } + + if (include) { + adapterIds.Push(adapterId) + + if (limit && adapterIds.Length >= limit) { + break 2 + } + } + } + } + } + } + + return adapterIds + } + + _getWebServicesForOperation(webService) { + webServices := "" + + if (webService) { + webServices := Type(webService == "String") ? Map(webService["id"], webService) : webService + } else { + webServices := this.entityTypeMgr.GetManager("web_service") + .EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Execute() + } + + return webServices + } + + _getFilterWeights(filters) { + weights := filters.Has("weight") + ? filters["weight"] + : "" + + if (!weights) { + weights := [] + + startingWeight := -10 + maxWeight := 10 + + Loop (maxWeight - startingWeight) { + weights.Push(startingWeight + A_Index - 1) + } + } + + if (Type(weights) == "String") { + weights := [weights] + } + + return weights + } + + _filterArrayValues(definitionArray, filterArray) { + include := !filterArray || !!definitionArray + + if (include) { + if (Type(filterArray) == "String") { + filterArray := [filterArray] + } + + if (Type(definitionArray) == "String") { + definitionArray := [definitionArray] + } + + for , val in filterArray { + definitionHasVal := false + + for , definitionVal in definitionArray { + definitionHasVal := this._filterValue(definitionVal, val) + + if (definitionVal == val) { + definitionHasVal := true + + break + } + } + + if (!definitionHasVal) { + include := false + + break + } + } + } + + return include + } + + _filterMapValues(definitionMap, filterMap) { + include := !filterMap || !!definitionMap + + if (include) { + if (Type(filterMap) == "String") { + filterMap := [filterMap] + } + + if (Type(definitionMap) == "String") { + definitionMap := [definitionMap] + } + + for key, val in filterMap { + exists := definitionMap.Has(key) + + if (exists) { + exists := this._filterValue(definitionMap[key], val) + } + + if (!exists) { + include := false + + break + } + } + } + + return include + } + + _filterValue(definitionVal, filterVal) { + include := false + + if (Type(filterVal) == "Array") { + include := this._filterArrayValues(definitionVal, filterVal) + } else if (Type(filterVal) == "Map") { + include := this._filterMapValues(definitionVal, filterVal) + } else { + include := (definitionVal == filterVal) + } + + return include + } +} diff --git a/Lib/Shared/Modules/WebServices/Entity/WebServiceEntity.ahk b/Lib/Shared/Modules/WebServices/Entity/WebServiceEntity.ahk new file mode 100644 index 00000000..d0caf51b --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Entity/WebServiceEntity.ahk @@ -0,0 +1,309 @@ +class WebServiceEntity extends FieldableEntity { + cacheObj := "" + stateObj := "" + persistentStateObj := "" + statusIndicators := [] + isWebServiceEntity := true + + Authenticated { + get => this.IsAuthenticated() + } + + UserId { + get => this.AuthData["user_id"] + } + + AuthData[key] { + get => this.GetAuthData(key, true) + set => this.SetAuthData(key, value) + } + + __New(id, entityTypeId, container, cacheObj, stateObj, persistentStateObj, fieldFactory, widgetFactory, eventMgr, storageObj, idSanitizer, autoLoad := true, parentEntity := "", parentEntityStorage := false) { + this.cacheObj := cacheObj + this.stateObj := stateObj + this.persistentStateObj := persistentStateObj + + super.__New(id, entityTypeId, container, fieldFactory, widgetFactory, eventMgr, storageObj, idSanitizer, autoLoad, parentEntity, parentEntityStorage) + } + + static Create(container, eventMgr, id, entityTypeId, storageObj, idSanitizer, autoLoad := true, parentEntity := "", parentEntityStorage := false) { + className := this.Prototype.__Class + + return %className%( + id, + entityTypeId, + container, + container.Get("cache.web_services"), + container.Get("state.web_services_tmp"), + container.Get("state.web_services"), + container.Get("entity_field_factory." . entityTypeId), + container.Get("entity_widget_factory." . entityTypeId), + eventMgr, + storageObj, + idSanitizer, + autoLoad, + parentEntity, + parentEntityStorage + ) + } + + BaseFieldDefinitions() { + definitions := super.BaseFieldDefinitions() + + if (this.idVal == "launchpad_api" && definitions.Has("name")) { + definitions["name"]["editable"] := false + } + + definitions["Provider"] := Map( + "type", "entity_reference", + "entityType", "web_service_provider", + "required", true, + "editable", false + ) + + definitions["AutoLogin"] := Map( + "type", "boolean", + "description", "Automatically authenticate with this service when Launchpad starts.", + "required", false, + "default", (this.idVal == "launchpad_api") + ) + + definitions["Enabled"] := Map( + "type", "boolean", + "required", false, + "default", true + ) + + definitions["StatusIndicator"] := Map( + "type", "boolean", + "required", false, + "default", (this.idVal == "launchpad_api") + ) + + definitions["StatusIndicatorExpanded"] := Map( + "type", "boolean", + "required", false, + "default", (this.idVal == "launchpad_api") + ) + + definitions["ResponseCache"] := Map( + "type", "boolean", + "required", false, + "default", true + ) + + definitions["ResponseCacheDefaultExpireSeconds"] := Map( + "title", "Response Cache - Default Expiration (seconds)", + "type", "string", + "required", false, + "default", 3600 + ) + + return definitions + } + + GetStatusIndicators() { + return this.statusIndicators + } + + AddStatusIndicator(statusIndicatorCtl) { + this.statusIndicators.Push(statusIndicatorCtl) + } + + UpdateStatusIndicators() { + for , statusIndicatorCtl in this.statusIndicators { + statusIndicatorCtl.UpdateStatusIndicator() + } + } + + IsAuthenticated() { + isAuthenticated := false + + if (this["Provider"] && this["Provider"]["SupportsAuthentication"]) { + isAuthenticated := this["Provider"]["Authenticator"].IsAuthenticated(this) + } + + return isAuthenticated + } + + Login() { + if (this["Provider"] && this["Provider"]["SupportsAuthentication"]) { + this["Provider"]["Authenticator"].Login(this) + } + } + + Logout() { + if (this["Provider"] && this["Provider"]["SupportsAuthentication"]) { + this["Provider"]["Authenticator"].Logout(this) + } + } + + Request(path, method := "", data := "", useAuthentication := -1, cacheResponse := true) { + if (!method) { + method := this["Provider"]["DefaultMethod"] + } + + if (useAuthentication == -1) { + useAuthentication := this["Provider"]["AuthenticateRequestsByDefault"] + } + + return BasicWebServiceRequest(this.eventMgr, this, this.cacheObj, method, path, data, useAuthentication, cacheResponse) + } + + GetAuthData(key := "", includePersistent := true) { + val := this._getStateData(this.stateObj, key) + + if (!val && includePersistent) { + val := this._getStateData(this.persistentStateObj, key) + } + + return val + } + + SetAuthData(keyOrMap, value, persist := false) { + result := this._setStateData(this.stateObj, keyOrMap, value) + + if (persist) { + this._setStateData(this.persistentStateObj, keyOrMap, value) + } + + return this + } + + ResetAuthData(newData := "", persist := false) { + if (!newData) { + newData := Map() + } + + if (!newData.Has("authenticated")) { + newData["authenticated"] := false + } + + this._createStateParents(this.stateObj) + this.stateObj.State["WebServices"][this.Id]["AuthData"] := newData + this.stateObj.SaveState() + + if (persist) { + this._createStateParents(this.persistentStateObj) + this.persistentStateObj.State["WebServices"][this.Id]["AuthData"] := Map( + "authenticated", newData["authenticated"] + ) + } + + return this + } + + DeleteAuthData(key, persist := false) { + this._deleteStateData(this.stateObj, key) + + if (persist) { + this._deleteStateData(this.persistentStateObj, key) + } + + return this + } + + _getStateData(stateObj, key := "") { + save := this._createStateParents(stateObj) + + if (save) { + stateObj.SaveState() + } + + authData := stateObj.State["WebServices"][this.Id]["AuthData"] + + if (key) { + authData := (authData.Has(key) ? authData[key] : "") + } + + return authData + } + + _setStateData(stateObj, key, value) { + this._createStateParents(stateObj) + stateObj.State["WebServices"][this.Id]["AuthData"][key] := value + stateObj.SaveState() + + return this + } + + _deleteStateData(stateObj, key) { + created := this._createStateParents(stateObj) + save := created + + if (!created) { + parent := this._getStateData(stateObj) + + if (HasBase(parent, Map.Prototype) && parent.Has(key)) { + parent.Delete(key) + save := true + } + } + + if (save) { + stateObj.SaveState() + } + + return this + } + + _createStateParents(stateObj) { + modified := false + + if (!stateObj.State.Has("WebServices")) { + stateObj.State["WebServices"] := Map() + modified := true + } + + if (!stateObj.State["WebServices"].Has(this.Id)) { + stateObj.State["WebServices"][this.Id] := Map() + modified := true + } + + if (!stateObj.State["WebServices"][this.Id].Has("AuthData")) { + stateObj.State["WebServices"][this.Id]["AuthData"] := Map() + modified := true + } + + return modified + } + + GetStatusInfo() { + ; @todo fix this data + statusText := "Not logged in" + imgPath := "" + email := "" + + if (this.Authenticated) { + email := this.AuthData["email"] + + if (email) { + statusText := email + } else { + statusText := "Logged in" + } + + imgPath := this.AuthData["photo"] + + if (SubStr(imgPath, 1, 4) == "http") { + cachePath := "account--profile.jpg" + imgPath := this.app["manager.cache"]["file"].GetCachedDownload(cachePath, imgPath) + } + } + + return Map("name", statusText, "email", email, "photo", imgPath) + } + + ShowAccountDetails() { + accountResult := this.container["manager.gui"].Dialog(Map( + "type", "AccountInfoWindow", + "ownerOrParent", this.guiId, + "child", true, + "webService", this + )) + + if (accountResult == "OK" || accountResult == "Logout" || accountResult == "Login") { + this.UpdateStatusIndicators() + } + } +} diff --git a/Lib/Shared/Modules/WebServices/Entity/WebServiceProviderEntity.ahk b/Lib/Shared/Modules/WebServices/Entity/WebServiceProviderEntity.ahk new file mode 100644 index 00000000..5c54ae3a --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Entity/WebServiceProviderEntity.ahk @@ -0,0 +1,92 @@ +class WebServiceProviderEntity extends FieldableEntity { + isWebServiceEntity := true + + BaseFieldDefinitions() { + definitions := super.BaseFieldDefinitions() + + definitions["EndpointUrl"] := Map( + "default", "", + "required", true + ) + + definitions["AuthenticationEndpointUrl"] := Map( + "default", "", + "required", false + ) + + definitions["AuthenticationRefreshPath"] := Map( + "default", "", + "required", false + ) + + definitions["IconSrc"] := Map( + "type", "icon_file", + "default", "webhook", + "required", true + ) + + definitions["SupportsAuthentication"] := Map( + "type", "boolean", + "required", false, + "default", false + ) + + definitions["Authenticator"] := Map( + "type", "service_reference", + "servicePrefix", "web_services_authenticator.", + "default", "", + "required", false + ) + + definitions["DefaultMethod"] := Map( + "default", "GET", + "required", false + ) + + definitions["AuthenticateRequestsByDefault"] := Map( + "type", "boolean", + "default", false, + "required", false + ) + + definitions["LoginWindow"] := Map( + "default", "", + "required", false + ) + + definitions["AppKey"] := Map( + "default", "", + "required", false + ) + + return definitions + } + + Url(path, queryParams := "") { + if (InStr(path, "/") != 1) { + path := "/" . path + } + + return UrlObj(this["EndpointUrl"] . path) + .AddQueryParams(queryParams) + } + + FullPath(path) { + url := this.Url(path) + return url.Path + } + + GetAuthenticationRefreshUrl(queryParams := "") { + endpointUrl := this["AuthenticationEndpointUrl"] + ? this["AuthenticationEndpointUrl"] + : this["EndpointUrl"] + refreshPath := this["AuthenticationRefreshPath"] + + if (refreshPath && InStr(refreshPath, "/") != 1) { + refreshPath := "/" . refreshPath + } + + return UrlObj(endpointUrl . refreshPath) + .AddQueryParams(queryParams) + } +} diff --git a/Lib/Shared/Modules/WebServices/Event/WebServicesEntityDataParamsEvent.ahk b/Lib/Shared/Modules/WebServices/Event/WebServicesEntityDataParamsEvent.ahk new file mode 100644 index 00000000..ad6ba482 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Event/WebServicesEntityDataParamsEvent.ahk @@ -0,0 +1,20 @@ +class WebServicesEntityDataParamsEvent extends EntityEvent { + _webService := "" + _params := "" + + __New(eventName, entityTypeId, entityObj, webService, params) { + this._webService := webService + this._params := params + + super.__New(eventName, entityTypeId, entityObj) + } + + WebService { + get => this._webService + } + + Params { + get => this._params + set => this._params := value + } +} diff --git a/Lib/Shared/Modules/WebServices/Event/WebServicesRequestEvent.ahk b/Lib/Shared/Modules/WebServices/Event/WebServicesRequestEvent.ahk new file mode 100644 index 00000000..d52a8c42 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Event/WebServicesRequestEvent.ahk @@ -0,0 +1,17 @@ +class WebServicesRequestEvent extends EventBase { + _requestObj := "" + + __New(eventName, requestObj) { + this._requestObj := requestObj + + super.__New(eventName) + } + + Request { + get => this.requestObj + } + + HttpReq { + get => this.Request.GetHttpReq() + } +} diff --git a/Lib/Shared/Modules/WebServices/Event/WebServicesResponseEvent.ahk b/Lib/Shared/Modules/WebServices/Event/WebServicesResponseEvent.ahk new file mode 100644 index 00000000..80010b44 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Event/WebServicesResponseEvent.ahk @@ -0,0 +1,20 @@ +class WebServicesResponseEvent extends EventBase { + _requestObj := "" + _responseObj := "" + + __New(eventName, requestObj, responseObj) { + this._requestObj := requestObj + this._responseObj := responseObj + + super.__New(eventName) + } + + Request { + get => this._requestObj + } + + Response { + get => this._responseObj + set => this._responseObj := value + } +} diff --git a/Lib/Shared/Modules/WebServices/EventSubscriber/WebServicesEventSubscriber.ahk b/Lib/Shared/Modules/WebServices/EventSubscriber/WebServicesEventSubscriber.ahk new file mode 100644 index 00000000..3e7a0598 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/EventSubscriber/WebServicesEventSubscriber.ahk @@ -0,0 +1,322 @@ +class WebServicesEventSubscriber extends EventSubscriberBase { + adapterMgr := "" + + __New(container, adapterMgr) { + this.adapterMgr := adapterMgr + + super.__New(container) + } + + GetEventSubscribers() { + return Map( + Events.APP_POST_STARTUP, [ + ObjBindMethod(this, "OnPostStartup") + ], + Events.APP_MENU_ITEMS_LATE, [ + ObjBindMethod(this, "OnMenuItemsLate") + ], + Events.APP_MENU_PROCESS_RESULT, [ + ObjBindMethod(this, "OnMenuProcessResult") + ], + EntityEvents.ENTITY_DATA_LAYERS, [ + ObjBindMethod(this, "EntityDataLayers") + ], + EntityEvents.ENTITY_LAYER_SOURCES, [ + ObjBindMethod(this, "EntityLayerSources") + ], + Events.APP_GET_RELEASE_INFO, [ + ObjBindMethod(this, "GetReleaseInfo") + ], + EntityEvents.ENTITY_FIELD_GROUPS, [ + ObjBindMethod(this, "EntityFieldGroups") + ], + EntityEvents.ENTITY_FIELD_DEFINITIONS, [ + ObjBindMethod(this, "EntityFieldDefinitions") + ], + EntityEvents.ENTITY_DETECT_VALUES, [ + ObjBindMethod(this, "EntityDetectValues") + ], + EntityEvents.ENTITY_LIST_ENTITIES, [ + ObjBindMethod(this, "ListEntities") + ] + ) + } + + OnPostStartup(event, extra, eventName, hwnd) { + webServices := this.container["entity_manager.web_service"] + .EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Condition(IsTrueCondition(), "AutoLogin") + .Execute() + + for key, webService in webServices { + webService.Login() + } + } + + OnMenuItemsLate(event, extra, eventName, hwnd) { + event.MenuItems.Push(Map( + "label", "Provide &Feedback", + "name", "ProvideFeedback" + )) + } + + OnMenuProcessResult(event, extra, eventName, hwnd) { + if (!event.IsFinished) { + if (event.Result == "ProvideFeedback") { + this.container["manager.gui"].Dialog(Map("type", "FeedbackWindow")) + event.IsFinished := true + } + } + } + + _getEntityLayerKey(webService, adapterId) { + return webService["id"] . "." . adapterId + } + + EntityDataLayers(event, extra, eventName, hwnd) { + if (HasProp(event.Entity, "isWebServiceEntity") && event.Entity.isWebServiceEntity) { + return + } + + webServices := this.container["entity_manager.web_service"] + .EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Execute() + + for webServiceId, webService in webServices { + adapterIds := this.adapterMgr.GetAdapterIds(Map( + "dataType", "entity_data", + "entityType", event.EntityTypeId, + "tags", "defaults" + ), "", 0, webService) + + for , adapterId in adapterIds { + adapter := this.adapterMgr.GetAdapter(adapterId) + layerExists := false + layerKey := this._getEntityLayerKey(webService, adapterId) + + for , existingLayerKey in event.Layers { + if (existingLayerKey == layerKey) { + layerExists := true + + break + } + } + + if (!layerExists) { + event.Layers.Push(layerKey) + } + } + } + } + + EntityLayerSources(event, extra, eventName, hwnd) { + if (HasProp(event.Entity, "isWebServiceEntity") && event.Entity.isWebServiceEntity) { + return + } + + webServices := this.container["entity_manager.web_service"] + .EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Execute() + + for webServiceId, webService in webServices { + adapters := this.adapterMgr.GetAdapters(Map( + "dataType", "entity_data", + "entityType", event.EntityTypeId + ), "", 0, webService) + + paramsEvent := WebServicesEntityDataParamsEvent( + WebServicesEvents.ENTITY_DATA_PARAMS, + event.EntityTypeId, + event.Entity, + webService, + Map("id", event.Entity.Id) + ) + this.container["manager.event"].DispatchEvent(paramsEvent) + + for key, adapter in adapters { + layerKey := this._getEntityLayerKey(webService, key) + + if (!event.LayerSources.Has(layerKey)) { + event.LayerSources[layerKey] := WebServiceAdapterLayerSource(adapter, paramsEvent.Params) + } + } + } + } + + GetReleaseInfo(event, extra, eventName, hwnd) { + if (!event.ReleaseInfo.Count && this.container.GetApp().Version != "{{VERSION}}") { + releaseInfo := this.adapterMgr.AdapterRequest("", "release_info") + + if (releaseInfo && releaseInfo.Count) { + event.ReleaseInfo := releaseInfo + } + } + } + + EntityFieldGroups(event, extra, eventName, hwnd) { + if (HasProp(event.Entity, "isWebServiceEntity") && event.Entity.isWebServiceEntity) { + return + } + + if (!event.FieldGroups.Has("web_services")) { + webServices := this.container["entity_manager.web_service"] + .EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Execute() + + addGroup := false + + for key, webService in webServices { + filters := Map( + "dataType", "entity_data", + "entityType", event.EntityTypeId + ) + operation := "read" + + if (this.adapterMgr.HasAdapters(filters, operation, webService)) { + addGroup := true + + break + } + } + + if (addGroup) { + event.FieldGroups["web_services"] := Map( + "name", "Web Services", + "weight", 100 + ) + } + } + } + + EntityFieldDefinitions(event, extra, eventName, hwnd) { + if (HasProp(event.Entity, "isWebServiceEntity") && event.Entity.isWebServiceEntity) { + return + } + + webServices := this.container["entity_manager.web_service"] + .EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Execute() + + for key, webService in webServices { + filters := Map( + "dataType", "entity_data", + "entityType", event.EntityTypeId + ) + operation := "read" + + if (this.adapterMgr.HasAdapters(filters, operation, webService)) { + event.FieldDefinitions["web_service_" . webService["id"] . "_ref"] := Map( + "title", webService["name"] . " Reference", + "description", "The key that is used to look up the entity's data from the " . webService["name"] . " web service.", + "help", "It defaults to the entity ID, but it can be overridden by setting this value.`n`nAddtionally, multiple copies of the same entity can exist by giving them different IDs but using the same " . webService["name"] . " reference.", + "group", "web_services", + "processValue", false, + "modes", Map("simple", Map("formField", false)) + ) + + break + } + } + } + + EntityDetectValues(event, extra, eventName, hwnd) { + if (HasProp(event.Entity, "isWebServiceEntity") && event.Entity.isWebServiceEntity) { + return + } + + webServices := this.container["entity_manager.web_service"] + .EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Execute() + + for key, webService in webServices { + fieldId := "web_service_" . webService["id"] . "_ref" + filters := Map( + "dataType", "entity_data", + "entityType", event.EntityTypeId + ) + + if ( + this.adapterMgr.HasAdapters(filters, "read", webService) + && (!event.Values.Has(fieldId) || !event.Values[fieldId]) + && event.Entity.HasField(fieldId) + && (!event.Entity.RawData.Has(fieldId) || !event.Entity.RawData[fieldId]) + ) { + paramsEvent := WebServicesEntityDataParamsEvent( + WebServicesEvents.ENTITY_DATA_PARAMS, + event.EntityTypeId, + event.Entity, + webService, + Map("id", event.Entity.Id) + ) + this.container["manager.event"].DispatchEvent(paramsEvent) + + result := this.adapterMgr.AdapterRequest( + paramsEvent.Params, + Map( + "dataType", "entity_lookup", + "entityType", event.EntityTypeId + ), + "read", + false, + webService + ) + + if (!result || Type(result) != "String") { + result := "" ; TODO decide whether to default to blank or the entity ID + } + + event.Values[fieldId] := result + } + } + } + + ListEntities(event, extra, eventName, hwnd) { + if (event.EntityTypeId == "web_service" || event.EntityTypeId == "web_service_provider") { + return + } + + if (event.includeExtended) { + entityMgr := this.container["entity_manager." . event.EntityTypeId] + results := this.adapterMgr.AdapterRequest("", Map( + "dataType", "entity_list", + "entityType", event.EntityTypeId + ), "read", true) + + if (results && HasBase(results, Array.Prototype)) { + managedIds := event.includeManaged + ? [] + : entityMgr.EntityQuery(EntityQuery.RESULT_TYPE_IDS).Execute() + + for index, id in results { + exists := false + + for , existingId in event.EntityList { + if (existingId == id) { + exists := true + break + } + } + + if (!exists && !event.includeManaged) { + for , managedId in managedIds { + if (managedId == id) { + exists := true + break + } + } + } + + if (!exists) { + event.EntityList.Push(id) + } + } + } + } + } +} diff --git a/Lib/Shared/Modules/WebServices/Events/WebServicesEvents.ahk b/Lib/Shared/Modules/WebServices/Events/WebServicesEvents.ahk new file mode 100644 index 00000000..bde6b479 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Events/WebServicesEvents.ahk @@ -0,0 +1,8 @@ +class WebServicesEvents { + static HTTP_REQ_ALTER := 0x4200 + static REQUEST_PRESEND := 0x4210 + static CACHED_RESPONSE_CREATED := 0x4215 + static HTTP_RESPONSE_CREATED := 0x4217 + static RESPONSE_ALTER := 0x4220 + static ENTITY_DATA_PARAMS := 0x4225 +} diff --git a/Lib/Shared/Modules/WebServices/Factory/WebServiceAdapterFactory.ahk b/Lib/Shared/Modules/WebServices/Factory/WebServiceAdapterFactory.ahk new file mode 100644 index 00000000..d1c423a4 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Factory/WebServiceAdapterFactory.ahk @@ -0,0 +1,41 @@ +class WebServiceAdapterFactory { + container := "" + + __New(container) { + this.container := container + } + + CreateWebServiceAdapter(webService, definition) { + adapterTypes := this.container.GetParameter("web_services.adapter_types") + + if (!definition.Has("adapterType") || !definition["adapterType"]) { + definition["adapterType"] := "json" + } + + if (adapterTypes.Has(definition["adapterType"])) { + defaults := adapterTypes[definition["adapterType"]] + + if (Type(defaults) == "String") { + defaults := Map("class", defaults) + } + + for key, val in defaults { + if (!definition.Has(key)) { + definition[key] := val + } + } + } + + if (!definition.Has("class")) { + throw AppException("Adapter class not known.") + } + + adapterClass := definition["class"] + + if (!HasMethod(%adapterClass%)) { + throw AppException("Adapter class " . adapterClass . " was not found.") + } + + return %adapterClass%.Create(this.container, webService, definition) + } +} diff --git a/Lib/Shared/Modules/WebServices/Gui/AuthenticationGui/LaunchpadLoginWindow.ahk b/Lib/Shared/Modules/WebServices/Gui/AuthenticationGui/LaunchpadLoginWindow.ahk new file mode 100644 index 00000000..0145f0cb --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Gui/AuthenticationGui/LaunchpadLoginWindow.ahk @@ -0,0 +1,32 @@ +class LaunchpadLoginWindow extends FormGuiBase { + entityObj := "" + entityManager := "" + missingFields := Map() + + GetDefaultConfig(container, config) { + defaults := super.GetDefaultConfig(container, config) + defaults["title"] := "Login" + defaults["text"] := "Logging in allows enhanced features such as online backup, restore, personalization, and sharing with the community.`n`nIf you'd like to log in, click the `"Get token`" button to go to the launchpad.games site to retrieve a valid login token and then paste it below." + defaults["buttons"] := "*&Login|&Cancel" + return defaults + } + + Controls() { + super.Controls() + this.Add("ButtonControl", "xs y+m vGetAuthToken w150 h30", "Get Token") + this.AddHeading("Login Token") + this.guiObj.AddEdit("vAuthToken xs y+m r1 w" . this.windowSettings["contentWidth"] . " c" . this.themeObj.GetColor("editText")) + } + + OnGetAuthToken(btn, info) { + Run("https://launchpad.games/profile") + } + + ProcessResult(result, submittedData := "") { + if (result == "Login") { + result := this.guiObj["AuthToken"].Text + } + + return result + } +} diff --git a/Lib/Shared/Volantis.App/Gui/Form/FeedbackWindow.ahk b/Lib/Shared/Modules/WebServices/Gui/Form/FeedbackWindow.ahk similarity index 56% rename from Lib/Shared/Volantis.App/Gui/Form/FeedbackWindow.ahk rename to Lib/Shared/Modules/WebServices/Gui/Form/FeedbackWindow.ahk index ba27dc1c..b790b0dc 100644 --- a/Lib/Shared/Volantis.App/Gui/Form/FeedbackWindow.ahk +++ b/Lib/Shared/Modules/WebServices/Gui/Form/FeedbackWindow.ahk @@ -1,11 +1,9 @@ class FeedbackWindow extends DialogBox { errorObj := "" notifierObj := "" - apiEndpointUrl := "" __New(container, themeObj, config) { this.notifierObj := container.Get("notifier").notifierObj - this.apiEndpointUrl := container.Get("config.app")["api_endpoint"] super.__New(container, themeObj, config) } @@ -38,20 +36,50 @@ class FeedbackWindow extends DialogBox { SendFeedback() { global appVersion - if (this.apiEndpointUrl) { - endpoint := this.apiEndpointUrl . "/submit-feedback" + filters := "feedback_submission" + operation := "create" + if ( + this.container.Has("web_services.adapter_manager") + && this.container["web_services.adapter_manager"].HasAdapters(filters, operation) + ) { body := Map() body["email"] := this.guiObj["Email"].Text body["version"] := appVersion body["feedback"] := this.guiObj["Feedback"].Text - request := WinHttpReq(endpoint) - response := request.Send("POST", body) - success := !!(request.GetStatusCode() == 200) + results := this.container["web_services.adapter_manager"].AdapterRequest( + Map("data", body), + filters, + operation, + true + ) - notification := success ? "Successfully sent feedback to Volantis Development" : "Failed to send feedback to Volantis Development" - this.notifierObj.Notify(notification, "Feedback Sent", success ? "info" : "error") + success := false + + for adapterId, adapterResult in results { + if (adapterResult) { + success := true + + break + } + } + + message := "" + + if (success) { + message := "Successfully sent feedback" + } else if (results.Count) { + message := "Failed to send feedback" + } else { + message := "No feedback adapters are enabled" + } + + this.notifierObj.Notify( + message, + "Feedback Submission", + success ? "info" : "error" + ) } } } diff --git a/Lib/Shared/Modules/WebServices/Gui/ManageWindow/ManageWebServicesWindow.ahk b/Lib/Shared/Modules/WebServices/Gui/ManageWindow/ManageWebServicesWindow.ahk new file mode 100644 index 00000000..873a9596 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/Gui/ManageWindow/ManageWebServicesWindow.ahk @@ -0,0 +1,87 @@ +class ManageWebServicesWindow extends ManageEntitiesWindow { + listViewColumns := Array("SERVICE", "PROVIDER", "USER", "AUTHENTICATED", "AUTO-LOGIN") + + GetListViewData(lv) { + data := Map() + + for key, webService in this.entityMgr { + data[key] := [ + webService["name"], + webService["Provider"]["name"], + "", + webService.Authenticated ? "Yes" : "No", + webService["AutoLogin"] ? "Yes" : "No" + ] + } + + return data + } + + GetEntityIconSrc(entityObj) { + return entityObj["Provider"]["IconSrc"] + } + + GetContextMenuItems(entityObj) { + menuItems := super.GetContextMenuItems(entityObj) + + if (entityObj["Provider"]["SupportsAuthentication"]) { + if (entityObj.Authenticated) { + menuItems.InsertAt(1, Map("label", "&Logout", "name", "WebServiceLogout")) + } else { + menuItems.InsertAt(1, Map("label", "&Login", "name", "WebServiceLogin")) + } + } + + return menuItems + } + + _shouldShowButton(entityObj, buttonName) { + shouldShow := super._shouldShowButton(entityObj, buttonName) + + if (shouldShow && buttonName == "DeleteEntity") { + shouldShow := entityObj.Id != "launchpad_api" + } + + return shouldShow + } + + ProcessContextMenuResult(result, key) { + if (result == "WebServiceLogout") { + this.Logout(key) + } else if (result == "WebServiceLogin") { + this.Login(key) + } else { + super.ProcessContextMenuResult(result, key) + } + } + + Logout(key) { + result := this.entityMgr[key].Logout() + + this.UpdateListView() + + return result + } + + Login(key) { + result := this.entityMgr[key].Login() + + this.UpdateListView() + + return result + } + + ViewEntity(key) { + entityObj := this.entityMgr[key] + } + + AddEntity() { + ; @todo open add wizard + + this.UpdateListView() + } + + DeleteEntity(key) { + entityObj := this.entityMgr[key] + } +} diff --git a/Lib/Shared/Modules/WebServices/LayerSource/WebServiceAdapterLayerSource.ahk b/Lib/Shared/Modules/WebServices/LayerSource/WebServiceAdapterLayerSource.ahk new file mode 100644 index 00000000..c4dab738 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/LayerSource/WebServiceAdapterLayerSource.ahk @@ -0,0 +1,51 @@ +class WebServiceAdapterLayerSource extends LayerSourceBase { + adapter := "" + params := "" + + __New(adapter, params := "") { + if (!params) { + params := Map() + } + + this.adapter := adapter + this.params := params + } + + SaveData(data := "") { + if (this.HasData()) { + if (this.adapter.definition["updateAllow"]) { + this.adapter.UpdateData(data, this.params) + } + } else if (this.adapter.definition["createAllow"]) { + this.adapter.CreateData(data, this.params) + } + + return this + } + + LoadData() { + data := "" + + if (this.adapter.definition["readAllow"]) { + data := this.adapter.ReadData(this.params) + } + + if (!data) { + data := Map() + } + + return data + } + + HasData() { + return this.adapter.DataExists(this.params) + } + + DeleteData() { + if (this.adapter.definitions["deleteAllow"]) { + this.adapter.DeleteData(this.params) + } + + return this + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceAdapter/FileWebServiceAdapter.ahk b/Lib/Shared/Modules/WebServices/WebServiceAdapter/FileWebServiceAdapter.ahk new file mode 100644 index 00000000..e922134a --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceAdapter/FileWebServiceAdapter.ahk @@ -0,0 +1,5 @@ +class FileWebServiceAdapter extends WebServiceAdapterBase { + dataClass := "" + + ; @todo Implement file downloading +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceAdapter/JsonWebServiceAdapter.ahk b/Lib/Shared/Modules/WebServices/WebServiceAdapter/JsonWebServiceAdapter.ahk new file mode 100644 index 00000000..5f63b104 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceAdapter/JsonWebServiceAdapter.ahk @@ -0,0 +1,3 @@ +class JsonWebServiceAdapter extends WebServiceAdapterBase { + dataClass := "JsonData" +} \ No newline at end of file diff --git a/Lib/Shared/Modules/WebServices/WebServiceAdapter/WebServiceAdapterBase.ahk b/Lib/Shared/Modules/WebServices/WebServiceAdapter/WebServiceAdapterBase.ahk new file mode 100644 index 00000000..8e30611e --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceAdapter/WebServiceAdapterBase.ahk @@ -0,0 +1,352 @@ +class WebServiceAdapterBase { + container := "" + webService := "" + definition := "" + dataClass := "" + merger := "" + operationTypes := ["create", "read", "update", "delete"] + + static ADAPTER_RESULT_DATA := "data" + static ADAPTER_RESULT_HTTP_STATUS := "httpStatus" + static ADAPTER_RESULT_SUCCESS := "success" + + __New(container, merger, webService, definition) { + this.container := container + this.merger := merger + this.webService := webService + + if (!definition) { + definition := Map() + } + + if (!HasBase(definition, Map.Prototype)) { + throw AppException("Definition must be a Map-like object.") + } + + this.definition := this.merger.Merge(this.GetDefaultDefinition(), definition) + } + + static Create(container, webService, definition) { + className := this.Prototype.__Class + + return %className%( + container, + container.Get("merger.list"), + webService, + definition + ) + } + + GetDefaultDefinition() { + return Map( + "dataType", "", + "adapterType", "json", + "requestPath", "", + "requestData", "", + "cacheResponse", true, + "cacheMaxAge", 186400, + "createAllow", false, + "createMethod", "POST", + "createAuth", true, + "readAllow", true, + "readMethod", "GET", + "readAuth", false, + "updateAllow", false, + "updateMethod", "POST", + "updateAuth", true, + "deleteAllow", false, + "deleteMethod", "PUT", + "deleteAuth", true, + "dataMap", Map(), + "dataSelector", "", + "weight", 0, + "entityType", "", + "tags", [], + "requiredParams", [], + ) + } + + SupportsOperation(operation) { + supported := false + + for index, operationType in this.operationTypes { + if (operation == operationType) { + supported := true + + break + } + } + + return supported + } + + SendRequest(operation, params := "") { + if (!this.SupportsOperation(operation)) { + throw AppException("The '" . operation . "' operation is not supported by this data adapter.") + } + + result := "" + data := params.Has("data") ? params["data"] : "" + + if (operation == "create") { + result := this.CreateData(data, params) + } else if (operation == "read") { + result := this.ReadData(params) + } else if (operation == "update") { + result := this.UpdateData(data, params) + } else if (operation == "delete") { + result := this.DeleteData(params) + } + + return result + } + + CreateData(data, params := "") { + if (!this.definition["createAllow"]) { + throw AppException("The 'create' operation is not allowed on this data adapter.") + } + + response := this._request( + params, + this.definition["createMethod"], + data ? data : this._getData(params), + this.definition["createAuth"], + false + ).Send() + + return this._getResult( + params, + response, + this._getResultType(params, WebServiceAdapterBase.ADAPTER_RESULT_SUCCESS) + ) + } + + _getResultType(params, default) { + resultType := default + + if (params.Has("resultType") && params["resultType"]) { + resultType := params["resultType"] + } + + return resultType + } + + _getResult(params, response, resultType) { + result := "" + + if (resultType == WebServiceAdapterBase.ADAPTER_RESULT_DATA) { + if (response.IsSuccessful()) { + data := response.GetResponseBody() + + if (data) { + result := this._mapData(this._parseData(data, params), params) + } + + } + } else if (resultType == WebServiceAdapterBase.ADAPTER_RESULT_HTTP_STATUS) { + result := response.GetHttpStatusCode() + } else if (resultType == WebServiceAdapterBase.ADAPTER_RESULT_SUCCESS) { + result := response.IsSuccessful() + } + + return result + } + + DataExists(params := "") { + if (!this.definition["readAllow"]) { + throw AppException("The 'read' operation is not allowed on this data adapter.") + } + + return this._request( + params, + this.definition["readMethod"], + this._getData(params), + this.definition["readAuth"], + false + ).Send().IsSuccessful() + } + + ReadData(params := "") { + if (!this.definition["readAllow"]) { + throw AppException("The 'read' operation is not allowed on this data adapter.") + } + + response := this._request( + params, + this.definition["readMethod"], + this._getData(params), + this.definition["readAuth"], + this.definition["cacheResponse"] + ).Send() + + return this._getResult( + params, + response, + this._getResultType(params, WebServiceAdapterBase.ADAPTER_RESULT_DATA) + ) + } + + UpdateData(data, params := "") { + if (!this.definition["updateAllow"]) { + throw AppException("The 'update' operation is not allowed on this data adapter.") + } + + response := this._request( + params, + this.definition["updateMethod"], + data ? data : this._getData(params), + this.definition["updateAuth"], + false + ).Send() + + return this._getResult( + params, + response, + this._getResultType(params, WebServiceAdapterBase.ADAPTER_RESULT_SUCCESS) + ) + } + + DeleteData(params := "") { + if (!this.definition["deleteAllow"]) { + throw AppException("The 'delete' operation is not allowed on this data adapter.") + } + + response := this._request( + params, + this.definition["deleteMethod"], + this._getData(params), + this.definition["deleteAuth"], + false + ).Send() + + return this._getResult( + params, + response, + this._getResultType(params, WebServiceAdapterBase.ADAPTER_RESULT_SUCCESS) + ) + } + + _requestPath(params) { + requestPath := this.definition["requestPath"] + isFound := true + + while isFound { + match := "" + isFound := RegExMatch(requestPath, "({([^}]+)})", &match) + + if (isFound) { + key := match[2] + + replacement := (params && params.Has(key)) + ? params[key] + : "" + + requestPath := StrReplace(requestPath, match[1], replacement) + } + } + + return requestPath + } + + _validateParams(params) { + if (!params) { + params := Map() + } + + valid := true + requiredParams := this.definition["requiredParams"] + + if (requiredParams) { + if (Type(requiredParams) == "String") { + requiredParams := [requiredParams] + } + + for , requiredParam in requiredParams { + if (!params.Has(requiredParam) || !params[requiredParam]) { + valid := false + + break + } + } + } + + return valid + } + + _request(params, method, data, useAuthentication, cacheResponse) { + if (!this._validateParams(params)) { + throw AppException("The data adapter request was called with invalid or missing parameters.") + } + + requestPath := this.definition["requestPath"] + + for key, value in params { + + } + + return this.webService.Request( + this._requestPath(params), + method, + data, + useAuthentication, + cacheResponse + ) + } + + _mapData(data, params, reverse := false) { + if ( + data + && HasBase(data, Map.Prototype) + && this.definition["dataMap"] + && HasBase(this.definition["dataMap"], Map.Prototype) + ) { + for key1, key2 in this.definition["dataMap"] { + oldKey := reverse ? key2 : key1 + newKey := reverse ? key1 : key2 + + if (data.Has(oldKey)) { + data[newKey] := data[oldKey] + data.Delete(oldKey) + } + } + } + } + + _parseData(data, params) { + if (data && this.dataClass) { + dataClass := this.dataClass + data := %dataClass%().FromString(&data) + + if (this.definition["dataSelector"]) { + dataSelector := this.definition["dataSelector"] + + if (Type(dataSelector) == "String") { + dataSelector := StrSplit(dataSelector, ".") + } + + for index, pathPart in dataSelector { + if (data.Has(pathPart)) { + data := data[pathPart] + } else { + data := "" + break + } + } + } + } + + return data + } + + _getData(params, data := "") { + if (!data) { + if (params.Has("data") && params["data"]) { + data := params["data"] + } else if (this.definition["requestData"]) { + data := this.definition["requestData"] + } + } + + return data + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceAuthenticator/JwtWebServiceAuthenticator.ahk b/Lib/Shared/Modules/WebServices/WebServiceAuthenticator/JwtWebServiceAuthenticator.ahk new file mode 100644 index 00000000..ce99bdce --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceAuthenticator/JwtWebServiceAuthenticator.ahk @@ -0,0 +1,178 @@ +class JwtWebServiceAuthenticator extends WebServiceAuthenticatorBase { + Login(webServiceEnt, retryCount := 0) { + if (retryCount > this.maxRetries) { + this._handleLoginFailure("You have used " . retryCount . " of " . this.maxRetries + 1 . " login attempts. Canceling login.") + return false + } + + authResult := "" + + if (!this._hasRefreshToken(webServiceEnt)) { + authResult := this._reauthenticate(webServiceEnt) + } + + success := false + + if (authResult != "Cancel") { + if (this._hasRefreshToken(webServiceEnt)) { + success := this._refreshAuthentication(webServiceEnt) + } + + if (!success) { + success := this.Login(webServiceEnt, retryCount + 1) + } + } + + return success + } + + Logout(webServiceEnt) { + webServiceEnt + .ResetAuthData() + .DeleteAuthData("auth_token", true) + .DeleteAuthData("refresh_token", true) + .DeleteAuthData("expires", true) + .SetAuthData("authenticated", false, true) + + + return true + } + + RefreshAuthentication(webServiceEnt) { + if (this.NeedsRefresh(webServiceEnt)) { + this.Login(webServiceEnt) + } + } + + AlterRequest(webServiceEnt, httpReqObj) { + bearerToken := webServiceEnt.AuthData["auth_token"] + + if (bearerToken) { + httpReqObj.requestHeaders["Authorization"] := "Bearer " . bearerToken + } + } + + _hasRefreshToken(webServiceEnt) { + return !!(webServiceEnt.AuthData["refresh_token"]) + } + + _reauthenticate(webServiceEnt) { + refreshToken := this._authenticationGui(webServiceEnt) + + if (refreshToken != "Cancel") { + this._setRefreshToken(webServiceEnt, refreshToken) + } + + return refreshToken + } + + _getRefreshToken(webServiceEnt) { + return webServiceEnt.AuthData["refresh_token"] + } + + _setRefreshToken(webServiceEnt, refreshToken) { + webServiceEnt.SetAuthData("refresh_token", refreshToken, true) + } + + _extractAuthData(webServiceEnt, response) { + loginData := response.GetJsonData() + + if (!loginData.Has("authenticated")) { + loginData["authenticated"] := !!(loginData.Has("refresh_token") && loginData["refresh_token"]) + } + + keyMap := Map( + "id_token", "auth_token", + "expires_in", "expires" + ) + + persistentKeys := [ + "user_id", + "refresh_token", + "auth_token", + "access_token", + "authenticated", + "expires" + ] + + expiresInKeys := [ + "expires" + ] + + skipKeys := [] + + for key, val in loginData { + if (keyMap.Has(key)) { + key := keyMap[key] + } + + persist := false + + for , persistKey in persistentKeys { + if (key == persistKey) { + persist := true + break + } + } + + expires := false + + for , expiresKey in expiresInKeys { + if (key == expiresKey) { + val := DateAdd(A_Now, val, "S") + break + } + } + + skip := false + + for , skipKey in skipKeys { + if (key == skipKey) { + skip := true + break + } + } + + if (!skip) { + webServiceEnt.SetAuthData(key, val, persist) + } + } + } + + _refreshAuthentication(webServiceEnt) { + apiKey := webServiceEnt["Provider"]["AppKey"] + refreshToken := webServiceEnt.AuthData["refresh_token"] + refreshUrl := webServiceEnt["Provider"].GetAuthenticationRefreshUrl(Map("key", apiKey)) + response := "" + + if (!apiKey) { + throw OperationFailedException("Missing API key for auth refresh.") + } + + if (!refreshToken) { + throw OperationFailedException("Missing refresh token for auth refresh.") + } + + if (refreshUrl) { + payload := Map( + "grant_type", "refresh_token", + "refresh_token", refreshToken + ) + + response := webServiceEnt.Request(refreshUrl, "POST", payload, false, false).Send() + } + + success := response.IsSuccessful() + + if (response && success) { + this._extractAuthData(webServiceEnt, response) + } else { + url := response.httpReqObj.url.ToString(true) + webServiceEnt.SetAuthData("refresh_token", "", true) + + ; @todo handle common http error codes + } + + return success + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceAuthenticator/WebServiceAuthenticatorBase.ahk b/Lib/Shared/Modules/WebServices/WebServiceAuthenticator/WebServiceAuthenticatorBase.ahk new file mode 100644 index 00000000..91664f03 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceAuthenticator/WebServiceAuthenticatorBase.ahk @@ -0,0 +1,83 @@ +class WebServiceAuthenticatorBase { + refreshThresholdSeconds := 600 + authenticatedStateKey := "authenticated" + expiresStateKey := "expires" + maxRetries := 3 + guiMgr := "" + + __New(guiMgr) { + this.guiMgr := guiMgr + } + + Login(webServiceEnt, retryCount := 0) { + + } + + Logout(webServiceEnt) { + + } + + RefreshAuthentication(webServiceEnt) { + + } + + AlterRequest(webServiceEnt, httpReqObj) { + + } + + IsAuthenticated(webServiceEnt) { + auth := webServiceEnt.AuthData[this.authenticatedStateKey] + expired := this.AuthenticationIsExpired(webServiceEnt) + + return auth && !expired + + } + + NeedsRefresh(webServiceEnt) { + needsRefresh := false + + if (this.IsAuthenticated(webServiceEnt)) { + expires := webServiceEnt.AuthData[this.expiresStateKey] + + if (expires) { + diff := DateDiff(A_Now, expires, "S") + needsRefresh := (diff >= (0 - this.refreshThresholdSeconds)) + } else { + needsRefresh := true + } + } else { + needsRefresh := true + } + + return needsRefresh + } + + AuthenticationIsExpired(webServiceEnt) { + expired := true + + if (webServiceEnt.AuthData[this.authenticatedStateKey] && webServiceEnt.AuthData[this.expiresStateKey]) { + expired := (DateDiff(A_Now, webServiceEnt.AuthData["expires"], "S") >= 0) + } + + return expired + } + + _authenticationGui(webServiceEnt) { + loginWindowGui := webServiceEnt["Provider"]["LoginWindow"] + result := "" + + if (loginWindowGui) { + result := this.guiMgr.Dialog(Map("type", loginWindowGui)) + } + + return result + } + + _handleLoginFailure(message) { + this.guiMgr.Dialog(Map( + "title", "Login Failure", + "text", message, + "buttons", "*&OK" + )) + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceRequest/BasicWebServiceRequest.ahk b/Lib/Shared/Modules/WebServices/WebServiceRequest/BasicWebServiceRequest.ahk new file mode 100644 index 00000000..a723ff4c --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceRequest/BasicWebServiceRequest.ahk @@ -0,0 +1,3 @@ +class BasicWebServiceRequest extends WebServiceRequestBase { + +} \ No newline at end of file diff --git a/Lib/Shared/Modules/WebServices/WebServiceRequest/WebServiceRequestBase.ahk b/Lib/Shared/Modules/WebServices/WebServiceRequest/WebServiceRequestBase.ahk new file mode 100644 index 00000000..e89cfce5 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceRequest/WebServiceRequestBase.ahk @@ -0,0 +1,201 @@ +class WebServiceRequestBase { + eventMgr := "" + webServiceEnt := "" + cacheObj := "" + path := "" + method := "" + data := "" + useAuthentication := false + httpReqObj := "" + responseObj := "" + cacheResponse := true + _url := "" + + Url { + get => this._getUrl() + } + + Response { + get => this.responseObj + } + + __New(eventMgr, webServiceEnt, cacheObj, method := "", path := "", data := "", useAuthentication := false, cacheResponse := true) { + this.eventMgr := eventMgr + this.webServiceEnt := webServiceEnt + this.cacheObj := cacheObj + + if (HasBase(path, UrlObj.Prototype)) { + this._url := path + path := this._url.Path + } + + if (!method) { + method := "GET" + } + + this.method := method + + if (path) { + this.path := path + } + + if (data) { + this.data := data + } + + this.useAuthentication := useAuthentication + this.cacheResponse := cacheResponse + } + + GetPath() { + return this.path + } + + SetPath(path := "") { + this.path := path + + if (this._url) { + this._url.Path := this.webServiceEnt["Provider"].Path(path) + } + + return this + } + + GetMethod() { + return this.method + } + + SetMethod(method := "GET") { + this.method := method + + return this + } + + GetData() { + return this.data + } + + SetData(data := "", clearCache := false) { + this.data := data + + if (clearCache) { + this.cacheObj.RemoveItem(this.GetPath()) + } + + return this + } + + GetUseAuthentication() { + return this.useAuthentication + } + + SetUseAuthentication(useAuthentication := false) { + this.useAuthentication := useAuthentication + + return this + } + + GetHttpReq() { + if (!this.httpReqObj) { + this.httpReqObj := WinHttpReq(this.Url) + } + + return this.httpReqObj + } + + SetHttpReq(httpReqObj := "") { + this.httpReqObj := httpReqObj + } + + _getUrl() { + if (!this._url) { + this._url := this.webServiceEnt["Provider"].Url(this.path) + } + + return this._url + } + + Send(resend := false) { + if (resend || !this.responseObj) { + if (this.RequestIsCached()) { + this.responseObj := this._createCachedResponse() + } else { + httpReqObj := this.GetHttpReq() + + if (this.GetUseAuthentication() && this.webServiceEnt["Provider"].Has("Authenticator", false)) { + authenticator := this.webServiceEnt["Provider"]["Authenticator"] + + if (authenticator.NeedsRefresh(this.webServiceEnt)) { + authenticator.RefreshAuthentication(this.webServiceEnt) + } + + authenticator.AlterRequest(this.webServiceEnt, httpReqObj) + } + + if (!this.cacheResponse) { + httpReqObj.requestHeaders["Cache-Control"] := "no-cache" + } + + event := WebServicesRequestEvent(WebServicesEvents.HTTP_REQ_ALTER, this) + this.eventMgr.DispatchEvent(event) + + event := WebServicesRequestEvent(WebServicesEvents.REQUEST_PRESEND, this) + this.eventMgr.DispatchEvent(event) + + httpReqObj.Send(this.GetMethod(), this.GetData()) + this.responseObj := this._createHttpReqResponse() + this._cacheResponse() + } + + event := WebServicesResponseEvent(WebServicesEvents.RESPONSE_ALTER, this, this.responseObj) + this.eventMgr.DispatchEvent(event) + + this.responseObj := event.Response + } + + return this.responseObj + } + + RequestIsCached() { + path := this.GetPath() + + return (this.cacheObj.ItemExists(path) && !this.cacheObj.ItemNeedsUpdate(path)) + } + + _createCachedResponse() { + response := CachedWebServiceResponse(this.webServiceEnt, this) + + event := WebServicesResponseEvent(WebServicesEvents.CACHED_RESPONSE_CREATED, this, response) + this.eventMgr.DispatchEvent(event) + + return event.Response + } + + _createHttpReqResponse() { + response := HttpReqWebServiceResponse(this.webServiceEnt, this) + + event := WebServicesResponseEvent(WebServicesEvents.HTTP_RESPONSE_CREATED, this, response) + this.eventMgr.DispatchEvent(event) + + return event.Response + } + + _cacheResponse() { + if (this.responseObj && this.cacheResponse) { + path := this.GetPath() + + if (this.responseObj.IsSuccessful()) { + body := this.responseObj.GetResponseBody() + + if (body) { + this.cacheObj.WriteItem(path, body, this.responseObj.GetHttpStatusCode()) + } else { + ; Response is empty, delete any existing cache for this item + this.cacheObj.RemoveItem(path) + } + } else if (this.responseObj.IsNotFound()) { + this.cacheObj.SetNotFound(path) + } + } + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceResponse/CachedWebServiceResponse.ahk b/Lib/Shared/Modules/WebServices/WebServiceResponse/CachedWebServiceResponse.ahk new file mode 100644 index 00000000..551b7efa --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceResponse/CachedWebServiceResponse.ahk @@ -0,0 +1,30 @@ +class CachedWebServiceResponse extends WebServiceResponseBase { + cacheObj := "" + + __New(webServiceEnt, webServiceReq) { + this.cacheObj := webServiceEnt.cacheObj + + super.__New(webServiceEnt, webServiceReq) + } + + GetHttpStatusCode() { + responseCode := 0 + + if (this.cacheObj) { + responseCode := this.cacheObj.GetResponseCode(this.webServiceReq.GetPath()) + } + + return responseCode + } + + GetResponseBody() { + body := "" + path := this.webServiceReq.GetPath() + + if (this.cacheObj.ItemExists(path)) { + body := this.cacheObj.ReadItem(path) + } + + return body + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceResponse/HttpReqWebServiceResponse.ahk b/Lib/Shared/Modules/WebServices/WebServiceResponse/HttpReqWebServiceResponse.ahk new file mode 100644 index 00000000..d7857400 --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceResponse/HttpReqWebServiceResponse.ahk @@ -0,0 +1,17 @@ +class HttpReqWebServiceResponse extends WebServiceResponseBase { + httpReqObj := "" + + __New(webServiceEnt, webServiceReq) { + this.httpReqObj := webServiceReq.GetHttpReq() + + super.__New(webServiceEnt, webServiceReq) + } + + GetHttpStatusCode() { + return this.httpReqObj.GetStatusCode() + } + + GetResponseBody() { + return Trim(this.httpReqObj.GetResponseData()) + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServiceResponse/WebServiceResponseBase.ahk b/Lib/Shared/Modules/WebServices/WebServiceResponse/WebServiceResponseBase.ahk new file mode 100644 index 00000000..e7413bfa --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServiceResponse/WebServiceResponseBase.ahk @@ -0,0 +1,57 @@ +class WebServiceResponseBase { + webServiceEnt := "" + webServiceReq := "" + successCodes := [200] + notFoundCodes := [404] + + __New(webServiceEnt, webServiceReq) { + this.webServiceEnt := webServiceEnt + this.webServiceReq := webServiceReq + } + + GetHttpStatusCode() { + return "" + } + + GetResponseBody() { + return "" + } + + GetJsonData() { + body := this.GetResponseBody() + + if (!body) { + body := "{}" + } + + return JsonData().FromString(&body) + } + + IsSuccessful() { + httpCode := this.GetHttpStatusCode() + success := false + + for , successCode in this.successCodes { + if (httpCode == successCode) { + success := true + break + } + } + + return success + } + + IsNotFound() { + httpCode := this.GetHttpStatusCode() + notFound := false + + for , notFoundCode in this.notFoundCodes { + if (httpCode == notFoundCode) { + notFound := true + break + } + } + + return notFound + } +} diff --git a/Lib/Shared/Modules/WebServices/WebServices.module.json b/Lib/Shared/Modules/WebServices/WebServices.module.json new file mode 100644 index 00000000..1f19e86b --- /dev/null +++ b/Lib/Shared/Modules/WebServices/WebServices.module.json @@ -0,0 +1,123 @@ +{ + "module": { + "name": "Web Services", + "type": "AppModule", + "icon": "", + "category": "Web Services", + "tags": ["Launchpad", "LaunchpadBuilder"], + "description": "Enables Launchpad to connect to and authenticate with remove services on the Internet for additional functionality.", + "author": { + "name": "Ben McClure, Volantis Dev", + "url": "https://volantisdev.com" + }, + "website": "https://launchpad.games", + "version": "{{VERSION}}", + "appVersion": "", + "dependencies": [] + }, + "parameters": { + "config.web_services_file": "@@{data_dir}\\WebServices.json", + "config.web_services_state_path": "@@{data_dir}\\WebServicesState.json", + "config.web_services_view_mode": "Report", + "entity_type.web_service": { + "name_singular": "Web Service", + "name_plural": "Web Services", + "entity_class": "WebServiceEntity", + "storage_config_storage_parent_key": "WebServices", + "storage_config_path_parameter": "config.web_services_file", + "manager_view_mode_parameter": "config.web_services_view_mode", + "definition_loader_parameter_key": "web_services.services", + "default_icon": "webhook", + "allow_add": true, + "allow_edit": true, + "allow_delete": true, + "manager_gui": "ManageWebServicesWindow", + "manager_link_in_tools_menu": true + }, + "entity_type.web_service_provider": { + "name_singular": "Service Provider", + "name_plural": "Service Providers", + "entity_class": "WebServiceProviderEntity", + "definition_loader_class": "ParameterEntityDefinitionLoader", + "definition_loader_parameter_key": "web_services.providers", + "storage_type": "runtime" + }, + "web_services.data_types.account_info": { + "name": "Account Info", + "description": "Account information related to an authentication session." + }, + "web_services.data_types.error_submission": { + "name": "Error Submission", + "description": "An endpoint to submit application errors to." + }, + "web_services.data_types.feedback_submission": { + "name": "Feedback Submission", + "description": "An endpoint to submit feedback to." + }, + "web_services.data_types.game_submission": { + "name": "Game Submission", + "description": "An endpoint to game launcher data to for sharing with the community." + }, + "web_services.data_types.release_info": { + "name": "Release Info", + "description": "Information about the latest (or any) version of Launchpad" + }, + "web_services.data_types.entity_list": { + "name": "Entity List", + "description": "A listing of entities from a web service." + }, + "web_services.data_types.entity_lookup": { + "name": "Entity Lookup", + "description": "Searches for the ID of a remote entity from the provided data." + }, + "web_services.data_types.entity_data": { + "name": "Entity Data", + "description": "Data to be imported into an entity within the application." + }, + "web_services.data_types.availability_check": { + "name": "Availability check", + "description": "Simply checks for any response from the web service." + }, + "web_services.adapter_types.json": { + "class": "JsonWebServiceAdapter" + }, + "web_services.adapter_types.file": { + "class": "FileWebServiceAdapter" + } + }, + "services": { + "cache.web_services": { + "class": "FileCache", + "arguments": ["@{App}", "@cache_state.web_services", "@@config.cache_dir", "WebServices"] + }, + "cache_state.web_services": { + "class": "CacheState", + "arguments": ["@{App}", "@@config.cache_dir", "WebServices.json"] + }, + "event_subscriber.web_services": { + "class": "WebServicesEventSubscriber", + "arguments": ["@{}", "@web_services.adapter_manager"], + "tags": ["event_subscriber"] + }, + "state.web_services": { + "class": "JsonState", + "arguments": ["@{App}", "@@config.web_services_state_path"] + }, + "state.web_services_tmp": { + "class": "ParameterState", + "arguments": ["@{App}", "web_services.state.tmp"] + }, + "web_services_authenticator.jwt": { + "class": "JwtWebServiceAuthenticator", + "arguments": ["@manager.gui"] + }, + "web_services.adapter_factory": { + "class": "WebServiceAdapterFactory", + "arguments": ["@{}"] + }, + "web_services.adapter_manager": { + "class": "WebServiceAdapterManager", + "arguments": ["@{}", "web_services.adapters.", "@web_services.adapter_factory", "@manager.entity_type", "@manager.event"] + } + } +} diff --git a/Lib/Shared/Volantis.App/App/AppBase.ahk b/Lib/Shared/Volantis.App/App/AppBase.ahk index 14051eb9..307ceb47 100644 --- a/Lib/Shared/Volantis.App/App/AppBase.ahk +++ b/Lib/Shared/Volantis.App/App/AppBase.ahk @@ -1,5 +1,4 @@ class AppBase { - developer := "" versionStr := "" appName := "" appDir := "" @@ -8,9 +7,9 @@ class AppBase { configObj := "" stateObj := "" serviceContainerObj := "" - customTrayMenu := false themeReady := false startConfig := "" + isSetup := false static Instance := "" @@ -25,11 +24,20 @@ class AppBase { } Config { - get => this.Service("config.app") + get => this["config.app"] } State { - get => this.Service("state.app") + get => this["state.app"] + } + + Parameter[key] { + get => this.Services.GetParameter(key) + set => this.Services.SetParameter(key, value) + } + + __Item[serviceId] { + get => this.Service(serviceId) } __New(config := "", autoStart := true) { @@ -57,6 +65,16 @@ class AppBase { "app_dir", this.appDir, "data_dir", this.dataDir, "tmp_dir", this.tmpDir, + "app.website_url", "", + "app.custom_tray_menu", false, + "app.developer", "", + "app.has_settings", false, + "app.settings_window", "", + "app.show_restart_menu_item", true, + "app.supports_update_check", false, + "app.show_about_menu_item", false, + "app.about_window", "", + "app.show_website_menu_item", false, "resources_dir", "@@{app_dir}\Resources", "config_path", "@@{app_dir}\" . this.appName . ".json", "config_key", "config", @@ -374,10 +392,6 @@ class AppBase { config["appName"] := appBaseName } - if (!config.Has("developer")) { - config["developer"] := "" - } - if (!config.Has("appDir") || !config["appDir"]) { config["appDir"] := A_ScriptDir } @@ -396,7 +410,6 @@ class AppBase { this.appName := config["appName"] this.versionStr := config["version"] - this.developer := config["developer"] this.appDir := config["appDir"] this.tmpDir := config["tmpDir"] this.dataDir := config["dataDir"] @@ -412,26 +425,26 @@ class AppBase { this.LoadServices(config) if (!config.Has("useShell") || config("useShell")) { - this.Service("shell") + this["shell"] } OnError(ObjBindMethod(this, "OnException")) event := AppRunEvent(Events.APP_PRE_INITIALIZE, this, config) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) this.InitializeApp(config) event := AppRunEvent(Events.APP_POST_INITIALIZE, this, config) - this.Service("manager.event").DispatchEvent(event) - - event := AppRunEvent(Events.APP_POST_STARTUP, this, config) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) event := AppRunEvent(Events.APP_PRE_RUN, this, config) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) this.RunApp(config) + + event := AppRunEvent(Events.APP_POST_STARTUP, this, config) + this["manager.event"].DispatchEvent(event) } LoadServices(config) { @@ -441,18 +454,18 @@ class AppBase { )) this.Services.LoadDefinitions(MapDefinitionLoader(config)) - sdFactory := this.Service("factory.structured_data") - serviceFile := this.Services.GetParameter("service_files.app") + sdFactory := this["factory.structured_data"] + serviceFile := this.Parameter["service_files.app"] if (FileExist(serviceFile)) { this.Services.LoadDefinitions(FileDefinitionLoader(sdFactory, serviceFile)) } - this.Service("config.app") + this["config.app"] this.InitializeTheme() this.InitializeModules(config) - for index, moduleServiceFile in this.Service("manager.module").GetModuleServiceFiles() { + for index, moduleServiceFile in this["manager.module"].GetModuleServiceFiles() { if (FileExist(moduleServiceFile)) { this.Services.LoadDefinitions(FileDefinitionLoader(sdFactory, moduleServiceFile)) } else { @@ -461,49 +474,49 @@ class AppBase { } ; Reload user config files to ensure they are the active values - this.Service("config.app").LoadConfig(true) + this["config.app"].LoadConfig(true) ; Register early event subscribers (e.g. modules) - this.Service("manager.event").RegisterServiceSubscribers(this.Services) + this["manager.event"].RegisterServiceSubscribers(this.Services) - this.Service("manager.event").Register(Events.APP_SERVICES_LOADED, "AppServices", ObjBindMethod(this, "OnServicesLoaded")) + this["manager.event"].Register(Events.APP_SERVICES_LOADED, "AppServices", ObjBindMethod(this, "OnServicesLoaded")) event := ServiceDefinitionsEvent(Events.APP_SERVICE_DEFINITIONS, "", "", config) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) if (event.Services.Count || event.Parameters.Count) { this.Services.LoadDefinitions(SimpleDefinitionLoader(event.Services, event.Parameters)) } - serviceFile := this.Services.GetParameter("service_files.user") + serviceFile := this.Parameter["service_files.user"] if (FileExist(serviceFile)) { this.Services.LoadDefinitions(FileDefinitionLoader(sdFactory, serviceFile)) } ; Register any missing late-loading event subscribers - this.Service("manager.event").RegisterServiceSubscribers(this.Services) + this["manager.event"].RegisterServiceSubscribers(this.Services) event := AppRunEvent(Events.APP_SERVICES_LOADED, this, config) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) } OnServicesLoaded(event, extra, eventName, hwnd) { - this.Service("manager.cache") - this.Service("manager.entity_type").All() - this.Service("manager.installer").RunInstallers(InstallerBase.INSTALLER_TYPE_REQUIREMENT) + this["manager.cache"] + this["manager.entity_type"].All() + this["manager.installer"].RunInstallers(InstallerBase.INSTALLER_TYPE_REQUIREMENT) } InitializeModules(config) { - includeFiles := this.Services.GetParameter("include_files") - updated := this.Service("manager.module").UpdateModuleIncludes(includeFiles["modules"], includeFiles["module_tests"]) + includeFiles := this.Parameter["include_files"] + updated := this["manager.module"].UpdateModuleIncludes(includeFiles["modules"], includeFiles["module_tests"]) if (updated) { message := A_IsCompiled ? "Your modules have been updated. Currently, you must recompile " this.appName . " yourself for the changes to take effect. Would you like to exit now (highly recommended)?" : "Your modules have been updated, and " this.appName . " must be restarted for the changes to take effect. Would you like to restart now?" - response := this.app.Service("manager.gui").Dialog(Map( + response := this.app["manager.gui"].Dialog(Map( "title", "Module Includes Updated", "text", message )) @@ -519,16 +532,16 @@ class AppBase { } InitializeTheme() { - this.Service("gdip", "manager.gui", "manager.theme") + this[["gdip", "manager.gui", "manager.theme"]] this.themeReady := true } InitializeApp(config) { A_AllowMainWindow := false - if (this.customTrayMenu) { + if (this.Parameter["app.custom_tray_menu"]) { A_TrayMenu.Delete() - this.Service("manager.event").Register(Events.AHK_NOTIFYICON, "TrayClick", ObjBindMethod(this, "OnTrayIconRightClick"), 1) + this["manager.event"].Register(Events.AHK_NOTIFYICON, "TrayClick", ObjBindMethod(this, "OnTrayIconRightClick"), 1) } } @@ -537,19 +550,19 @@ class AppBase { this.CheckForUpdates(false) } - if (this.Services.HasParameter("config_path") && !FileExist(this.Parameter("config_path"))) { + if (this.Services.HasParameter("config_path") && !FileExist(this.Parameter["config_path"])) { this.InitialSetup(config) } } OpenApp() { - mainWin := this.Parameter("config.main_window") + mainWin := this.Parameter["config.main_window"] if (mainWin) { - if (this.Service("manager.gui").Has(mainWin)) { - WinActivate("ahk_id " . this.Service("manager.gui")[mainWin].GetHwnd()) + if (this["manager.gui"].Has(mainWin)) { + WinActivate("ahk_id " . this["manager.gui"][mainWin].GetHwnd()) } else { - this.Service("manager.gui").OpenWindow(Map( + this["manager.gui"].OpenWindow(Map( "type", mainWin, "title", this.appName )) @@ -559,7 +572,7 @@ class AppBase { ExitApp() { event := AppRunEvent(Events.APP_SHUTDOWN, this) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) if (this.Services.Has("gdip")) { Gdip_Shutdown(this.Services["gdip"].GetHandle()) @@ -570,7 +583,7 @@ class AppBase { RestartApp() { event := AppRunEvent(Events.APP_RESTART, this) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) if (this.Services.Has("gdip")) { Gdip_Shutdown(this.Services["gdip"].GetHandle()) @@ -586,7 +599,7 @@ class AppBase { throw AppException("The shell is disabled, so shell commands cannot currently be run.") } - result := this.Service("shell").Exec(A_ComSpec . " /C " . command).StdOut.ReadAll() + result := this["shell"].Exec(A_ComSpec . " /C " . command).StdOut.ReadAll() if (trimOutput) { result := Trim(result, " `r`n`t") @@ -606,12 +619,12 @@ class AppBase { } for index, arrName in name { - results[arrName] := this.Service(arrName) + results[arrName] := this[arrName] } if (params && params.Length) { for index, arrName in params { - results[arrName] := this.Service(arrName) + results[arrName] := this[arrName] } } @@ -621,14 +634,15 @@ class AppBase { return this.Services.Get(name) } - Parameter(name) { - return this.Services.GetParameter(name) - } - OnException(e, mode) { extra := (e.HasProp("Extra") && e.Extra != "") ? "`n`nExtra information:`n" . e.Extra : "" occurredIn := e.What ? " in " . e.What : "" - developer := this.developer ? this.developer : "the developer(s)" + + developer := this.Parameter["app.developer"] + + if (!developer) { + developer := "the developer(s)" + } errorText := this.appName . " has experienced an unhandled exception. You can find the details below." errorText .= "`n`n" . e.Message . extra @@ -646,7 +660,7 @@ class AppBase { } if (this.Services.Has("logger")) { - this.Service("logger").Error(errorText) + this["logger"].Error(errorText) } errorText .= "`n" @@ -659,7 +673,7 @@ class AppBase { if (this.themeReady) { btns := allowContinue ? "*&Continue|&Reload|&Exit" : "*&Reload|&Exit" - this.Service("manager.gui").Dialog(Map( + this["manager.gui"].Dialog(Map( "type", "ErrorDialog", "title", "Unhandled Exception", "text", errorText, @@ -683,7 +697,7 @@ class AppBase { OnTrayIconRightClick(wParam, lParam, msg, hwnd) { if (lParam == Events.MOUSE_RIGHT_UP) { - if (this.customTrayMenu) { + if (this.Parameter["app.custom_tray_menu"]) { this.ShowTrayMenu() return 0 } @@ -691,11 +705,7 @@ class AppBase { } InitialSetup(config) { - ; Override this to set config values as needed - } - - CheckForUpdates(notify := true) { - ; Optional method to override + this.isSetup := true } ShowTrayMenu() { @@ -706,7 +716,7 @@ class AppBase { menuItems.Push(Map("label", "Restart", "name", "RestartApp")) menuItems.Push(Map("label", "Exit", "name", "ExitApp")) - result := this.Service("manager.gui").Menu(menuItems, this) + result := this["manager.gui"].Menu(menuItems, this) this.HandleTrayMenuClick(result) } @@ -730,4 +740,238 @@ class AppBase { this.ExitApp() super.__Delete() } + + MainMenu(parentGui, parentCtl, showOpenAppItem := false) { + menuItems := this.GetMainMenuItems(showOpenAppItem) + + if (menuItems.Length) { + this.HandleMainMenuClick(this["manager.gui"].Menu( + menuItems, + parentGui, + parentCtl + )) + } + } + + GetMainMenuItems(showOpenAppItem := false) { + menuItems := [] + menuItems := this.AddMainMenuEarlyItems(menuItems, showOpenAppItem) + + if (menuItems.Length) { + menuItems.Push("") + } + + length := menuItems.Length + + toolsItems := this.GetToolsMenuItems() + + if (toolsItems.Length) { + menuItems.Push(Map("label", "&Tools", "name", "ToolsMenu", "childItems", toolsItems)) + } + + aboutItems := this.GetAboutMenuItems() + + if (aboutItems.Length) { + menuItems.Push(Map("label", "&About", "name", "About", "childItems", aboutItems)) + } + + menuItems := this.AddMainMenuMiddleItems(menuItems) + + if (menuItems.Length > length) { + menuItems.Push("") + } + + length := menuItems.Length + menuItems := this.AddMainMenuLateItems(menuItems) + + if (menuItems.Length > length) { + menuItems.Push("") + } + + if (this.Parameter["app.show_restart_menu_item"]) { + menuItems.Push(Map("label", "&Restart", "name", "Reload")) + } + + menuItems.Push(Map("label", "E&xit", "name", "Exit")) + + event := MenuItemsEvent(Events.APP_MENU_ITEMS_ALTER, menuItems) + this.Dispatch(event) + menuItems := event.MenuItems + + return menuItems + } + + GetAboutMenuItems() { + aboutItems := [] + + if (this.Parameter["app.show_about_menu_item"]) { + aboutItems.Push(Map("label", "&About " . this.appName, "name", "About")) + } + + if (this.Parameter["app.show_website_menu_item"]) { + aboutItems.Push(Map("label", "Open &Website", "name", "OpenWebsite")) + } + + event := MenuItemsEvent(Events.APP_MENU_ABOUT_ITEMS_ALTER, aboutItems) + this.Dispatch(event) + aboutItems := event.MenuItems + + return aboutItems + } + + GetToolsMenuItems() { + toolsItems := this.AddEntityManagerMenuLinks([]) + event := MenuItemsEvent(Events.APP_MENU_TOOLS_ITEMS_ALTER, toolsItems) + this.Dispatch(event) + toolsItems := event.MenuItems + + return toolsItems + } + + AddMainMenuEarlyItems(menuItems, showOpenAppItem := false) { + if (showOpenAppItem) { + menuItems.Push(Map("label", "Open " . this.appName, "name", "OpenApp")) + menuItems.Push("") + } + + event := MenuItemsEvent(Events.APP_MENU_ITEMS_EARLY, menuItems) + this.Dispatch(event) + menuItems := event.MenuItems + + return menuItems + } + + AddMainMenuMiddleItems(menuItems) { + event := MenuItemsEvent(Events.APP_MENU_ITEMS_MIDDLE, menuItems) + this.Dispatch(event) + menuItems := event.MenuItems + return menuItems + } + + AddMainMenuLateItems(menuItems) { + if (this.Parameter["app.has_settings"]) { + menuItems.Push(Map("label", "&Settings", "name", "Settings")) + } + + if (this.Parameter["app.supports_update_check"]) { + menuItems.Push(Map("label", "Check for &Updates", "name", "CheckForUpdates")) + } + + event := MenuItemsEvent(Events.APP_MENU_ITEMS_LATE, menuItems) + this.Dispatch(event) + menuItems := event.MenuItems + + return menuItems + } + + AddEntityManagerMenuLinks(menuItems) { + menuEntityTypes := this._getToolsMenuEntityTypes() + + for key, entityType in menuEntityTypes { + menuLinkText := entityType.definition["manager_menu_link_text"] + + if (!menuLinkText) { + menuLinkText := "&" . entityType.definition["name_plural"] + } + + menuItems.Push(Map("label", menuLinkText, "name", "manage_" . key)) + } + + return menuItems + } + + Dispatch(event) { + this["manager.event"].DispatchEvent(event) + } + + _getToolsMenuEntityTypes() { + entityTypes := Map() + + for key, entityType in this["manager.entity_type"] { + if (entityType.definition["manager_link_in_tools_menu"]) { + entityTypes[key] := entityType + } + } + + return entityTypes + } + + HandleMainMenuClick(result) { + event := MenuResultEvent(Events.APP_MENU_PROCESS_RESULT, result) + this.Dispatch(event) + result := event.Result + + if (!event.IsFinished) { + if (result == "About") { + this.ShowAbout() + } else if (result == "OpenWebsite") { + this.OpenWebsite() + } else if (result == "Settings") { + this.ShowSettings() + } else if (result == "CheckForUpdates") { + this.CheckForUpdates() + } else if (result == "Reload") { + this.restartApp() + } else if (result == "Exit") { + this.ExitApp() + } else { + for key, entityType in this._getToolsMenuEntityTypes() { + if (result == "manage_" . key) { + this["entity_type." . key].OpenManageWindow() + break + } + } + } + } + + return result + } + + ShowSettings() { + windowName := this.Parameter["app.settings_window"] + + if (windowName) { + this["manager.gui"].Dialog(Map("type", windowName, "unique", false)) + } + } + + ShowAbout() { + windowName := this.Parameter["app.about_window"] + + if (windowName) { + this["manager.gui"].Dialog(Map("type", windowName)) + } + } + + OpenWebsite() { + websiteUrl := this.Parameter["app.website_url"] + + if (websiteUrl) { + Run(websiteUrl) + } + } + + CheckForUpdates(notify := true) { + if (this.Parameter["app.supports_update_check"]) { + updateAvailable := false + + event := ReleaseInfoEvent(Events.APP_GET_RELEASE_INFO, this) + this.Dispatch(event) + releaseInfo := event.ReleaseInfo + + if ( + releaseInfo + && releaseInfo.Has("version") + && releaseInfo["version"] + && this["version_checker"].VersionIsOutdated(releaseInfo["version"], this.Version) + ) { + updateAvailable := true + this["manager.gui"].Dialog(Map("type", "UpdateAvailableWindow"), releaseInfo) + } + + if (!updateAvailable && notify) { + this["notifier"].Info("You're running the latest version of " . this.appName . ". Shiny!") + } + } + } } diff --git a/Lib/Shared/Volantis.App/App/TestAppBase.ahk b/Lib/Shared/Volantis.App/App/TestAppBase.ahk index a5263d94..25120777 100644 --- a/Lib/Shared/Volantis.App/App/TestAppBase.ahk +++ b/Lib/Shared/Volantis.App/App/TestAppBase.ahk @@ -1,13 +1,13 @@ class TestAppBase extends AppBase { ExitApp() { event := AppRunEvent(Events.APP_SHUTDOWN, this) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) ; Don't actually exit } RestartApp() { event := AppRunEvent(Events.APP_SHUTDOWN, this) - this.Service("manager.event").DispatchEvent(event) + this["manager.event"].DispatchEvent(event) ; Don't actually restart } } diff --git a/Lib/Shared/Volantis.App/BulkOperation/BulkOperationBase.ahk b/Lib/Shared/Volantis.App/BulkOperation/BulkOperationBase.ahk index a9406e35..6f85723d 100644 --- a/Lib/Shared/Volantis.App/BulkOperation/BulkOperationBase.ahk +++ b/Lib/Shared/Volantis.App/BulkOperation/BulkOperationBase.ahk @@ -40,7 +40,7 @@ class BulkOperationBase { } if (this.app.Services.Has("logger")) { - this.app.Service("logger").Debug(Type(this) . ": Starting bulk operation...") + this.app["logger"].Debug(Type(this) . ": Starting bulk operation...") } this.running := true @@ -56,7 +56,7 @@ class BulkOperationBase { LogResults() { if (this.app.Services.Has("logger")) { - this.app.Service("logger").Info(Type(this) . " Results: " . this.GetResultMessage()) + this.app["logger"].Info(Type(this) . " Results: " . this.GetResultMessage()) } } @@ -89,7 +89,7 @@ class BulkOperationBase { ownerOrParent := this.parent } - this.progress := this.app.Service("manager.gui").OpenWindow(Map( + this.progress := this.app["manager.gui"].OpenWindow(Map( "type", "ProgressIndicator", "title", this.progressTitle, "text", this.progressText, @@ -115,7 +115,7 @@ class BulkOperationBase { Notify() { if (this.shouldNotify && this.app.Services.Has("notifier")) { - this.app.Service("notifier").Info(this.GetResultMessage()) + this.app["notifier"].Info(this.GetResultMessage()) } } diff --git a/Lib/Shared/Volantis.App/BulkOperation/InstallOp/InstallOp.ahk b/Lib/Shared/Volantis.App/BulkOperation/InstallOp/InstallOp.ahk index 6c4d0aad..b093857a 100644 --- a/Lib/Shared/Volantis.App/BulkOperation/InstallOp/InstallOp.ahk +++ b/Lib/Shared/Volantis.App/BulkOperation/InstallOp/InstallOp.ahk @@ -17,7 +17,7 @@ class InstallOp extends BulkOperationBase { for index, name in this.installers { name := "installer." . name - installer := this.app.Service(name) + installer := this.app[name] if (!HasBase(installer, InstallerBase.Prototype)) { throw AppException("Provided installer is not valid: " . name) diff --git a/Lib/Shared/Volantis.App/DataSource/DataSourceBase.ahk b/Lib/Shared/Volantis.App/DataSource/DataSourceBase.ahk deleted file mode 100644 index 91c1fdce..00000000 --- a/Lib/Shared/Volantis.App/DataSource/DataSourceBase.ahk +++ /dev/null @@ -1,70 +0,0 @@ -class DataSourceBase { - cache := "" - useCache := false - maxCacheAge := 86400 - - __New(cacheManager := "", cacheName := "") { - if (cacheManager != "" && cacheName != "") { - InvalidParameterException.CheckTypes("DataSourceBase", "cacheManager", cacheManager, "CacheManager") - this.useCache := true - this.cache := cacheManager[cacheName] - } - } - - ItemExists(path) { - return this.useCache ? this.cache.ItemExists(path) : false - } - - ReadItem(path, private := false, maxCacheAge := "") { - if (maxCacheAge == "") { - maxCacheAge := this.maxCacheAge - } - - item := "" - - if (this.ItemNeedsRetrieval(path)) { - item := this.RetrieveItem(path, private, maxCacheAge) - } else if (this.useCache) { - item := this.cache.ReadItem(path) - } - - return item - } - - ItemNeedsRetrieval(path) { - return (!this.useCache || this.cache.ItemNeedsUpdate(path)) - } - - RetrieveItem(path, private := false, maxCacheAge := "") { - return "" - } - - CopyItem(path, destination) { - if (this.ItemNeedsRetrieval(path)) { - this.RetrieveItem(path) - } - - return this.useCache ? this.cache.CopyItem(path, destination) : destination - } - - GetRemoteLocation(path) { - return path - } - - ReadListing(path) { - listingInstance := DSListing(path, this) - - listing := [] - - if (listingInstance.Exists()) { - listing := listingInstance.Read() - } - - return listing - } - - ReadJson(key, path := "") { - dsItem := DSJson(key, path, this) - return dsItem.Read() - } -} diff --git a/Lib/Shared/Volantis.App/DataSourceItem/DSAssetFile.ahk b/Lib/Shared/Volantis.App/DataSourceItem/DSAssetFile.ahk deleted file mode 100644 index a5165b1f..00000000 --- a/Lib/Shared/Volantis.App/DataSourceItem/DSAssetFile.ahk +++ /dev/null @@ -1,3 +0,0 @@ -class DSAssetFile extends DSFile { - allowRead := false -} diff --git a/Lib/Shared/Volantis.App/DataSourceItem/DSFile.ahk b/Lib/Shared/Volantis.App/DataSourceItem/DSFile.ahk deleted file mode 100644 index 187d931b..00000000 --- a/Lib/Shared/Volantis.App/DataSourceItem/DSFile.ahk +++ /dev/null @@ -1,7 +0,0 @@ -class DSFile extends DataSourceItemBase { - allowRead := true ; Some files are only meant to be copied - - Read() { - return this.allowRead ? super.Read() : "" - } -} diff --git a/Lib/Shared/Volantis.App/DataSourceItem/DSJson.ahk b/Lib/Shared/Volantis.App/DataSourceItem/DSJson.ahk deleted file mode 100644 index 991c41dc..00000000 --- a/Lib/Shared/Volantis.App/DataSourceItem/DSJson.ahk +++ /dev/null @@ -1,17 +0,0 @@ -class DSJson extends DSFile { - itemSuffix := "" - dataType := "Map" - - Read() { - content := super.Read() - dataType := this.dataType - obj := %dataType%() - - if (content) { - data := JsonData() - obj := data.FromString(&content) - } - - return obj - } -} diff --git a/Lib/Shared/Volantis.App/DataSourceItem/DSListing.ahk b/Lib/Shared/Volantis.App/DataSourceItem/DSListing.ahk deleted file mode 100644 index 1d48a12c..00000000 --- a/Lib/Shared/Volantis.App/DataSourceItem/DSListing.ahk +++ /dev/null @@ -1,7 +0,0 @@ -class DSListing extends DSJson { - dataType := "Array" - - __New(path, dataSourceKey := "") { - super.__New(path, "", dataSourceKey) - } -} diff --git a/Lib/Shared/Volantis.App/DataSourceItem/DataSourceItemBase.ahk b/Lib/Shared/Volantis.App/DataSourceItem/DataSourceItemBase.ahk deleted file mode 100644 index ef0ff3ea..00000000 --- a/Lib/Shared/Volantis.App/DataSourceItem/DataSourceItemBase.ahk +++ /dev/null @@ -1,53 +0,0 @@ -class DataSourceItemBase { - endpoint := "" - basePath := "" - itemSuffix := "" - path := "" - key := "" - - __New(key, path := "", dataSource := "") { - InvalidParameterException.CheckTypes("DataSourceItemBase", "key", key, "", "path", path, "") - InvalidParameterException.CheckEmpty("DataSourceItemBase", "key", key) - InvalidParameterException.CheckTypes("DataSourceItemBase", "dataSource", dataSource, "DataSourceBase") - - this.endpoint := dataSource - this.key := key - this.path := path - } - - GetPath(includeFilename := true) { - path := this.basePath - - if (path != "" && this.path != "") { - path .= "/" - } - - path .= this.path - - if (includeFilename) { - if (path) { - path .= "/" - } - - path .= this.key . this.itemSuffix - } - - return path - } - - GetRemoteLocation() { - return this.endpoint.GetRemoteLocation(this.GetPath()) - } - - Exists() { - return this.endpoint.ItemExists(this.GetPath()) - } - - Read() { - return this.endpoint.ReadItem(this.GetPath()) - } - - Copy(destination) { - return this.endpoint.CopyItem(this.GetPath(), destination) - } -} diff --git a/Lib/Shared/Volantis.App/Entity/AppEntityBase.ahk b/Lib/Shared/Volantis.App/Entity/AppEntityBase.ahk deleted file mode 100644 index 5b0e84ce..00000000 --- a/Lib/Shared/Volantis.App/Entity/AppEntityBase.ahk +++ /dev/null @@ -1,195 +0,0 @@ -class AppEntityBase extends FieldableEntity { - app := "" - dataSourcePath := "" - existsInDataSource := false - - __New(app, id, entityTypeId, container, eventMgr, storageObj, idSanitizer, parentEntity := "") { - this.app := app - - super.__New(id, entityTypeId, container, eventMgr, storageObj, idSanitizer, parentEntity) - } - - static Create(container, eventMgr, id, entityTypeId, storageObj, idSanitizer, parentEntity := "") { - className := this.Prototype.__Class - - return %className%( - container.GetApp(), - id, - entityTypeId, - container, - eventMgr, - storageObj, - idSanitizer, - parentEntity - ) - } - - GetDefaultFieldGroups() { - groups := super.GetDefaultFieldGroups() - - groups["advanced"] := Map( - "name", "Advanced", - "weight", 100 - ) - - groups["api"] := Map( - "name", "API", - "weight", 150 - ) - - return groups - } - - BaseFieldDefinitions() { - definitions := super.BaseFieldDefinitions() - - definitions["DataSourceKeys"] := Map( - "description", "The data source keys to load defaults from, in order.", - "help", "The default data source is 'api' which connects to the default api endpoint (Which can be any HTTP location compatible with Launchpad's API format)", - "default", [this.app.Config["data_source_key"]], - "multiple", true, - "group", "api", - "processValue", false, - "modes", Map("simple", Map("formField", false)) - ) - - definitions["DataSourceItemKey"] := Map( - "description", "The key that is used to look up the entity's data from configured external data sources.", - "help", "It defaults to the key which is usually sufficient, but it can be overridden by setting this value.`n`nAddtionally, multiple copies of the same data source entity can exist by giving them different keys but using the same DataSourceKey", - "group", "api", - "processValue", false, - "modes", Map("simple", Map("formField", false)) - ) - - definitions["AssetsDir"] := Map( - "type", "directory", - "description", "The directory where any required assets for this entity will be saved.", - "default", this.app.Config["assets_dir"] . "\" . this.Id, - "group", "advanced", - "formField", false, - "modes", Map("simple", Map("formField", false)) - ) - - definitions["DependenciesDir"] := Map( - "type", "directory", - "description", "The directory where dependencies which have been installed for this entity can be accessed.", - "default", this.app.appDir . "\Vendor", - "group", "advanced", - "required", true, - "formField", false, - "modes", Map("simple", Map("formField", false)) - ) - - return definitions - } - - _getLayerNames() { - layerNames := super._getLayerNames() - layerNames.Push("ds") - - return layerNames - } - - _getLayerSources() { - layerSources := super._getLayerSources() - layerSources["ds"] := ObjBindMethod(this, "AggregateDataSourceDefaults") - - return layerSources - } - - UpdateDataSourceDefaults(recurse := true) { - ; @todo Move this to a module - this.GetData().UnloadLayer("ds") - - if (recurse) { - for key, child in this.GetReferencedEntities(true) { - child.UpdateDataSourceDefaults(recurse) - } - } - } - - AggregateDataSourceDefaults(includeParentData := true, includeChildData := true) { - defaults := (this.parentEntity != "" && includeParentData) - ? this.parentEntity.AggregateDataSourceDefaults(includeParentData, false) - : Map() - - ; @todo Uncomment if needed, remove if not - ;this.GetData().SetLayer("ds", defaults) - - for index, dataSource in this.GetAllDataSources() { - defaults := this.merger.Merge(this.GetDataSourceDefaults(dataSource), defaults) - } - - if (includeChildData) { - for key, child in this.GetReferencedEntities(true) { - defaults := this.merger.Merge(child.AggregateDataSourceDefaults(false, includeChildData), defaults) - } - } - - return defaults - } - - GetAllDataSources() { - dataSources := Map() - - if (this.Has("DataSourceKeys", false)) { - dataSourceKeys := this["DataSourceKeys"] - - if (!HasBase(dataSourceKeys, Array.Prototype)) { - dataSourceKeys := [dataSourceKeys] - } - - for index, dataSourceKey in dataSourceKeys { - if (this.app.Service("manager.data_source").Has(dataSourceKey)) { - dataSource := this.app.Service("manager.data_source")[dataSourceKey] - - if (dataSource) { - dataSources[dataSourceKey] := dataSource - } - } - } - } - - return dataSources - } - - GetDataSourceDefaults(dataSource) { - defaults := Map() - itemKey := this.DiscoverDataSourceItemKey() - - if (itemKey) { - dsData := dataSource.ReadJson(itemKey, this.GetDataSourceItemPath()) - - if (dsData) { - this.existsInDataSource := true - - if (dsData.Has("data")) { - dsData := dsData["data"] - } - - if (dsData.Has("defaults")) { - defaults := this.merger.Merge(dsData["defaults"], defaults) - defaults := this.MergeAdditionalDataSourceDefaults(defaults, dsData) - } - } - } - - return defaults - } - - DiscoverDataSourceItemKey() { - return this.Id - } - - GetDataSourceItemPath() { - return this.dataSourcePath - } - - MergeAdditionalDataSourceDefaults(defaults, dataSourceData) { - return defaults - } - - GetAssetPath(filePath) { - return this["AssetsDir"] . "\" . filePath - } -} diff --git a/Lib/Shared/Volantis.App/Entity/BackupEntity.ahk b/Lib/Shared/Volantis.App/Entity/BackupEntity.ahk index 9ff0d0b0..9dfc0cf0 100644 --- a/Lib/Shared/Volantis.App/Entity/BackupEntity.ahk +++ b/Lib/Shared/Volantis.App/Entity/BackupEntity.ahk @@ -1,11 +1,12 @@ -class BackupEntity extends AppEntityBase { +class BackupEntity extends FieldableEntity { backup := "" - __New(app, key, config, parentEntity := "", requiredConfigKeys := "") { - super.__New(app, key, config, parentEntity, requiredConfigKeys) - backupClass := config.Has("BackupClass") ? config["BackupClass"] : "FileBackup" + SetupEntity() { + super.SetupEntity() if (!this.backup) { + backupClass := this.config.Has("BackupClass") ? this.config["BackupClass"] : "FileBackup" + this.CreateBackupObject(backupClass) } } @@ -13,10 +14,6 @@ class BackupEntity extends AppEntityBase { BaseFieldDefinitions() { definitions := super.BaseFieldDefinitions() - if (definitions.Has("DataSourceKeys")) { - definitions["DataSourceKeys"]["default"] := [] - } - definitions["IsEditable"] := Map( "type", "boolean", "default", true @@ -25,7 +22,7 @@ class BackupEntity extends AppEntityBase { definitions["IconSrc"] := Map( "type", "icon_file", "description", "The path to this an icon (.ico or .exe).", - "default", this.app.Service("manager.theme")[].GetIconPath("Backup") + "default", this.app["manager.theme"][].GetIconPath("backup") ) definitions["Source"] := Map( @@ -96,12 +93,12 @@ class BackupEntity extends AppEntityBase { this.CreateBackupObject() } - AutoDetectValues(recurse := true) { + AutoDetectValues() { if (!this.backup) { this.CreateBackupObject() } - detectedValues := super.AutoDetectValues(recurse) + detectedValues := super.AutoDetectValues() return detectedValues } diff --git a/Lib/Shared/Volantis.App/Entity/TaskEntity.ahk b/Lib/Shared/Volantis.App/Entity/TaskEntity.ahk index c8a5b9b7..3f294960 100644 --- a/Lib/Shared/Volantis.App/Entity/TaskEntity.ahk +++ b/Lib/Shared/Volantis.App/Entity/TaskEntity.ahk @@ -1,3 +1,3 @@ -class TaskEntity extends AppEntityBase { +class TaskEntity extends FieldableEntity { } diff --git a/Lib/Shared/Volantis.App/Event/MenuItemsEvent.ahk b/Lib/Shared/Volantis.App/Event/MenuItemsEvent.ahk new file mode 100644 index 00000000..23c1f89f --- /dev/null +++ b/Lib/Shared/Volantis.App/Event/MenuItemsEvent.ahk @@ -0,0 +1,16 @@ +class MenuItemsEvent extends EventBase { + menuItemsObj := "" + + MenuItems { + get => this.menuItemsObj + } + + __New(eventName, menuItems := "") { + if (!menuItems) { + menuItems := [] + } + + this.menuItemsObj := menuItems + super.__New(eventName) + } +} diff --git a/Lib/Shared/Volantis.App/Event/MenuResultEvent.ahk b/Lib/Shared/Volantis.App/Event/MenuResultEvent.ahk new file mode 100644 index 00000000..8d9011d6 --- /dev/null +++ b/Lib/Shared/Volantis.App/Event/MenuResultEvent.ahk @@ -0,0 +1,20 @@ +class MenuResultEvent extends EventBase { + resultItem := "" + finished := false + + Result { + get => this.resultItem + set => this.resultItem := value + } + + IsFinished { + get => this.finished + set => !!(value) + } + + __New(eventName, result) { + this.resultItem := result + + super.__New(eventName) + } +} diff --git a/Lib/Shared/Volantis.App/Event/ReleaseInfoEvent.ahk b/Lib/Shared/Volantis.App/Event/ReleaseInfoEvent.ahk new file mode 100644 index 00000000..84af7d5c --- /dev/null +++ b/Lib/Shared/Volantis.App/Event/ReleaseInfoEvent.ahk @@ -0,0 +1,23 @@ +class ReleaseInfoEvent extends EventBase { + appObj := "" + releaseInfoObj := Map() + + App { + get => this.appObj + } + + ReleaseInfo { + get => this.releaseInfoObj + set => this.releaseInfoObj := value + } + + __New(eventName, app, releaseInfo := "") { + this.appObj := app + + if (releaseInfo) { + this.releaseInfoObj := releaseInfo + } + + super.__New(eventName) + } +} diff --git a/Lib/Shared/Volantis.App/Events/Events.ahk b/Lib/Shared/Volantis.App/Events/Events.ahk index 35992f39..74ee9b65 100644 --- a/Lib/Shared/Volantis.App/Events/Events.ahk +++ b/Lib/Shared/Volantis.App/Events/Events.ahk @@ -19,5 +19,15 @@ class Events { static APP_SHUTDOWN := 0x1020 static APP_RESTART := 0x1025 + static APP_MENU_ITEMS_EARLY := 0x1030 + static APP_MENU_ITEMS_MIDDLE := 0x1032 + static APP_MENU_ITEMS_LATE := 0x1034 + static APP_MENU_ITEMS_ALTER := 0x1036 + static APP_MENU_TOOLS_ITEMS_ALTER := 0x1038 + static APP_MENU_ABOUT_ITEMS_ALTER := 0x1040 + static APP_MENU_PROCESS_RESULT := 0x1042 + + static APP_GET_RELEASE_INFO := 0x1050 + static AHK_NOTIFYICON := 0x404 } diff --git a/Lib/Shared/Volantis.App/Gui/Dialog/EntityDeleteWindow.ahk b/Lib/Shared/Volantis.App/Gui/Dialog/EntityDeleteWindow.ahk index 73d274dc..f5a6a6cd 100644 --- a/Lib/Shared/Volantis.App/Gui/Dialog/EntityDeleteWindow.ahk +++ b/Lib/Shared/Volantis.App/Gui/Dialog/EntityDeleteWindow.ahk @@ -2,7 +2,6 @@ entityObj := "" entityManager := "" missingFields := Map() - dataSource := "" __New(container, themeObj, config, entityObj, entityManager) { this.entityObj := entityObj diff --git a/Lib/Shared/Volantis.App/Gui/Dialog/ErrorDialog.ahk b/Lib/Shared/Volantis.App/Gui/Dialog/ErrorDialog.ahk index 566e0964..a97a6e87 100644 --- a/Lib/Shared/Volantis.App/Gui/Dialog/ErrorDialog.ahk +++ b/Lib/Shared/Volantis.App/Gui/Dialog/ErrorDialog.ahk @@ -1,7 +1,6 @@ class ErrorDialog extends DialogBox { errorObj := "" notifierObj := "" - apiEndpoint := "" formShown := false formH := 0 guiH := 0 @@ -9,15 +8,6 @@ class ErrorDialog extends DialogBox { __New(container, themeObj, config, errorObj) { this.errorObj := errorObj this.notifierObj := container.Get("notifier").notifierObj - - if (container.Has("manager.data_source")) { - dsManager := container.Get("manager.data_source") - - if (dsManager.GetDefaultDataSource()) { - this.apiEndpoint := container.Get("manager.data_source").GetDefaultDataSource() - } - } - this.formShown := config.Has("submitError") ? config["submitError"] : false super.__New(container, themeObj, config) @@ -118,9 +108,14 @@ class ErrorDialog extends DialogBox { SendError() { global appVersion - if (this.apiEndpoint) { - endpoint := this.apiEndpoint.endpointUrl . "/submit-error" + ; @todo Move the API connection stuff into the LaunchpadApi module + filters := "error_submission" + operation := "create" + if ( + this.container.Has("web_services.adapter_manager") + && this.container["web_services.adapter_manager"].HasAdapters(filters, operation) + ) { body := Map() body["message"] := this.errorObj.Message body["what"] := this.errorObj.What @@ -132,12 +127,25 @@ class ErrorDialog extends DialogBox { body["version"] := appVersion ? appVersion : "" body["details"] := this.guiObj["ErrorDetails"].Text - request := WinHttpReq(endpoint) - response := request.Send("POST", body) - success := !!(request.GetStatusCode() == 200) + results := this.container["web_services.adapter_manager"].AdapterRequest( + Map("data", body), + filters, + operation, + true + ) + + success := false + + for adapterId, adapterResult in results { + if (adapterResult) { + success := true + + break + } + } - notification := success ? "Successfully sent error to Volantis Development" : "Failed to send error to Volantis Development" - this.notifierObj.Notify(notification, "Error Sent", success ? "info" : "error") + notification := success ? "Successfully sent error details for further investigation" : "Failed to send error details" + this.notifierObj.Notify(notification, "Error Submission", success ? "info" : "error") } } } diff --git a/Lib/Shared/Volantis.App/Gui/Dialog/UpdateAvailableWindow.ahk b/Lib/Shared/Volantis.App/Gui/Dialog/UpdateAvailableWindow.ahk index 8e0a2a83..d0d8c4a1 100644 --- a/Lib/Shared/Volantis.App/Gui/Dialog/UpdateAvailableWindow.ahk +++ b/Lib/Shared/Volantis.App/Gui/Dialog/UpdateAvailableWindow.ahk @@ -21,9 +21,9 @@ class UpdateAvailableWindow extends FormGuiBase { super.Controls() this.guiObj.AddText("w" . this.windowSettings["contentWidth"] . " y+" . (this.margin*2), "Current version: " . appVersion) this.SetFont("normal", "Bold") - this.guiObj.AddText("w" . this.windowSettings["contentWidth"] . " y+" . (this.margin), "Latest version: " . this.releaseInfo["data"]["version"]) + this.guiObj.AddText("w" . this.windowSettings["contentWidth"] . " y+" . (this.margin), "Latest version: " . this.releaseInfo["version"]) this.SetFont() - this.guiObj.AddLink("w" . this.windowSettings["contentWidth"] . " y+" . (this.margin), 'View release notes') + this.guiObj.AddLink("w" . this.windowSettings["contentWidth"] . " y+" . (this.margin), 'View release notes') this.guiObj.AddText("w" . this.windowSettings["contentWidth"] . " y+" . (this.margin*2), "Would you like to update " . this.app.appName . " now?") } @@ -36,14 +36,14 @@ class UpdateAvailableWindow extends FormGuiBase { } ApplyUpdate() { - downloadUrl := this.releaseInfo["data"].Has("installer") ? this.releaseInfo["data"]["installer"] : "" + downloadUrl := this.releaseInfo.Has("installer") ? this.releaseInfo["installer"] : "" if (!DirExist(this.app.tmpDir . "\Installers")) { DirCreate(this.app.tmpDir . "\Installers") } if (downloadUrl) { - localFile := this.app.tmpDir . "\Installers\" . this.app.appName . "-" . this.releaseInfo["data"]["version"] . ".exe" + localFile := this.app.tmpDir . "\Installers\" . this.app.appName . "-" . this.releaseInfo["version"] . ".exe" FileDelete(this.app.tmpDir . "\Installers\" . this.app.appName . "-*") Download(downloadUrl, localFile) Run(localFile) diff --git a/Lib/Shared/Volantis.App/Gui/EntityEditor/EntityEditorBase.ahk b/Lib/Shared/Volantis.App/Gui/EntityEditor/EntityEditorBase.ahk index 7ad3903b..fd681036 100644 --- a/Lib/Shared/Volantis.App/Gui/EntityEditor/EntityEditorBase.ahk +++ b/Lib/Shared/Volantis.App/Gui/EntityEditor/EntityEditorBase.ahk @@ -9,7 +9,6 @@ class EntityEditorBase extends FormGuiBase { entityObj := "" missingFields := Map() - dataSource := "" entityFormFactory := "" entityForm := "" @@ -75,9 +74,4 @@ class EntityEditorBase extends FormGuiBase { AddEntityCtl(heading, fieldName, showDefaultCheckbox, params*) { return this.Add("EntityControl", "", heading, this.entityObj, fieldName, showDefaultCheckbox, params*) } - - Create() { - super.Create() - this.dataSource := this.app.Service("manager.data_source").GetDefaultDataSource() - } } diff --git a/Lib/Shared/Volantis.App/Gui/GuiBase.ahk b/Lib/Shared/Volantis.App/Gui/GuiBase.ahk index 04c1ab3b..412fb7eb 100644 --- a/Lib/Shared/Volantis.App/Gui/GuiBase.ahk +++ b/Lib/Shared/Volantis.App/Gui/GuiBase.ahk @@ -30,10 +30,12 @@ class GuiBase { isShown := false config := "" merger := "" + addedControls := [] GetDefaultConfig(container, config) { return Map( "id", Type(this), + "resizable", false, "titlebar", true, "waitForResult", false, "titleIsMenu", false, @@ -81,6 +83,10 @@ class GuiBase { extraOptions["Border"] := true } + if (this.config["resizable"]) { + extraOptions["Resize"] := true + } + if (this.owner != "") { extraOptions["Owner" . this.owner.Hwnd] := true } @@ -106,7 +112,6 @@ class GuiBase { this.margin := this.windowSettings["spacing"]["margin"] this.guiId := this.config["id"] - this.RegisterCallbacks() this.Create() } @@ -124,7 +129,7 @@ class GuiBase { RegisterCallbacks() { guiId := "Gui" . this.guiId - this.app.Service("manager.event") + this.app["manager.event"] .Register(Events.MOUSE_MOVE, guiId, ObjBindMethod(this, "OnMouseMove")) .Register(Events.WM_NCCALCSIZE, guiId, ObjBindMethod(this, "OnCalcSize")) .Register(Events.WM_NCACTIVATE, guiId, ObjBindMethod(this, "OnActivate")) @@ -137,10 +142,10 @@ class GuiBase { __Delete() { if (this.app) { - this.app.Service("manager.event").Unregister(Events.MOUSE_MOVE, "Gui" . this.guiId) - this.app.Service("manager.event").Unregister(Events.WM_NCCALCSIZE, "Gui" . this.guiId) - this.app.Service("manager.event").Unregister(Events.WM_NCACTIVATE, "Gui" . this.guiId) - this.app.Service("manager.event").Unregister(Events.WM_NCHITTEST, "Gui" . this.guiId) + this.app["manager.event"].Unregister(Events.MOUSE_MOVE, "Gui" . this.guiId) + this.app["manager.event"].Unregister(Events.WM_NCCALCSIZE, "Gui" . this.guiId) + this.app["manager.event"].Unregister(Events.WM_NCACTIVATE, "Gui" . this.guiId) + this.app["manager.event"].Unregister(Events.WM_NCHITTEST, "Gui" . this.guiId) } if (this.activeTooltip) { @@ -169,7 +174,9 @@ class GuiBase { } Add(ctlClass, options := "", params*) { - return %ctlClass%(this, options, params*) + ctlObj := %ctlClass%(this, options, params*) + this.addedControls.Push(ctlObj) + return ctlObj } OnCalcSize(wParam, lParam, msg, hwnd) { @@ -342,20 +349,12 @@ class GuiBase { return this.guiObj.AddEdit(opts, defaultValue) } - UpdateStatusIndicator() { + UpdateStatusIndicator(webService) { if (this.config["showStatusIndicator"]) { - this.titlebar.statusIndicator.UpdateStatusIndicator(this.GetStatusInfo(), this.StatusWindowIsOnline() ? "status" : "statusOffline") + webService.UpdateStatusIndicators() } } - StatusWindowIsOnline() { - return false - } - - GetStatusInfo() { - return Map("name", "", "photo", "") - } - SetFont(fontPreset := "normal", extraStyles := "", colorName := "text") { this.guiObj.SetFont() this.guiObj.SetFont("c" . this.themeObj.GetColor(colorName) . " " . this.themeObj.GetFont(fontPreset) . " " . extraStyles) @@ -568,22 +567,22 @@ class GuiBase { } if (!this.isClosed && WinExist("ahk_id " . this.guiObj.Hwnd)) { - this.app.Service("manager.gui").StoreWindowState(this) + this.app["manager.gui"].StoreWindowState(this) WinClose("ahk_id " . this.guiObj.Hwnd) } else { this.Destroy() } - this.app.Service("manager.gui").CleanupWindow(this.guiId) + this.app["manager.gui"].CleanupWindow(this.guiId) } Destroy() { if (!this.isClosed && this.config["saveWindowState"]) { - this.app.Service("manager.gui").StoreWindowState(this) + this.app["manager.gui"].StoreWindowState(this) } if (this.owner) { - this.app.Service("manager.gui").ReleaseFromParent(this.guiId) + this.app["manager.gui"].ReleaseFromParent(this.guiId) } this.Cleanup() @@ -595,7 +594,7 @@ class GuiBase { } Cleanup() { - this.app.Service("manager.gui").UnloadComponent(this.guiId) + this.app["manager.gui"].UnloadComponent(this.guiId) ; Extend to clear any global variables used } @@ -762,8 +761,12 @@ class GuiBase { } OnSize(guiObj, minMax, width, height) { - if (this.config["titlebar"]) { - this.titlebar.OnSize(minMax, width, height) + for index, ctlObj in this.addedControls { + ctlObj.OnSize(guiObj, minMax, width, height) } + + ; if (this.config["titlebar"]) { + ; this.titlebar.OnSize(minMax, width, height) + ; } } } diff --git a/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageEntitiesWindow.ahk b/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageEntitiesWindow.ahk index 4d18b352..1a4b3c09 100644 --- a/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageEntitiesWindow.ahk +++ b/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageEntitiesWindow.ahk @@ -24,6 +24,7 @@ class ManageEntitiesWindow extends ManageWindowBase { defaults := super.GetDefaultConfig(container, config) defaults["entity_type"] := this.entityTypeId defaults["title"] := this.entityType.definition["name_plural"] + return defaults } @@ -74,10 +75,9 @@ class ManageEntitiesWindow extends ManageWindowBase { defaultIcon := this.themeObj.GetIconPath(defaultIconName) iconNum := 1 - iconField := this.entityType.definition["icon_field"] for key, entityObj in this.entityMgr { - iconSrc := entityObj[iconField] + iconSrc := this.GetEntityIconSrc(entityObj) if (!InStr(iconSrc, ":\")) { iconSrc := this.themeObj.GetIconPath(iconSrc) @@ -94,6 +94,17 @@ class ManageEntitiesWindow extends ManageWindowBase { return IL } + GetEntityIconSrc(entityObj) { + iconSrc := "" + iconField := this.entityType.definition["icon_field"] + + if (iconField && entityObj.Has(iconField)) { + iconSrc := entityObj[iconField] + } + + return iconSrc + } + OnDoubleClick(LV, rowNum) { key := this.listView.GetRowKey(rowNum) @@ -149,31 +160,35 @@ class ManageEntitiesWindow extends ManageWindowBase { this.AutoXYWH("y", ["AddButton"]) } - GetContextMenuItems() { + GetContextMenuItems(entityObj) { definition := this.entityType.definition menuItems := [] - if (definition["allow_view"]) { + if (definition["allow_view"] && this._shouldShowButton(entityObj, "ViewEntity")) { menuItems.Push(Map("label", "&View", "name", "ViewEntity")) } - if (definition["allow_edit"]) { + if (definition["allow_edit"] && this._shouldShowButton(entityObj, "EditEntity")) { menuItems.Push(Map("label", "Edit", "name", "EditEntity")) } - if (definition["allow_delete"]) { + if (definition["allow_delete"] && this._shouldShowButton(entityObj, "DeleteEntity")) { menuItems.Push(Map("label", "Delete", "name", "DeleteEntity")) } return menuItems } + _shouldShowButton(entityObj, buttonName) { + return true + } + ShowListViewContextMenu(lv, item, isRightClick, X, Y) { key := this.listView.GetRowKey(item) entityObj := this.entityMgr[key] - menuItems := this.GetContextMenuItems() - result := this.app.Service("manager.gui").Menu(menuItems, this) + menuItems := this.GetContextMenuItems(entityObj) + result := this.app["manager.gui"].Menu(menuItems, this) this.ProcessContextMenuResult(result, key) } diff --git a/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageWindowBase.ahk b/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageWindowBase.ahk index 56c85348..72cb7f01 100644 --- a/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageWindowBase.ahk +++ b/Lib/Shared/Volantis.App/Gui/ManageWindow/ManageWindowBase.ahk @@ -6,11 +6,13 @@ lvWidth := 0 showDetailsPane := false detailsFields := [] + lvResizeOpts := "wh" GetDefaultConfig(container, config) { defaults := super.GetDefaultConfig(container, config) defaults["frameShadow"] := false defaults["saveWindowState"] := true + defaults["resizable"] := true return defaults } @@ -74,8 +76,7 @@ opts.Push("w" . this.lvWidth) } - this.listView := this.Add("ListViewControl", opts, "", this.listViewColumns, "GetListViewData", "GetListViewImgList", "InitListView", "ShouldHighlightRow") - this.listView.resizeOpts := "h" + this.listView := this.Add("ListViewControl", opts, "", this.listViewColumns, "GetListViewData", "GetListViewImgList", "InitListView", "ShouldHighlightRow", this.lvResizeOpts) return this.listView } diff --git a/Lib/Shared/Volantis.App/Gui/Menu/MenuGui.ahk b/Lib/Shared/Volantis.App/Gui/Menu/MenuGui.ahk index 13f2c878..a7b5d689 100644 --- a/Lib/Shared/Volantis.App/Gui/Menu/MenuGui.ahk +++ b/Lib/Shared/Volantis.App/Gui/Menu/MenuGui.ahk @@ -133,7 +133,7 @@ if (btn.ChildItems) { this.childOpen := true - this.result := this.app.Service("manager.gui").Menu(Map( + this.result := this.app["manager.gui"].Menu(Map( "parent", this, "child", true, "openAtCtlSide", "right" diff --git a/Lib/Shared/Volantis.App/GuiControl/EntityControl.ahk b/Lib/Shared/Volantis.App/GuiControl/EntityControl.ahk index fb3688c8..7c159b01 100644 --- a/Lib/Shared/Volantis.App/GuiControl/EntityControl.ahk +++ b/Lib/Shared/Volantis.App/GuiControl/EntityControl.ahk @@ -66,7 +66,7 @@ class EntityControl extends GuiControlBase { this.widget.WriteValueToEntity() if (this.refreshDataOnChange && (!this.dependentFields || this.dependentFields.Length == 0)) { - this.entityObj.UpdateDataSourceDefaults() + this.entityObj.UpdateDefaults() } this.SetDependentFieldValues() @@ -117,7 +117,7 @@ class EntityControl extends GuiControlBase { SetDependentFieldValues() { if (this.dependentFields && this.dependentFields.Length > 0) { - this.entityObj.UpdateDataSourceDefaults() + this.entityObj.UpdateDefaults() for index, field in this.dependentFields { this.guiObj.guiObj[field].Value := this.entityObj.GetField(field).GetRawValue() diff --git a/Lib/Shared/Volantis.App/GuiControl/GuiControlBase.ahk b/Lib/Shared/Volantis.App/GuiControl/GuiControlBase.ahk index 399d18cc..9d58675e 100644 --- a/Lib/Shared/Volantis.App/GuiControl/GuiControlBase.ahk +++ b/Lib/Shared/Volantis.App/GuiControl/GuiControlBase.ahk @@ -1,5 +1,6 @@ class GuiControlBase { app := "" + container := "" guiObj := "" ctl := "" defaultH := 20 @@ -12,6 +13,7 @@ class GuiControlBase { __New(guiObj, options := "", heading := "", params*) { InvalidParameterException.CheckTypes("GuiControlBase", "guiObj", guiObj, "GuiBase") this.app := guiObj.app + this.container := this.app.Services this.guiObj := guiObj if (HasBase(options, GuiControlParameters.Prototype)) { diff --git a/Lib/Shared/Volantis.App/GuiControl/ListViewControl.ahk b/Lib/Shared/Volantis.App/GuiControl/ListViewControl.ahk index 2d5c06f8..650cad6e 100644 --- a/Lib/Shared/Volantis.App/GuiControl/ListViewControl.ahk +++ b/Lib/Shared/Volantis.App/GuiControl/ListViewControl.ahk @@ -11,7 +11,7 @@ class ListViewControl extends GuiControlBase { imgListL := "" resizeOpts := "wh" - CreateControl(columns, dataCallback, imgListCallback := "", initCallback := "", highlightRowCallback := "", resizeOpts := "") { + CreateControl(columns, dataCallback, imgListCallback := "", initCallback := "", highlightRowCallback := "", resizeOpts := "wh") { global LVM_GETHEADER super.CreateControl(false) columns.InsertAt(this.keyCol, "") diff --git a/Lib/Shared/Volantis.App/GuiControl/LocationBlock.ahk b/Lib/Shared/Volantis.App/GuiControl/LocationBlock.ahk index 40f371d8..3f535567 100644 --- a/Lib/Shared/Volantis.App/GuiControl/LocationBlock.ahk +++ b/Lib/Shared/Volantis.App/GuiControl/LocationBlock.ahk @@ -83,7 +83,7 @@ class LocationBlock extends GuiControlBase { } OnLocationOptions(btn, info) { - result := this.app.Service("manager.gui").Menu(Map( + result := this.app["manager.gui"].Menu(Map( "parent", this.guiObj, "child", true ), btn.MenuItems, btn) diff --git a/Lib/Shared/Volantis.App/GuiControl/StatusIndicatorControl.ahk b/Lib/Shared/Volantis.App/GuiControl/StatusIndicatorControl.ahk index bec24d81..12bceb51 100644 --- a/Lib/Shared/Volantis.App/GuiControl/StatusIndicatorControl.ahk +++ b/Lib/Shared/Volantis.App/GuiControl/StatusIndicatorControl.ahk @@ -1,32 +1,83 @@ class StatusIndicatorControl extends GuiControlBase { - statusIndicatorW := 120 - statusIndicatorMinW := 120 + statusIndicatorW := 40 + statusIndicatorMinW := 40 + statusIndicatorExpandedMinW := 120 innerControl := "" + webService := "" - CreateControl(statusInfo, handler := "", statusStyle := "status") { + CreateControl(webService, handler := "") { super.CreateControl(false) + this.webService := webService + + if (!handler) { + handler := ObjBindMethod(this, "OnStatusIndicatorClick") + } if (handler == "" && HasMethod(this.guiObj, "OnStatusIndicatorClick")) { handler := "OnStatusIndicatorClick" } + this.statusIndicatorW := webService["StatusIndicatorExpanded"] ? this.statusIndicatorExpandedMinW : this.statusIndicatorMinW + options := this.parameters.SetDefaultOptions(this.parameters["options"].Clone(), [ "x+" . this.guiObj.margin, "yp", "w" . this.statusIndicatorW, "h26", - "vStatusIndicator" + "vStatusIndicator" . webService.Id ]) - - name := statusInfo && statusInfo.Has("name") ? statusInfo["name"] : "" - photo := statusInfo && statusInfo.Has("photo") ? statusInfo["photo"] : "" + + statusInfo := webService.GetStatusInfo() + statusStyle := webService.Authenticated ? "status" : "statusOffline" + name := (statusInfo && statusInfo.Has("name") && webService["StatusIndicatorExpanded"]) ? statusInfo["name"] : "" + photo := (statusInfo && statusInfo.Has("photo")) ? statusInfo["photo"] : "" this.innerControl := this.guiObj.Add("ButtonControl", options, name, handler, statusStyle, Map("photo", photo)) this.ctl := this.innerControl.ctl return this.ctl } - UpdateStatusIndicator(statusInfo, statusStyle := "status") { + OnStatusIndicatorClick(btn, info) { + webService := this.webService + menuItems := [] + + if (webService) { + if (webService.Authenticated) { + menuItems.Push(Map("label", "Account Details", "name", "AccountDetails")) + menuItems.Push(Map("label", "Logout", "name", "Logout")) + } else { + menuItems.Push(Map("label", "Login", "name", "Login")) + } + } + + result := this.container["manager.gui"].Menu(menuItems, this, btn) + + if (result == "AccountDetails") { + accountResult := this.container["manager.gui"].Dialog(Map( + "type", "AccountInfoWindow", + "webService", this.webService, + "ownerOrParent", this.guiObj.guiId, + "child", true + )) + + if (accountResult == "OK") { + this.UpdateStatusIndicator(webService) + } + } else if (result == "Logout") { + if (webService) { + webService.Logout() + } + } else if (result == "Login") { + if (webService) { + webService.Login() + } + } + } + + UpdateStatusIndicator() { + statusInfo := this.webService.GetStatusInfo() + statusStyle := this.webService.Authenticated ? "status" : "statusOffline" + oldW := this.statusIndicatorW newW := this.CalculateWidth(statusInfo) this.statusIndicatorW := newW @@ -38,23 +89,24 @@ class StatusIndicatorControl extends GuiControlBase { this.ctl.Move(statusX - difference,, statusW + difference) } - name := statusInfo && statusInfo.Has("name") ? statusInfo["name"] : "" - photo := statusInfo && statusInfo.Has("photo") ? statusInfo["photo"] : "" + name := (statusInfo && statusInfo.Has("name") && this.webService["StatusIndicatorExpanded"]) ? statusInfo["name"] : "" + photo := (statusInfo && statusInfo.Has("photo")) ? statusInfo["photo"] : "" this.guiObj.themeObj.DrawButton(this.ctl, name, statusStyle, Map("photo", photo)) return difference } CalculateWidth(statusInfo) { - width := this.statusIndicatorMinW + expanded := this.webService["StatusIndicatorExpanded"] + width := expanded ? this.statusIndicatorExpandedMinW : this.statusIndicatorMinW requiredW := 10 if (statusInfo) { - if (statusInfo.Has("name")) { + if (statusInfo.Has("name") && expanded) { requiredW += this.guiObj.themeObj.CalculateTextWidth(statusInfo["name"]) } - if (StatusInfo.Has("photo")) { + if (StatusInfo.Has("photo") || !expanded) { requiredW += 26 } } diff --git a/Lib/Shared/Volantis.App/GuiControl/TabsControl.ahk b/Lib/Shared/Volantis.App/GuiControl/TabsControl.ahk index 3b6a1e62..591a2a05 100644 --- a/Lib/Shared/Volantis.App/GuiControl/TabsControl.ahk +++ b/Lib/Shared/Volantis.App/GuiControl/TabsControl.ahk @@ -122,6 +122,5 @@ class TabsControl extends GuiControlBase { } this.guiObj.AutoXYWH("wh", [this.ctl.Name]) - this.ResizeColumns() } } diff --git a/Lib/Shared/Volantis.App/GuiControl/TitlebarControl.ahk b/Lib/Shared/Volantis.App/GuiControl/TitlebarControl.ahk index 3e4ed8cb..872dccae 100644 --- a/Lib/Shared/Volantis.App/GuiControl/TitlebarControl.ahk +++ b/Lib/Shared/Volantis.App/GuiControl/TitlebarControl.ahk @@ -5,10 +5,12 @@ class TitlebarControl extends GuiControlBase { iconW := 16 titlebarH := 31 titlebarButtonW := 16 - initialStatusIndicatorW := 120 + initialStatusIndicatorW := 40 + initialStatusIndicatorExpandedW := 120 titleButton := "" titleText := "" statusIndicator := "" + statusIndicators := [] minBtn := "" maxBtn := "" unmaxBtn := "" @@ -33,9 +35,21 @@ class TitlebarControl extends GuiControlBase { buttonsW := 0 statusIndicatorW := this.guiObj.config["showStatusIndicator"] ? this.initialStatusIndicatorW : 0 + statusIndicatorExpandedW := this.guiObj.config["showStatusIndicator"] ? this.initialStatusIndicatorExpandedW : 0 + + serviceMgr := this.container["entity_manager.web_service"] + webServices := Map() if (this.guiObj.config["showStatusIndicator"]) { - buttonsW += statusIndicatorW + (this.guiObj.margin * 2) + webServices := serviceMgr.EntityQuery(EntityQuery.RESULT_TYPE_ENTITIES) + .Condition(IsTrueCondition(), "Enabled") + .Condition(IsTrueCondition(), "StatusIndicator") + .Execute() + + for serviceId, webService in webServices { + serviceStatusW := (webService["StatusIndicatorExpanded"]) ? statusIndicatorExpandedW : statusIndicatorW + buttonsW += serviceStatusW + (this.guiObj.margin * 2) + } } if (this.guiObj.config["showMinimize"]) { @@ -84,20 +98,19 @@ class TitlebarControl extends GuiControlBase { this.titleText := this.guiObj.guiObj.AddText(opts, titleText) } - if (this.guiObj.config["showStatusIndicator"]) { - opts := "x" . buttonsX . " y" . (this.topMargin - 5) . " w" . statusIndicatorW - statusStyle := this.guiObj.StatusWindowIsOnline() ? "status" : "statusOffline" - initialInfo := Map() - statusInfo := this.guiObj.GetStatusInfo() - - if (statusInfo) { - initialInfo := statusInfo.Clone() - initialInfo["name"] := "" + if (this.guiObj.config["showStatusIndicator"] && webServices.Count) { + for serviceId, service in webServices { + expanded := service["StatusIndicatorExpanded"] + serviceStatusW := expanded ? statusIndicatorExpandedW : statusIndicatorW + opts := "x" . buttonsX . " y" . (this.topMargin - 5) . " w" . serviceStatusW + + statusIndicator := this.guiObj.Add("StatusIndicatorControl", opts, "", service, "") + this.statusIndicators.Push(statusIndicator) + service.AddStatusIndicator(statusIndicator) + + difference := statusIndicator.UpdateStatusIndicator() + buttonsX += serviceStatusW + (this.guiObj.margin * 2) } - - this.statusIndicator := this.guiObj.Add("StatusIndicatorControl", opts, "", initialInfo, "", statusStyle) - difference := this.statusIndicator.UpdateStatusIndicator(statusInfo, statusStyle) - buttonsX += this.initialStatusIndicatorW + (this.guiObj.margin * 2) } handler := this.RegisterCallback("OnTitlebarButtonClick") @@ -182,7 +195,7 @@ class TitlebarControl extends GuiControlBase { } } - OnSize(minMax, width, height) { + OnSize(guiObj, minMax, width, height) { if (minMax == 1 and this.guiObj.config["showMaximize"]) { this.guiObj.guiObj["WindowUnmaxButton"].Visible := true this.guiObj.guiObj["WindowMaxButton"].Visible := false @@ -198,7 +211,15 @@ class TitlebarControl extends GuiControlBase { this.guiObj.AutoXYWH("w", ["WindowTitlebar"]) if (this.guiObj.config["showStatusIndicator"]) { - this.guiObj.AutoXYWH("x*", ["StatusIndicator"]) + indicatorCtlNames := [] + + for index, statusIndicator in this.statusIndicators { + indicatorCtlNames.Push(statusIndicator.ctl.Name) + } + + if (indicatorCtlNames.Length) { + this.guiObj.AutoXYWH("x*", indicatorCtlNames) + } } if (this.guiObj.config["showClose"]) { @@ -209,7 +230,7 @@ class TitlebarControl extends GuiControlBase { this.guiObj.AutoXYWH("x*", ["WindowMaxButton", "WindowUnmaxButton"]) } - if (this.guiObj.config["showMaximize"]) { + if (this.guiObj.config["showMinimize"]) { this.guiObj.AutoXYWH("x*", ["WindowMinButton"]) } } diff --git a/Lib/Shared/Volantis.App/Service/AuthService.ahk b/Lib/Shared/Volantis.App/Service/AuthService.ahk deleted file mode 100644 index 9d9b4cd2..00000000 --- a/Lib/Shared/Volantis.App/Service/AuthService.ahk +++ /dev/null @@ -1,154 +0,0 @@ -class AuthService extends AppServiceBase { - authProviderObj := "" - stateObj := "" - authenticationEnabled := false - authInfoObj := "" - - __New(app, authProviderObj, stateObj) { - InvalidParameterException.CheckTypes("AuthenticationService", "stateObj", stateObj, "StateBase") - - if (authProviderObj && Type(authProviderObj) == "String") { - authProviderObj := app.Services.Get(authProviderObj) - } - - this.authProviderObj := authProviderObj - this.stateObj := stateObj - - authState := this.stateObj.Authentication - - if (authState && authState.Count > 0) { - authInfoObj := AuthInfo() - authInfoObj.Authenticated := true - - for key, value in authState { - authInfoObj.Set(key, value, true) - } - - this.authInfoObj := authInfoObj - } - - super.__New(app) - } - - SetAuthProvider(authProviderObj) { - this.authProviderObj := authProviderObj - } - - SetState(stateObj) { - this.stateObj := stateObj - } - - Login() { - if (this.app.Config["api_authentication"] && this.authProviderObj) { - authInfoObj := "" - - if (!this.IsAuthenticated()) { - authInfoObj := this.authProviderObj.Login() - } else if (this.AuthenticationNeedsRefresh()) { - authInfoObj := this.RefreshAuthentication() - } - - if (authInfoObj) { - this.UpdateAuthState(authInfoObj) - this.app.UpdateStatusIndicators() - } - } - } - - GetStatusInfo() { - statusText := "Not logged in" - imgPath := "" - email := "" - - if (this.IsAuthenticated()) { - playerName := this.app.Config["player_name"] - email := this.authInfoObj.Get("email") - - if (playerName) { - statusText := playerName - } else if (email) { - statusText := email - } else { - statusText := "Logged in" - } - - imgPath := this.authInfoObj.Get("photo") - - if (SubStr(imgPath, 1, 4) == "http") { - cachePath := "account--profile.jpg" - imgPath := this.app.Service("manager.cache")["file"].GetCachedDownload(cachePath, imgPath) - } - } - - return Map("name", statusText, "email", email, "photo", imgPath) - } - - Logout() { - if (this.app.Config["api_authentication"] && this.authProviderObj && this.authInfoObj) { - this.authProviderObj.Logout(this.authInfoObj) - this.authInfoObj := "" - this.stateObj.Authentication := Map() - this.app.UpdateStatusIndicators() - } - } - - IsAuthenticated() { - return this.app.Config["api_authentication"] && this.authProviderObj && this.authInfoObj && this.authInfoObj.Authenticated - } - - AuthenticationNeedsRefresh() { - needsRefresh := false - - if (this.app.Config["api_authentication"] && this.authProviderObj && this.IsAuthenticated()) { - needsRefresh := this.authProviderObj.NeedsRefresh(this.authInfoObj) - } - - return needsRefresh - } - - RefreshAuthentication() { - if (this.app.Config["api_authentication"] && this.authProviderObj && this.IsAuthenticated()) { - authInfoObj := this.authProviderObj.RefreshAuthentication(this.authInfoObj) - - if (authInfoObj) { - this.UpdateAuthState(authInfoObj) - } - } - } - - UpdateAuthState(authInfoObj) { - if (this.app.Config["api_authentication"] && this.authProviderObj && authInfoObj) { - this.authInfoObj := authInfoObj - this.AddUserInfoFromApi(authInfoObj) - this.stateObj.SetAuthentication(authInfoObj.GetPersistentData()) - } - } - - AddUserInfoFromApi(authInfoObj) { - dataSource := this.app.Service("manager.data_source").GetDefaultDataSource() - - if (dataSource) { - apiStatus := dataSource.GetStatus() - - if (apiStatus) { - if (apiStatus.Has("email")) { - authInfoObj.Set("email", apiStatus["email"], true) - } - - if (apiStatus.Has("photo")) { - authInfoObj.Set("photo", apiStatus["photo"], true) - } - } - } - } - - AlterApiRequest(request) { - if (this.IsAuthenticated()) { - if (this.AuthenticationNeedsRefresh()) { - this.RefreshAuthentication() - } - - this.authProviderObj.AddAuthInfoToRequest(this.authInfoObj, request) - } - } -} diff --git a/Lib/Shared/Volantis.App/Service/ComponentManager/DataSourceManager.ahk b/Lib/Shared/Volantis.App/Service/ComponentManager/DataSourceManager.ahk deleted file mode 100644 index 2b688991..00000000 --- a/Lib/Shared/Volantis.App/Service/ComponentManager/DataSourceManager.ahk +++ /dev/null @@ -1,61 +0,0 @@ -class DataSourceManager extends ComponentManagerBase { - primaryKey := "" - - __New(container, eventMgr, notifierObj, primaryKey := "") { - if (primaryKey) { - this.primaryKey := primaryKey - } - - super.__New(container, "data_source.", eventMgr, notifierObj, DataSourceBase) - } - - GetDefaultDataSource() { - if (!this.primaryKey) { - throw ComponentException("There is no default data source set") - } - - if (!this.Has(this.primaryKey)) { - throw ComponentException("Primary data source key " . this.primaryKey . " does not exist") - } - - return this[this.primaryKey] - } - - GetDefaultComponentId() { - return this.primaryKey - } - - GetItem(key := "") { - if (key == "") { - key := this.primaryKey - } - - return super.GetItem(key) - } - - ReadListing(path, dataSourceKey := "") { - if (dataSourceKey == "") { - dataSourceKey := this.primaryKey - } - - if (!this.Has(dataSourceKey)) { - throw ComponentException("Component " . dataSourceKey . " does not exist in the data source manager") - } - - dataSource := this[dataSourceKey] - return dataSource.ReadListing(path) - } - - ReadJson(key, path := "", dataSourceKey := "") { - if (dataSourceKey == "") { - dataSourceKey := this.primaryKey - } - - if (!this.Has(dataSourceKey)) { - throw ComponentException("Component " . dataSourceKey . " does not exist in the data source manager") - } - - dataSource := this[dataSourceKey] - return dataSource.ReadJson(key, path) - } -} diff --git a/Lib/Shared/Volantis.App/Service/ComponentManager/GuiManager.ahk b/Lib/Shared/Volantis.App/Service/ComponentManager/GuiManager.ahk index cfbcfd9e..022e8688 100644 --- a/Lib/Shared/Volantis.App/Service/ComponentManager/GuiManager.ahk +++ b/Lib/Shared/Volantis.App/Service/ComponentManager/GuiManager.ahk @@ -278,7 +278,7 @@ class GuiManager extends ComponentManagerBase { guiObj := obj } else if (HasBase(obj, GuiBase.Prototype)) { guiObj := obj.guiObj - } else if (Type(guiObj) == "String" && this.Has(obj)) { + } else if (Type(obj) == "String" && this.Has(obj)) { guiObj := this[obj].guiObj } diff --git a/Lib/Shared/Volantis.App/State/ParameterState.ahk b/Lib/Shared/Volantis.App/State/ParameterState.ahk new file mode 100644 index 00000000..7e573004 --- /dev/null +++ b/Lib/Shared/Volantis.App/State/ParameterState.ahk @@ -0,0 +1,39 @@ +class ParameterState extends StateBase { + parameterKey := "" + + __New(app, parameterKey, autoLoad := false) { + this.parameterKey := parameterKey + super.__New(app, "", autoLoad) + } + + SaveState(newState := "") { + if (newState != "") { + this.stateMap := newState + } + + if (this.parameterKey) { + this.container.Parameters[this.parameterKey] := this.stateMap + } + + return this.stateMap + } + + LoadState() { + if (this.parameterKey && !this.stateLoaded) { + newState := super.LoadState() + + if (this.container.HasParameter(this.parameterKey)) { + paramValue := this.container.Parameters[this.parameterKey] + + if (HasBase(paramValue, Map.Prototype)) { + newState := paramValue + } + } + + this.stateMap := newState + this.stateLoaded := true + } + + return this.stateMap + } +} diff --git a/Lib/Shared/Volantis.App/State/StateBase.ahk b/Lib/Shared/Volantis.App/State/StateBase.ahk index 4aaf8b0a..c19f07e9 100644 --- a/Lib/Shared/Volantis.App/State/StateBase.ahk +++ b/Lib/Shared/Volantis.App/State/StateBase.ahk @@ -1,5 +1,6 @@ class StateBase { app := "" + container := "" stateMap := Map() stateLoaded := false @@ -14,13 +15,14 @@ class StateBase { } IsStateOutdated() { - return this.app.Service("version_checker").VersionIsOutdated(this.app.Version, this.Version) + return this.app["version_checker"].VersionIsOutdated(this.app.Version, this.Version) } __New(app, state := "", autoLoad := false) { InvalidParameterException.CheckTypes("StateBase", "app", app, "AppBase") this.app := app + this.container := app.Services if (state != "") { InvalidParameterException.CheckTypes("StateBase", "state", state, "Map") diff --git a/Lib/Shared/Volantis.Base/EventSubscriber/EventSubscriberBase.ahk b/Lib/Shared/Volantis.Base/EventSubscriber/EventSubscriberBase.ahk index 871cef25..3043d56d 100644 --- a/Lib/Shared/Volantis.Base/EventSubscriber/EventSubscriberBase.ahk +++ b/Lib/Shared/Volantis.Base/EventSubscriber/EventSubscriberBase.ahk @@ -2,6 +2,12 @@ Extending this class is optional as its main purpose is to document the API */ class EventSubscriberBase { + container := "" + + __New(container) { + this.container := container + } + /* Format: Map( @@ -12,6 +18,6 @@ class EventSubscriberBase { ) */ GetEventSubscribers() { - return [] + return Map() } } diff --git a/Lib/Shared/Volantis.Base/UrlObj/UrlObj.ahk b/Lib/Shared/Volantis.Base/UrlObj/UrlObj.ahk index 18a0e7e4..1b011249 100644 --- a/Lib/Shared/Volantis.Base/UrlObj/UrlObj.ahk +++ b/Lib/Shared/Volantis.Base/UrlObj/UrlObj.ahk @@ -51,19 +51,21 @@ class UrlObj { urlMap := Map() urlParts := "" - regexStr := "^((P[^:/?#]+):)?(//(P[^/?#]*))?(P[^?#]*)(\?(P[^#]*))?(#(P.*))?" - isUrl := RegExMatch(urlStr, regexStr, urlParts) - - loop urlParts.Count { - matchName := urlParts.Name[A_Index] - matchVal := urlParts[A_Index] - - if (matchName) { - if (matchName == "query") { - matchVal := this._splitQueryStr(matchVal) + regexStr := "^((?P[^:/?#]+):)?(//(?P[^/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?" + isUrl := RegExMatch(urlStr, regexStr, &urlParts) + + if (urlParts) { + loop urlParts.Count { + matchName := urlParts.Name[A_Index] + matchVal := urlParts[A_Index] + + if (matchName) { + if (matchName == "query") { + matchVal := this._splitQueryStr(matchVal) + } + + urlMap[matchName] := matchVal } - - urlMap[matchName] := matchVal } } diff --git a/Lib/Shared/Volantis.Data/LayeredData/LayeredDataBase.ahk b/Lib/Shared/Volantis.Data/LayeredData/LayeredDataBase.ahk index 56d06d51..60a4095d 100644 --- a/Lib/Shared/Volantis.Data/LayeredData/LayeredDataBase.ahk +++ b/Lib/Shared/Volantis.Data/LayeredData/LayeredDataBase.ahk @@ -8,7 +8,7 @@ * * Example: * - Layer 1: Initial defaults - * - Layer 2: Defaults from datasource + * - Layer 2: Defaults from external sources * - Layer 3: Auto-detected defaults * - Layer 4: User configuration values * - Processor 1: Token expander @@ -25,10 +25,12 @@ class LayeredDataBase { cloner := "" userLayers := ["data"] loadingLayers := Map() + extraDataLayer := "data" + extraDataKey := "_extra" static NO_VALUE := ":NO_VAL:" - __New(cloner, processors, layerNames, layerSources) { + __New(cloner, processors, layerNames := "", layerSources := "") { this.cloner := cloner if (processors) { @@ -255,6 +257,54 @@ class LayeredDataBase { } } + GetExtraData(key := "") { + extraData := this.GetValue(this.extraDataKey, false, this.extraDataLayer, Map()) + + if (key) { + extraData := extraData.Has(key) ? extraData[key] : Map() + } + + return extraData + } + + SetExtraData(value, key := "") { + if (key) { + extraData := this.GetExtraData() + extraData[key] := value + value := extraData + } + + this.SetValue(this.extraDataKey, value, this.extraDataLayer) + + return this + } + + HasExtraData(key := "") { + hasData := this.HasValue(this.extraDataKey, this.extraDataLayer, false) + + if (hasData && key) { + extraData := this.GetExtraData() + hasData := extraData.Has(key) + } + + return hasData + } + + DeleteExtraData(key := "") { + if (key) { + extraData := this.GetExtraData() + + if (extraData.Has(key)) { + extraData.Delete(key) + this.SetExtraData(extraData) + } + } else { + this.DeleteValue(this.extraDataKey, this.extraDataLayer) + } + + return this + } + /** key: The key to retrieve @@ -486,8 +536,22 @@ class LayeredDataBase { return data } + GetUserLayers() { + layerNames := this.userLayers + + layers := Map() + + for index, layerName in layerNames { + layers[layerName] := this.GetLayer(layerName) + } + + return layers + } + CloneLayers(layers := "") { if (layers == "") { + layers := this.GetUserLayers() + } else if (layers == "*") { this.LoadAllLayers() layers := this.layers } else if (Type(layers) == "String") { @@ -500,8 +564,10 @@ class LayeredDataBase { cloned := Map() - for key, layer in layers { - cloned[key] := this.CloneData(layer) + if (layers) { + for key, layer in layers { + cloned[key] := this.CloneData(layer) + } } return cloned diff --git a/Lib/Shared/Volantis.Entity/Entity/EntityBase.ahk b/Lib/Shared/Volantis.Entity/Entity/EntityBase.ahk index a92092c7..079af972 100644 --- a/Lib/Shared/Volantis.Entity/Entity/EntityBase.ahk +++ b/Lib/Shared/Volantis.Entity/Entity/EntityBase.ahk @@ -2,15 +2,21 @@ class EntityBase { idVal := "" entityTypeIdVal := "" parentEntityObj := "" + parentEntityTypeId := "" + parentEntityId := "" + parentEntityStorage := false container := "" + app := "" eventMgr := "" dataObj := "" storageObj := "" idSanitizer := "" sanitizeId := true + loading := false loaded := false - merger := "" dataLayer := "data" + dataLoaded := false + merger := "" cloner := "" Id { @@ -20,14 +26,16 @@ class EntityBase { EntityTypeId { get => this.entityTypeIdVal + set => this.entityTypeIdVal := value } EntityType { get => this.GetEntityType() + set => this.EntityTypeId := value } FieldData { - Get => this.GetData().GetMergedData() + get => this.GetData().GetMergedData() } Name { @@ -35,14 +43,25 @@ class EntityBase { set => this.SetValue("name", value) } - UnmergedFieldData { + RawData { get => this.GetData().GetLayer(this.dataLayer) set => this.GetData().SetLayer(this.dataLayer, value) } ParentEntity { get => this.GetParentEntity() - set => this.SetParentEntity(value) + } + + ReferencedEntities { + get => this.GetReferencedEntities(false) + } + + ChildEntities { + get => this.GetReferencedEntities(true) + } + + ChildEntityData { + get => this.GetAllChildEntityData() } __Item[key := ""] { @@ -54,7 +73,8 @@ class EntityBase { return this.GetAllValues().__Enum(numberOfVars) } - __New(id, entityTypeId, container, eventMgr, storageObj, idSanitizer := "", autoLoad := true, parentEntity := "") { + __New(id, entityTypeId, container, eventMgr, storageObj, idSanitizer := "", autoLoad := true, parentEntity := "", parentEntityStorage := false) { + this.app := container.GetApp() this.idSanitizer := idSanitizer if (this.sanitizeId && this.idSanitizer) { @@ -68,25 +88,44 @@ class EntityBase { this.storageObj := storageObj this.merger := container.Get("merger.list") this.cloner := container.Get("cloner.list") + this.parentEntityStorage := parentEntityStorage - if (!parentEntity) { - parentEntity := this.DiscoverParentEntity(container, eventMgr, id, storageObj, idSanitizer) + if (!parentEntity && this.parentEntityObj) { + parentEntity := this.parentEntityObj } - if (parentEntity) { - this.SetParentEntity(parentEntity) - } + this.DiscoverParentEntity(container, eventMgr, id, storageObj, idSanitizer, parentEntity) this._createEntityData() this.SetupEntity() if (autoLoad) { - this.LoadEntity(false, true) + this.LoadEntity() } } + static Create(container, eventMgr, id, entityTypeId, storageObj, idSanitizer, autoLoad := true, parentEntity := "", parentEntityStorage := false) { + className := this.Prototype.__Class + + return %className%( + id, + entityTypeId, + container, + eventMgr, + storageObj, + idSanitizer, + autoLoad, + parentEntity, + parentEntityStorage + ) + } + _createEntityData() { - this.dataObj := EntityData(this, this._getLayerNames(), this._getLayerSources()) + if (!this.dataLoaded) { + this.dataObj := EntityData(this, this._getLayerNames(), this._getLayerSources()) + } + + this.dataLoaded := true } _getLayerNames() { @@ -95,39 +134,51 @@ class EntityBase { } _getLayerSources() { + layerSource := this.parentEntityStorage + ? ParentEntityLayerSource(this) + : EntityStorageLayerSource(this.storageObj, this.GetStorageId()) + return Map( "defaults", ObjBindMethod(this, "InitializeDefaults"), "auto", ObjBindMethod(this, "AutoDetectValues"), - "data", EntityStorageLayerSource(this.storageObj, this.GetStorageId()) + "data", layerSource ) } - static Create(container, eventMgr, id, entityTypeId, storageObj, idSanitizer, parentEntity := "") { - className := this.Prototype.__Class - - return %className%( - id, - entityTypeId, - container, - eventMgr, - storageObj, - idSanitizer, - parentEntity - ) + /** + * Get an array of all IDs + * + * List managed IDs and give modules a chance to add others. + */ + ListEntities(includeManaged := true, includeExtended := true) { + return this.container["entity_manager." . this.EntityTypeId] + .ListEntities(includeManaged, includeExtended) } - DiscoverParentEntity(container, eventMgr, id, storageObj, idSanitizer) { - return "" + DiscoverParentEntity(container, eventMgr, id, storageObj, idSanitizer, parentEntity := "") { + event := EntityParentEvent(EntityEvents.ENTITY_DISCOVER_PARENT, this.entityTypeId, this, parentEntity) + this.eventMgr.DispatchEvent(event) + + if (event.ParentEntity) { + this.parentEntityObj := event.ParentEntity + } else if (event.ParentEntityId) { + this.parentEntityTypeId := event.ParentEntityTypeId + this.parentEntityId := event.ParentEntityId + this.parentEntityMgr := event.ParentEntityManager + ? event.ParentEntityManager + : container.Get("entity_manager." . event.ParentEntityTypeId) + + } + + this.parentEntityObj := event.ParentEntity + + return event.ParentEntity } GetParentEntity() { return this.parentEntityObj } - SetParentEntity(parentEntity) { - this.parentEntityObj := parentEntity - } - SetupEntity() { event := EntityEvent(EntityEvents.ENTITY_PREPARE, this.entityTypeId, this) this.eventMgr.DispatchEvent(event) @@ -137,26 +188,16 @@ class EntityBase { return this.GetData().GetMergedData(!raw) } - GetEntityTypeId() { - return this.entityTypeId - } - GetEntityType() { ; @todo Inject entity type manager service return this.container.Get("manager.entity_type")[this.EntityTypeId] } - InitializeDefaults(recurse := true) { + InitializeDefaults() { defaults := Map( "name", this.Id ) - if (recurse) { - for key, referencedEntity in this.GetReferencedEntities(true) { - this.merger.Merge(defaults, referencedEntity.InitializeDefaults()) - } - } - return defaults } @@ -202,26 +243,27 @@ class EntityBase { return this.GetData().DeleteValue(key, this.dataLayer) } - CreateSnapshot(name, recurse := true) { - this.GetData().CreateSnapshot(name) - + CreateSnapshot(name, recurse := false) { if (recurse) { - for index, entityObj in this.GetReferencedEntities(true) { - if (entityObj.HasOwnDataStorage()) { - entityObj.GetData().CreateSnapshot(name, recurse) - } + for index, entityObj in this.ChildEntities { + entityObj.GetData().CreateSnapshot(name, recurse) } } - return this - } + this.GetData().CreateSnapshot(name) - HasOwnDataStorage() { - return this.dataObj + return this } - RestoreSnapshot(name, recurse := true) { + RestoreSnapshot(name, recurse := false) { this.GetData().RestoreSnapshot(name) + + if (recurse) { + for index, entityObj in this.ChildEntities { + entityObj.GetData().RestoreSnapshot(name, recurse) + } + } + return this } @@ -230,24 +272,28 @@ class EntityBase { } LoadEntity(reload := false, recurse := false) { - loaded := false + if (this.loading) { + throw AppException("Attempting to load entity with a circular reference.") + } - if (!this.loaded || reload) { - this.RefreshEntityData(true) + if (!this.loading && this.dataLoaded && (!this.loaded || reload)) { + this.loading := true + this.RefreshEntityData(recurse) this.CreateSnapshot("original") this.loaded := true loaded := true - } + this.loading := false - if (recurse) { - for index, entityObj in this.GetReferencedEntities(true) { - entityObj.LoadEntity(reload, recurse) + if (recurse) { + for index, entityObj in this.ChildEntities { + entityObj.LoadEntity(reload, recurse) + } } - } - if (loaded) { - event := EntityEvent(EntityEvents.ENTITY_LOADED, this.entityTypeId, this) - this.eventMgr.DispatchEvent(event) + if (loaded) { + event := EntityEvent(EntityEvents.ENTITY_LOADED, this.entityTypeId, this) + this.eventMgr.DispatchEvent(event) + } } } @@ -255,7 +301,7 @@ class EntityBase { this.GetData().UnloadAllLayers(reloadUserData) if (recurse) { - for index, entityObj in this.GetReferencedEntities(true) { + for index, entityObj in this.ChildEntities { entityObj.RefreshEntityData(recurse, reloadUserData) } } @@ -264,16 +310,16 @@ class EntityBase { this.eventMgr.DispatchEvent(event) } - AutoDetectValues(recurse := true) { + AutoDetectValues() { values := Map() - if (recurse) { - for key, referencedEntity in this.GetReferencedEntities(true) { - this.merger.Merge(values, referencedEntity.AutoDetectValues(recurse)) - } - } + event := EntityDetectValuesEvent(EntityEvents.ENTITY_DETECT_VALUES, this.EntityTypeId, this, values) + this.eventMgr.DispatchEvent(event) + + event := EntityDetectValuesEvent(EntityEvents.ENTITY_DETECT_VALUES_ALTER, this.EntityTypeId, this, event.Values) + this.eventMgr.DispatchEvent(event) - return values + return event.Values } SaveEntity(recurse := true) { @@ -285,15 +331,15 @@ class EntityBase { event := EntityEvent(EntityEvents.ENTITY_PRESAVE, this.entityTypeId, this) this.eventMgr.DispatchEvent(event) - - this.GetData().SaveData() - this.CreateSnapshot("original") if (recurse) { - for index, entityObj in this.GetReferencedEntities(true) { + for index, entityObj in this.ChildEntities { entityObj.SaveEntity(recurse) } } + + this.GetData().SaveData() + this.CreateSnapshot("original") if (alreadyExists) { event := EntityEvent(EntityEvents.ENTITY_UPDATED, this.entityTypeId, this) @@ -317,11 +363,17 @@ class EntityBase { } } - DeleteEntity() { + DeleteEntity(recurse := false) { if (this.storageObj.HasData(this.GetStorageId())) { event := EntityEvent(EntityEvents.ENTITY_PREDELETE, this.entityTypeId, this) this.eventMgr.DispatchEvent(event) + if (recurse) { + for index, entityObj in this.ChildEntities { + entityObj.DeleteEntity(recurse) + } + } + this.storageObj.DeleteData(this.GetStorageId()) event := EntityEvent(EntityEvents.ENTITY_DELETED, this.entityTypeId, this) @@ -344,14 +396,14 @@ class EntityBase { return !!(changes.GetAdded().Count || changes.GetModified().Count || changes.GetDeleted().Count) } - DiffChanges(recursive := true) { + DiffChanges(recurse := true) { diff := this.GetData().DiffChanges("original", this.dataLayer) - if (recursive) { + if (recurse) { diffs := [diff] - for index, referencedEntity in this.GetReferencedEntities(true) { - diffs.Push(referencedEntity.DiffChanges(recursive)) + for index, referencedEntity in this.ChildEntities { + diffs.Push(referencedEntity.DiffChanges(recurse)) } diff := DiffResult.Combine(diffs) @@ -391,7 +443,7 @@ class EntityBase { result := "Cancel" while (mode) { - result := this.app.Service("manager.gui").Dialog(Map( + result := this.app["manager.gui"].Dialog(Map( "type", "SimpleEntityEditor", "mode", mode, "child", !!(ownerOrParent), @@ -435,4 +487,52 @@ class EntityBase { return text } + + UpdateDefaults(recurse := false) { + if (recurse) { + for key, child in this.ChildEntities { + child.UpdateDefaults(recurse) + } + } + + this.GetData().UnloadAllLayers(false) + } + + GetAllChildEntityData() { + return this.GetData().GetExtraData() + } + + GetChildEntityData(entityTypeId, entityId) { + dataKey := entityTypeId . "." . entityId + + childData := this.GetData().GetExtraData(dataKey) + + return childData ? childData : Map() + } + + SetChildEntityData(entityTypeId, entityId, data) { + dataKey := entityTypeId . "." . entityId + + if (!data) { + data := Map() + } + + this.GetData().SetExtraData(data, dataKey) + + return this + } + + HasChildEntityData(entityTypeId, entityId) { + dataKey := entityTypeId . "." . entityId + + return this.GetData().HasExtraData(dataKey) + } + + DeleteChildEntityData(entityTypeId, entityId) { + dataKey := entityTypeId . "." . entityId + + this.GetData().DeleteExtraData(dataKey) + + return this + } } diff --git a/Lib/Shared/Volantis.Entity/Entity/FieldableEntity.ahk b/Lib/Shared/Volantis.Entity/Entity/FieldableEntity.ahk index 2776d249..38709289 100644 --- a/Lib/Shared/Volantis.Entity/Entity/FieldableEntity.ahk +++ b/Lib/Shared/Volantis.Entity/Entity/FieldableEntity.ahk @@ -11,10 +11,29 @@ class FieldableEntity extends EntityBase { get => this.GetFieldDefinitions() } - __New(id, entityTypeId, container, eventMgr, storageObj, idSanitizer := "", autoLoad := true) { - this.entityFieldFactory := container.Get("entity_field_factory." . entityTypeId) - this.entityWidgetFactory := container.Get("entity_widget_factory." . entityTypeId) - super.__New(id, entityTypeId, container, eventMgr, storageObj, idSanitizer, autoLoad) + __New(id, entityTypeId, container, fieldFactory, widgetFactory, eventMgr, storageObj, idSanitizer := "", autoLoad := true, parentEntity := "", parentEntityStorage := false) { + this.entityFieldFactory := fieldFactory + this.entityWidgetFactory := widgetFactory + + super.__New(id, entityTypeId, container, eventMgr, storageObj, idSanitizer, autoLoad, parentEntity, parentEntityStorage) + } + + static Create(container, eventMgr, id, entityTypeId, storageObj, idSanitizer, autoLoad := true, parentEntity := "", parentEntityStorage := false) { + className := this.Prototype.__Class + + return %className%( + id, + entityTypeId, + container, + container.Get("entity_field_factory." . entityTypeId), + container.Get("entity_widget_factory." . entityTypeId), + eventMgr, + storageObj, + idSanitizer, + autoLoad, + parentEntity, + parentEntityStorage + ) } GetDefaultFieldGroups() { @@ -235,8 +254,8 @@ class FieldableEntity extends EntityBase { } } - InitializeDefaults(recurse := true) { - defaults := super.InitializeDefaults(recurse) + InitializeDefaults() { + defaults := super.InitializeDefaults() for key, fieldObj in this.GetFields() { defaults[fieldObj.Definition["storageKey"]] := fieldObj.Definition["default"] diff --git a/Lib/Shared/Volantis.Entity/EntityField/BooleanEntityField.ahk b/Lib/Shared/Volantis.Entity/EntityField/BooleanEntityField.ahk index b3dad413..91d37e01 100644 --- a/Lib/Shared/Volantis.Entity/EntityField/BooleanEntityField.ahk +++ b/Lib/Shared/Volantis.Entity/EntityField/BooleanEntityField.ahk @@ -2,20 +2,61 @@ class BooleanEntityField extends EntityFieldBase { DefinitionDefaults(fieldDefinition) { defaults := super.DefinitionDefaults(fieldDefinition) defaults["widget"] := "checkbox" + defaults["default"] := false return defaults } - GetValue() { - isTrue := StrLower(super.GetValue()) + GetValue(index := "") { + value := super.GetValue(index) - if (isTrue == "true" || isTrue == "false") { - isTrue := (isTrue == "true") + if (!HasBase(value, Array.Prototype)) { + value := [value] } - return !!(isTrue) + newValues := [] + + for singleIndex, singleValue in value { + isTrue := StrLower(singleValue) + + if (isTrue == "true" || isTrue == "false") { + isTrue := (isTrue == "true") + } + + newValues.Push(!!isTrue) + } + + value := newValues + + if (!value.Length) { + value.Push("") + } + + if (index && !value.Has(index)) { + throw AppException("Index out of range") + } + + if (index) { + return value[index] + } else if (this.multiple) { + return value + } else { + return value[1] + } } - SetValue(value) { - super.SetValue(!!(value)) + SetValue(value, index := "") { + if (index || !this.multiple || !HasBase(value, Array.Prototype)) { + value := !!value + } else { + newValues := [] + + for singleIndex, singleValue in value { + newValues.Push(!!singleValue) + } + + value := newValues + } + + super.SetValue(value, index) } } diff --git a/Lib/Shared/Volantis.Entity/EntityField/EntityFieldBase.ahk b/Lib/Shared/Volantis.Entity/EntityField/EntityFieldBase.ahk index a13646a4..c4856c10 100644 --- a/Lib/Shared/Volantis.Entity/EntityField/EntityFieldBase.ahk +++ b/Lib/Shared/Volantis.Entity/EntityField/EntityFieldBase.ahk @@ -10,6 +10,7 @@ class EntityFieldBase { userLayer := "data" cloner := "" merger := "" + multiple := false needsEntityRefresh := false static VALUE_TYPE_DATA := "data" @@ -19,6 +20,10 @@ class EntityFieldBase { get => this.fieldDefinition set => this.fieldDefinition := value } + + IsMultiple { + get => this.multiple + } __New(fieldTypeId, entityObj, container, eventMgr, dataObj, fieldKey, fieldDefinition) { this.fieldTypeId := fieldTypeId @@ -31,6 +36,11 @@ class EntityFieldBase { this.merger := container.Get("merger.list") this.Definition := ParameterBag(this.DefinitionDefaults(fieldDefinition)) this.Definition.Add(fieldDefinition) + this.multiple := (this.Definition["cardinality"] == 0 || this.Definition["cardinality"] > 1) + + if (this.multiple && this.Definition["default"] && !HasBase(this.Definition["default"], Array.Prototype)) { + this.Definition["default"] := [this.Definition["default"]] + } } static Create(container, entityTypeId, entityObj, dataObj, fieldId, definition) { @@ -98,9 +108,8 @@ class EntityFieldBase { "formField", true, "group", "general", "help", "", - "limit", false, + "cardinality", 1, "modes", Map(), - "multiple", false, "processValue", false, "refreshEntityOnChange", false, "required", false, @@ -139,15 +148,49 @@ class EntityFieldBase { return result } - GetValue() { - return this.GetRawValue() + GetValue(index := "") { + return this.GetRawValue(index) } - GetRawValue() { - return this._callback("GetValue") + GetRawValue(index := "") { + value := this._callback("GetValue") + + if (this.multiple && !HasBase(value, Array.Prototype)) { + value := [value] + } + + if (this.multiple && index && !value.Has(index)) { + throw AppException("Index " . index . " does not exist in field " . this.fieldKey . ".") + } + + if (this.multiple && index) { + value := value[index] + } + + return value } - SetValue(value) { + SetValue(value, index := "") { + if (index && this.multiple) { + existingValues := this.GetRawValue() + + if (existingValues.Length < (index + 1)) { + throw AppException("Index to set is too high, there are only " . existingValues.Length . " values in field " . this.fieldKey . ".") + } + + if (existingValues.Length < index) { + existingValues.Push(value) + } else { + existingValues[index] := value + } + + value := existingValues + } + + if (this.multiple && !HasBase(value, Array.Prototype)) { + value := [value] + } + this._callback("SetValue", value) this.RefreshEntity() return this @@ -175,9 +218,19 @@ class EntityFieldBase { } Validate(value) { - return this - .CreateValidator(this.GetValidators(value)) - .Validate(value) + if (!HasBase(value, Array.Prototype)) { + value := [value] + } + + results := [] + + validator := this.CreateValidator(this.GetValidators(value)) + + for index, singleValue in value { + results.Push(validator.Validate(singleValue)) + } + + return this.multiple ? results : results[1] } /** @@ -187,11 +240,9 @@ class EntityFieldBase { _parseLayer(layer := "", allowAll := true) { if (!layer) { layer := this.Definition["dataLayer"] + } else if (layer == "*" && !allowAll) { + throw EntityException("Cannot pass wildcard for this layer value.") } else if (layer == "*") { - if (!allowAll) { - throw EntityException("Cannot pass wildcard for this layer value.") - } - layer := "" } @@ -234,6 +285,14 @@ class EntityFieldBase { allowEmpty ) + if (this.multiple && HasBase(val, Array.Prototype)) { + if (val.Length) { + val := (val[1] != "") + } else { + val := false + } + } + if (negate) { val := !val } @@ -244,6 +303,10 @@ class EntityFieldBase { _hasDefaultValue(allowEmpty := true, negate := false) { hasValue := allowEmpty ? true : !!(this.Definition["default"]) + if (hasValue && !allowEmpty && this.multiple && HasBase(this.Definition["default"], Array.Prototype)) { + hasValue := !!(this.Definition["default"][1]) + } + if (negate) { hasValue := !hasValue } diff --git a/Lib/Shared/Volantis.Entity/EntityField/EntityReferenceField.ahk b/Lib/Shared/Volantis.Entity/EntityField/EntityReferenceField.ahk index 6591eedc..af424717 100644 --- a/Lib/Shared/Volantis.Entity/EntityField/EntityReferenceField.ahk +++ b/Lib/Shared/Volantis.Entity/EntityField/EntityReferenceField.ahk @@ -1,4 +1,4 @@ -class EntityReferenceField extends ServiceReferenceField { +class EntityReferenceField extends EntityFieldBase { managerObj := "" DefinitionDefaults(fieldDefinition) { @@ -8,12 +8,14 @@ class EntityReferenceField extends ServiceReferenceField { throw EntityException("Entity reference fields require an entityType mapping.") } - managerObj := this._entityManager(entityTypeId) defaults := super.DefinitionDefaults(fieldDefinition) - defaults["servicePrefix"] := managerObj.GetServicePrefix() - defaults["entityType"] := managerObj.entityTypeId + defaults["entityType"] := entityTypeId + defaults["widget"] := "select" defaults["child"] := false + defaults["storeEntityData"] := false + defaults["selectOptionsCallback"] := ObjBindMethod(this, "GetEntitySelectOptions") + defaults["selectConditions"] := [] return defaults } @@ -32,34 +34,98 @@ class EntityReferenceField extends ServiceReferenceField { return validators } + GetValue(index := "") { + value := super.GetValue(index) + + if (!HasBase(value, Array.Prototype)) { + value := [value] + } + + entities := [] + entityManager := this._entityManager() + + for entityIndex, entityId in value { + if (!entityId) { + entities.Push("") + } else if (entityManager.Has(entityId)) { + entities.Push(entityManager[entityId]) + } else { + throw AppException("Entity with ID '" . entityId . "' does not exist.") + } + } + + if (!this.multiple || index) { + value := entities.Length ? entities[1] : "" + } else { + value := entities + } + + return value + } + + SetValue(value, index := "") { + if (!HasBase(value, Array.Prototype)) { + value := [value] + } + + newValues := [] + + for singleIndex, singleValue in value { + if (HasBase(singleValue, EntityBase.Prototype)) { + newValues.Push(singleValue.Id) + } else if (Type(singleValue) == "String") { + newValues.Push(singleValue) + } else { + throw AppException("Invalid entity reference data.") + } + } + + value := newValues + + if (!this.multiple || index) { + value := value.Length ? value[1] : "" + } + + super.SetValue(value, index) + return this + } + + GetEntitySelectOptions() { + options := this._getSelectQuery().Execute() + + if (!this.Definition["required"]) { + options.InsertAt(1, "") + } + + return options + } + _entityManager(entityTypeId := "") { if (!this.managerObj) { if (!entityTypeId) { entityTypeId := this.Definition["entityType"] } - this.managerObj := this.container.Get("entity_manager." . entityTypeId) + this.managerObj := this.container["entity_manager." . entityTypeId] } return this.managerObj } - _getService(entityId) { - if (!this.Definition["entityType"]) { - throw AppException("Entity type of reference field is not specified") - } + _getSelectQuery() { + query := this._entityManager().EntityQuery(EntityQuery.RESULT_TYPE_IDS) + conditions := this.Definition["selectConditions"] - entityObj := "" + if (conditions) { + if (Type(conditions) != "Array") { + conditions := [conditions] + } - if (entityId) { - entityObj := this._entityManager()[entityId] - entityObj.LoadEntity() + for index, condition in conditions { + query.Condition(condition) + } } - return entityObj - } - - _getSelectQuery() { - return this._entityManager().EntityQuery(EntityQuery.RESULT_TYPE_IDS) + return query } } diff --git a/Lib/Shared/Volantis.Entity/EntityField/ServiceReferenceField.ahk b/Lib/Shared/Volantis.Entity/EntityField/ServiceReferenceField.ahk index 10ed2747..b0c21423 100644 --- a/Lib/Shared/Volantis.Entity/EntityField/ServiceReferenceField.ahk +++ b/Lib/Shared/Volantis.Entity/EntityField/ServiceReferenceField.ahk @@ -1,6 +1,13 @@ class ServiceReferenceField extends EntityFieldBase { - ReferencedObject { - get => this.GetValue() + DefinitionDefaults(fieldDefinition) { + defaults := super.DefinitionDefaults(fieldDefinition) + + defaults["servicePrefix"] := "" + defaults["widget"] := "select" + defaults["selectOptionsCallback"] := ObjBindMethod(this, "GetServiceSelectOptions") + defaults["selectConditions"] := [] + + return defaults } GetValidators(value) { @@ -13,30 +20,34 @@ class ServiceReferenceField extends EntityFieldBase { return validators } - DefinitionDefaults(fieldDefinition) { - defaults := super.DefinitionDefaults(fieldDefinition) + GetValue(index := "") { + value := super.GetValue(index) - defaults["servicePrefix"] := "" - defaults["widget"] := "select" - defaults["selectOptionsCallback"] := ObjBindMethod(this, "GetEntitySelectOptions") - defaults["selectConditions"] := [] - - return defaults - } + if (!HasBase(value, Array.Prototype)) { + value := [value] + } - GetValue() { - serviceObj := "" - serviceId := super.GetValue() + newValues := [] - if (serviceId ) { - if (Type(serviceId) != "String") { - serviceObj := serviceId + for singleIndex, singleValue in value { + if (Type(singleValue) != "String") { + serviceObj := singleValue } else { - serviceObj := this._getService(serviceId) + serviceObj := this._getService(singleValue) + } + + if (serviceObj) { + newValues.Push(serviceObj) } } - return serviceObj + value := newValues + + if (!this.multiple || index) { + value := value.Length ? value[1] : "" + } + + return value } _getService(serviceId) { @@ -53,8 +64,20 @@ class ServiceReferenceField extends EntityFieldBase { return serviceObj } - SetValue(value) { - super.SetValue(this._getServiceId(value)) + SetValue(value, index := "") { + if (index || !this.multiple || !HasBase(value, Array.Prototype)) { + value := this._getServiceId(singleValue) + } else { + newValues := [] + + for singleIndex, singleValue in value { + newValues[singleIndex] = this._getServiceId(singleValue) + } + + value := newValues + } + + super.SetValue(value, index) } _getServiceId(value) { @@ -69,7 +92,7 @@ class ServiceReferenceField extends EntityFieldBase { return this.container.Query(this.Definition["servicePrefix"], ContainerQuery.RESULT_TYPE_NAMES, false, true) } - GetEntitySelectOptions() { + GetServiceSelectOptions() { query := this._getSelectQuery() conditions := this.Definition["selectConditions"] diff --git a/Lib/Shared/Volantis.Entity/EntityManager/EntityManagerBase.ahk b/Lib/Shared/Volantis.Entity/EntityManager/EntityManagerBase.ahk index c84eacc2..00399489 100644 --- a/Lib/Shared/Volantis.Entity/EntityManager/EntityManagerBase.ahk +++ b/Lib/Shared/Volantis.Entity/EntityManager/EntityManagerBase.ahk @@ -94,4 +94,30 @@ class EntityManagerBase extends ComponentManagerBase { childManager.LoadComponents(reloadComponents) } } + + /** + * Get an array of all IDs + * + * List managed IDs and give modules a chance to add others. + */ + ListEntities(includeManaged := true, includeExtended := true) { + entities := includeManaged + ? this.EntityQuery(EntityQuery.RESULT_TYPE_IDS).Execute() + : [] + + if (includeExtended) { + event := EntityListEvent( + EntityEvents.ENTITY_LIST_ENTITIES, + this.entityTypeId, + entities, + includeManaged, + includeExtended + ) + this.eventMgr.DispatchEvent(event) + + entities := event.EntityList + } + + return entities + } } diff --git a/Lib/Shared/Volantis.Entity/EntityStorage/EntityStorageBase.ahk b/Lib/Shared/Volantis.Entity/EntityStorage/EntityStorageBase.ahk index b471ec6f..ebf04287 100644 --- a/Lib/Shared/Volantis.Entity/EntityStorage/EntityStorageBase.ahk +++ b/Lib/Shared/Volantis.Entity/EntityStorage/EntityStorageBase.ahk @@ -24,6 +24,7 @@ class EntityStorageBase { id := this._dereferenceId(idOrObj) data := this._dereferenceData(idOrObj, data) this._saveEntityData(id, data) + return this } _saveEntityData(id, data) { @@ -48,6 +49,7 @@ class EntityStorageBase { DeleteData(idOrObj) { this._deleteEntityData(this._dereferenceId(idOrObj)) + return this } _dereferenceId(idOrObj) { @@ -64,7 +66,7 @@ class EntityStorageBase { _dereferenceData(idOrObj, data := "") { if (HasBase(idOrObj, EntityBase.Prototype) && !data) { - data := idOrObj.UnmergedFieldData + data := idOrObj.RawData } return data diff --git a/Lib/Shared/Volantis.Entity/EntityStorage/NullEntityStorage.ahk b/Lib/Shared/Volantis.Entity/EntityStorage/NullEntityStorage.ahk new file mode 100644 index 00000000..38cb4c2b --- /dev/null +++ b/Lib/Shared/Volantis.Entity/EntityStorage/NullEntityStorage.ahk @@ -0,0 +1,3 @@ +class NullEntityStorage extends EntityStorageBase { + +} diff --git a/Lib/Shared/Volantis.Entity/Event/EntityDetectValuesEvent.ahk b/Lib/Shared/Volantis.Entity/Event/EntityDetectValuesEvent.ahk new file mode 100644 index 00000000..2a5bf92c --- /dev/null +++ b/Lib/Shared/Volantis.Entity/Event/EntityDetectValuesEvent.ahk @@ -0,0 +1,17 @@ +class EntityDetectValuesEvent extends EntityEvent { + _valuesMap := "" + + Values { + get => this._valuesMap + } + + __New(eventName, entityTypeId, entityObj, values := "") { + if (!values) { + values := Map() + } + + this._valuesMap := values + + super.__New(eventName, entityTypeId, entityObj) + } +} diff --git a/Lib/Shared/Volantis.Entity/Event/EntityLayerSourcesEvent.ahk b/Lib/Shared/Volantis.Entity/Event/EntityLayerSourcesEvent.ahk new file mode 100644 index 00000000..f8431925 --- /dev/null +++ b/Lib/Shared/Volantis.Entity/Event/EntityLayerSourcesEvent.ahk @@ -0,0 +1,13 @@ +class EntityLayerSourcesEvent extends EntityEvent { + _layerSourcesObj := "" + + LayerSources { + get => this._layerSourcesObj + } + + __New(eventName, entityTypeId, entityObj, layerSourcesObj) { + this._layerSourcesObj := layerSourcesObj + + super.__New(eventName, entityTypeId, entityObj) + } +} diff --git a/Lib/Shared/Volantis.Entity/Event/EntityListEvent.ahk b/Lib/Shared/Volantis.Entity/Event/EntityListEvent.ahk new file mode 100644 index 00000000..9782b0be --- /dev/null +++ b/Lib/Shared/Volantis.Entity/Event/EntityListEvent.ahk @@ -0,0 +1,32 @@ +class EntityListEvent extends EventBase { + _entityTypeId := "" + _entityList := [] + _includeManaged := false + _includeExtended := false + + __New(eventName, entityTypeId, entityList, includeManaged, includeExtended) { + this._entityTypeId := entityTypeId + this._entityList := entityList + this._includeManaged := includeManaged + this._includeExtended := includeExtended + + super.__New(eventName) + } + + EntityTypeId { + get => this._entityTypeId + } + + EntityList { + get => this._entityList + set => this._entityList := value + } + + IncludeManaged { + get => this._includeManaged + } + + IncludeExtended { + get => this._includeExtended + } +} diff --git a/Lib/Shared/Volantis.Entity/Event/EntityParentEvent.ahk b/Lib/Shared/Volantis.Entity/Event/EntityParentEvent.ahk new file mode 100644 index 00000000..c95ed335 --- /dev/null +++ b/Lib/Shared/Volantis.Entity/Event/EntityParentEvent.ahk @@ -0,0 +1,28 @@ +class EntityParentEvent extends EntityEvent { + _parentEntity := "" + _parentEntityTypeId := "" + _parentEntityId := "" + + __New(eventName, entityTypeId, entityObj, parentEntity := "", parentEntityTypeId := "", parentEntityId := "") { + this._parentEntity := parentEntity + this._parentEntityTypeId := parentEntityTypeId + this._parentEntityId := parentEntityId + + super.__New(eventName, entityTypeId, entityObj) + } + + ParentEntity { + get => this._parentEntity + set => this._parentEntity := value + } + + ParentEntityTypeId { + get => this._parentEntityTypeId + set => this._parentEntityTypeId := value + } + + ParentEntityId { + get => this._parentEntityId + set => this._parentEntityId := value + } +} diff --git a/Lib/Shared/Volantis.Entity/Event/EntityStorageEvent.ahk b/Lib/Shared/Volantis.Entity/Event/EntityStorageEvent.ahk deleted file mode 100644 index 643d20fe..00000000 --- a/Lib/Shared/Volantis.Entity/Event/EntityStorageEvent.ahk +++ /dev/null @@ -1,13 +0,0 @@ -class EntityStorageEvent extends EntityEvent { - _storageObj := "" - - Storage { - get => this._storageObj - } - - __New(eventName, entityTypeId, entityObj, storageObj) { - this._storageObj := storageObj - - super.__New(eventName, entityTypeId, entityObj) - } -} diff --git a/Lib/Shared/Volantis.Entity/Events/EntityEvents.ahk b/Lib/Shared/Volantis.Entity/Events/EntityEvents.ahk index 1076b822..2d2ed47a 100644 --- a/Lib/Shared/Volantis.Entity/Events/EntityEvents.ahk +++ b/Lib/Shared/Volantis.Entity/Events/EntityEvents.ahk @@ -1,6 +1,6 @@ class EntityEvents { - static ENTITY_STORAGE_OBJECTS := 0x4020 - static ENTITY_STORAGE_OBJECTS_ALTER := 0x4022 + static ENTITY_LAYER_SOURCES := 0x4020 + static ENTITY_LAYER_SOURCES_ALTER := 0x4022 static ENTITY_DATA_PROCESSORS := 0x4030 static ENTITY_DATA_PROCESSORS_ALTER := 0x4032 static ENTITY_PREPARE := 0x4035 @@ -15,10 +15,14 @@ class EntityEvents { static ENTITY_REFRESH := 0x4065 static ENTITY_DATA_LAYERS := 0x4070 static ENTITY_DATA_LAYERS_ALTER := 0x4071 + static ENTITY_DETECT_VALUES := 0x4075 + static ENTITY_DETECT_VALUES_ALTER := 0x4076 static ENTITY_VALIDATE := 0x4080 static ENTITY_FIELD_DEFINITIONS := 0x4085 static ENTITY_FIELD_DEFINITIONS_ALTER := 0x4087 static ENTITY_FIELD_GROUPS := 0x4090 static ENTITY_FIELD_GROUPS_ALTER := 0x4092 static ENTITY_REFERENCE_ENTITY_SAVED := 0x4095 + static ENTITY_LIST_ENTITIES := 0x4098 + static ENTITY_DISCOVER_PARENT := 0x4100 } diff --git a/Lib/Shared/Volantis.Entity/Factory/EntityFactory.ahk b/Lib/Shared/Volantis.Entity/Factory/EntityFactory.ahk index 19af31bc..7d45933b 100644 --- a/Lib/Shared/Volantis.Entity/Factory/EntityFactory.ahk +++ b/Lib/Shared/Volantis.Entity/Factory/EntityFactory.ahk @@ -110,6 +110,6 @@ class EntityFactory { throw EntityException("Unable to create entity '" . id . "' of type '" . entityTypeObj . "' in EntityFactory") } - return %entityTypeObj%.Create(this.container, this.eventMgr, id, this.entityTypeId, this.storageObj, this.idSanitizer, parentEntity) + return %entityTypeObj%.Create(this.container, this.eventMgr, id, this.entityTypeId, this.storageObj, this.idSanitizer, true, parentEntity, this.definition["parent_entity_storage"]) } } diff --git a/Lib/Shared/Volantis.Entity/Factory/EntityTypeFactory.ahk b/Lib/Shared/Volantis.Entity/Factory/EntityTypeFactory.ahk index 88d866bd..fe637a5b 100644 --- a/Lib/Shared/Volantis.Entity/Factory/EntityTypeFactory.ahk +++ b/Lib/Shared/Volantis.Entity/Factory/EntityTypeFactory.ahk @@ -30,6 +30,7 @@ class EntityTypeFactory { "event_manager", "manager.event", "notifier", "notifier", "parent_entity_type", "", + "parent_entity_storage", false, "default_icon", "cube-outline", "icon_field", "IconSrc", "allow_view", false, @@ -40,7 +41,8 @@ class EntityTypeFactory { "manager_view_mode_parameter", "", "manager_gui", "ManageEntitiesWindow", "manager_link_in_tools_menu", false, - "manager_menu_link_text", "" + "manager_menu_link_text", "", + "storage_type", "persistent" ) } @@ -97,7 +99,7 @@ class EntityTypeFactory { ), ) - if (definition["storage_class"] == "ConfigEntityStorage" && definition["storage_config_path_parameter"]) { + if (definition["storage_type"] == "persistent" && definition["storage_class"] == "ConfigEntityStorage" && definition["storage_config_path_parameter"]) { services["config_storage." . id] := Map( "class", definition["storage_config_storage_class"], "arguments", ["@@" . definition["storage_config_path_parameter"], definition["storage_config_storage_parent_key"]] @@ -107,6 +109,11 @@ class EntityTypeFactory { "class", "PersistentConfig", "arguments", ["@config_storage." . id, "@{}", "entity_data." . id] ) + } else if (definition["storage_type"] == "runtime") { + services["config." . id] := Map( + "class", "RuntimeConfig", + "arguments", ["@{}", "entity_data." . id] + ) } entityClass := definition["entity_class"] diff --git a/Lib/Shared/Volantis.Entity/LayerSource/EntityStorageLayerSource.ahk b/Lib/Shared/Volantis.Entity/LayerSource/EntityStorageLayerSource.ahk index b58e5bb9..ec182c25 100644 --- a/Lib/Shared/Volantis.Entity/LayerSource/EntityStorageLayerSource.ahk +++ b/Lib/Shared/Volantis.Entity/LayerSource/EntityStorageLayerSource.ahk @@ -24,5 +24,7 @@ class EntityStorageLayerSource extends LayerSourceBase { if (this.HasData()) { this.storageObj.DeleteData(this.storageId) } + + return this } } diff --git a/Lib/Shared/Volantis.Entity/LayerSource/ParentEntityLayerSource.ahk b/Lib/Shared/Volantis.Entity/LayerSource/ParentEntityLayerSource.ahk new file mode 100644 index 00000000..dfa685aa --- /dev/null +++ b/Lib/Shared/Volantis.Entity/LayerSource/ParentEntityLayerSource.ahk @@ -0,0 +1,55 @@ +class ParentEntityLayerSource extends LayerSourceBase { + entityObj := "" + + __New(entityObj) { + this.entityObj := entityObj + } + + SaveData(data := "") { + this._validateParentEntity() + + this.entityObj.ParentEntity + .SetChildEntityData(this.entityObj.EntityTypeId, this.entityObj.Id, data) + + return this + } + + LoadData() { + this._validateParentEntity() + + return this.entityObj.ParentEntity + .GetChildEntityData(this.entityObj.EntityTypeId, this.entityObj.Id) + } + + HasData() { + this._validateParentEntity() + + return this.entityObj.ParentEntity + .HasChildEntityData(this.entityObj.EntityTypeId, this.entityObj.Id) + } + + DeleteData() { + this._validateParentEntity() + + this.entityObj.ParentEntity + .DeleteChildEntityData(this.entityObj.EntityTypeId, this.entityObj.Id) + + return this + } + + _validateParentEntity() { + if (!this.entityObj.ParentEntity) { + throw AppException("Parent entity not set.") + } + + if (!HasBase(this.entityObj.ParentEntity, EntityBase.Prototype)) { + throw AppException("Parent entity is not an entity.") + } + + parentData := this.entityObj.ParentEntity.GetData() + + if (!parentData) { + throw AppException("Parent entity data is not set.") + } + } +} diff --git a/Lib/Shared/Volantis.Entity/LayeredData/EntityData.ahk b/Lib/Shared/Volantis.Entity/LayeredData/EntityData.ahk index 2a3c041d..26addd79 100644 --- a/Lib/Shared/Volantis.Entity/LayeredData/EntityData.ahk +++ b/Lib/Shared/Volantis.Entity/LayeredData/EntityData.ahk @@ -3,31 +3,60 @@ class EntityData extends LayeredDataBase { entity := "" eventMgr := "" - __New(entity, layerNames, layerSources) { + __New(entity, layerNames := "", layerSources := "") { this.entityTypeId := entity.EntityTypeId this.entity := entity this.eventMgr := entity.eventMgr super.__New( entity.cloner, - this._createProcessors(), - this._getLayerNames(layerNames), - this._collectEntityStorage(layerSources) + this._createProcessors(), + layerNames, + layerSources ) } - _collectEntityStorage(layerSources) { + InitializeLayers(layerNames) { + if (!layerNames) { + layerNames := [] + } + + this._appendLayerNames(["defaults"], layerNames) + + event := EntityLayersEvent(EntityEvents.ENTITY_DATA_LAYERS, this.entityTypeId, this.entity, layerNames) + this.eventMgr.DispatchEvent(event) + + layerNames := event.Layers + this._appendLayerNames(["auto", "data"], layerNames) + + event := EntityLayersEvent(EntityEvents.ENTITY_DATA_LAYERS_ALTER, this.entityTypeId, this.entity, layerNames) + this.eventMgr.DispatchEvent(event) + + layerNames := event.Layers + layers := Map() + + for index, layerName in layerNames { + this.layerPriority.Push(layerName) + layers[layerName] := Map() + } + + this.SetLayers(layers) + } + + SetLayerSources(layerSources) { if (!layerSources.Has("defaults")) { layerSources["defaults"] := ObjBindMethod(this.entity, "InitializeDefaults") } - event := EntityStorageEvent(EntityEvents.ENTITY_STORAGE_OBJECTS, this.entityTypeId, this.entity, layerSources) + event := EntityLayerSourcesEvent(EntityEvents.ENTITY_LAYER_SOURCES, this.entityTypeId, this.entity, layerSources) this.eventMgr.DispatchEvent(event) - event := EntityStorageEvent(EntityEvents.ENTITY_STORAGE_OBJECTS_ALTER, this.entityTypeId, this.entity, event.Storage) + event := EntityLayerSourcesEvent(EntityEvents.ENTITY_LAYER_SOURCES_ALTER, this.entityTypeId, this.entity, event.LayerSources) this.eventMgr.DispatchEvent(event) - return event.Storage + for key, source in event.LayerSources { + this.SetLayerSource(key, source) + } } _createProcessors() { @@ -45,25 +74,6 @@ class EntityData extends LayeredDataBase { return event.Processors } - _getLayerNames(layerNames) { - if (!layerNames) { - layerNames := [] - } - - this._appendLayerNames(["defaults"], layerNames) - - event := EntityLayersEvent(EntityEvents.ENTITY_DATA_LAYERS, this.entityTypeId, this.entity, layerNames) - this.eventMgr.DispatchEvent(event) - - layerNames := event.Layers - this._appendLayerNames(["auto", "data"], layerNames) - - event := EntityLayersEvent(EntityEvents.ENTITY_DATA_LAYERS_ALTER, this.entityTypeId, this.entity, layerNames) - this.eventMgr.DispatchEvent(event) - - return event.Layers - } - _appendLayerNames(namesToAppend, existingNames) { for index, name in namesToAppend { exists := false diff --git a/Lib/Shared/Volantis.Module/Factory/ModuleFactory.ahk b/Lib/Shared/Volantis.Module/Factory/ModuleFactory.ahk index b873f5a9..e5b73f15 100644 --- a/Lib/Shared/Volantis.Module/Factory/ModuleFactory.ahk +++ b/Lib/Shared/Volantis.Module/Factory/ModuleFactory.ahk @@ -22,7 +22,7 @@ class ModuleFactory { ), "module." . key, Map( "class", this.classMap.Has(key) ? this.classMap[key] : "SimpleModule", - "arguments", [key, "@module_info." . key, "@module_config." . key], + "arguments", [key, "@module_info." . key, "@module_config." . key, isCore], "file", file, "enabled", enabled, "core", isCore, diff --git a/Lib/Shared/Volantis.Module/Module/ModuleBase.ahk b/Lib/Shared/Volantis.Module/Module/ModuleBase.ahk index af874ef4..2712c2b5 100644 --- a/Lib/Shared/Volantis.Module/Module/ModuleBase.ahk +++ b/Lib/Shared/Volantis.Module/Module/ModuleBase.ahk @@ -2,11 +2,13 @@ class ModuleBase { moduleInfo := "" config := "" key := "" + _core := false - __New(key, moduleInfo, config) { + __New(key, moduleInfo, config, isCore) { this.key := key this.moduleInfo := moduleInfo this.config := config + this._core := isCore } IsEnabled() { @@ -14,7 +16,7 @@ class ModuleBase { } IsCore() { - return (this.config.Has("core") && this.config["core"]) + return this._core } GetConfigValue(key, defaultValue := "") { @@ -28,7 +30,20 @@ class ModuleBase { } GetVersion() { - return this.moduleInfo.Has("version") ? this.moduleInfo["version"] : "" + versionStr := this.moduleInfo.Has("version") ? this.moduleInfo["version"] : "" + + if (versionStr == "{{VERSION}}") { + + if (AppBase.Instance) { + versionStr := AppBase.Instance.Version + } + + if (versionStr == "{{VERSION}}") { + versionStr := "Core" + } + } + + return versionStr } GetServiceFiles() { diff --git a/Lib/Shared/Volantis.Theme/Theme/ThemeBase.ahk b/Lib/Shared/Volantis.Theme/Theme/ThemeBase.ahk index 61ead15e..a33e9428 100644 --- a/Lib/Shared/Volantis.Theme/Theme/ThemeBase.ahk +++ b/Lib/Shared/Volantis.Theme/Theme/ThemeBase.ahk @@ -8,7 +8,7 @@ class ThemeBase { defaultTheme := "Lightpad" vars := Map() colors := Map("background", "FFFFFF", "text", "000000", "textInactive", "959595", "accent", "9466FC", "accentBright", "EEE6FF", "accentBg", "8A57F0", "transColor", "") - themeAssets := Map("logo", "Resources\Graphics\Logo.png", "icon", "Resources\Graphics\Launchpad.ico", "spinner", "Resources\Graphics\Spinner.gif") + themeAssets := Map("logo", "Resources\Graphics\logo.png", "icon", "Resources\Graphics\launchpad.ico", "spinner", "Resources\Graphics\spinner.gif") symbols := Map() buttons := Map("height", Map("s", 20, "m", 30, "l", 40, "xl", 80), "fixedWidth", Map("s", 80, "m", 100, "l", 120, "xl", 140)) labels := Map("height", "auto", "fixedWidth", 75, "font", "normal") diff --git a/Lib/Shared/Volantis.Utility/IncludeWriter/IncludeWriterBase.ahk b/Lib/Shared/Volantis.Utility/IncludeWriter/IncludeWriterBase.ahk index cb9e37da..aab7342d 100644 --- a/Lib/Shared/Volantis.Utility/IncludeWriter/IncludeWriterBase.ahk +++ b/Lib/Shared/Volantis.Utility/IncludeWriter/IncludeWriterBase.ahk @@ -25,7 +25,12 @@ class IncludeWriterBase { updated := this.FilesAreDifferent(this.tmpPath, this.outputPath) if (updated) { - FileDelete(this.outputPath) + try { + FileDelete(this.outputPath) + } catch Any { + throw AppException("Unable to delete file path " . this.outputPath) + } + } } diff --git a/Lib/Shared/Volantis.Utility/VersionChecker/VersionChecker.ahk b/Lib/Shared/Volantis.Utility/VersionChecker/VersionChecker.ahk index fdf689d8..fe00f734 100644 --- a/Lib/Shared/Volantis.Utility/VersionChecker/VersionChecker.ahk +++ b/Lib/Shared/Volantis.Utility/VersionChecker/VersionChecker.ahk @@ -77,7 +77,7 @@ class VersionChecker { } incrementIndex := versionArr.Length - 1 - } else if (versionArr[0] == "0") { + } else if (versionArr[1] == "0") { incrementIndex := versionArr.Length } diff --git a/Lib/TestLib/Test/AppTestBase.ahk b/Lib/TestLib/Test/AppTestBase.ahk index 028d322c..4414cacf 100644 --- a/Lib/TestLib/Test/AppTestBase.ahk +++ b/Lib/TestLib/Test/AppTestBase.ahk @@ -6,12 +6,12 @@ class AppTestBase extends TestBase { GetTestAppConfig() { config := Map( "appName", "Test App", - "developer", "Test Developer", "appDir", A_ScriptDir, "tmpDir", this.testDir . "\Temp", "dataDir", this.testDir . "\Data", "version", this.testAppVersion, "parameters", Map( + "app.developer", "Test Developer", "config.flush_cache_on_exit", false, "config.logging_level", "none", "config.module_dirs", [], diff --git a/Resources/Graphics/Icons/Dark/Backup.ico b/Resources/Graphics/Icons/Dark/backup.ico similarity index 100% rename from Resources/Graphics/Icons/Dark/Backup.ico rename to Resources/Graphics/Icons/Dark/backup.ico diff --git a/Resources/Graphics/Icons/Dark/Config.ico b/Resources/Graphics/Icons/Dark/config.ico similarity index 100% rename from Resources/Graphics/Icons/Dark/Config.ico rename to Resources/Graphics/Icons/Dark/config.ico diff --git a/Resources/Graphics/Icons/Dark/Game.ico b/Resources/Graphics/Icons/Dark/game.ico similarity index 100% rename from Resources/Graphics/Icons/Dark/Game.ico rename to Resources/Graphics/Icons/Dark/game.ico diff --git a/Resources/Graphics/Icons/Dark/Logo.ico b/Resources/Graphics/Icons/Dark/logo.ico similarity index 100% rename from Resources/Graphics/Icons/Dark/Logo.ico rename to Resources/Graphics/Icons/Dark/logo.ico diff --git a/Resources/Graphics/Icons/Dark/Module.png b/Resources/Graphics/Icons/Dark/module.png similarity index 100% rename from Resources/Graphics/Icons/Dark/Module.png rename to Resources/Graphics/Icons/Dark/module.png diff --git a/Resources/Graphics/Icons/Dark/Platform.ico b/Resources/Graphics/Icons/Dark/platform.ico similarity index 100% rename from Resources/Graphics/Icons/Dark/Platform.ico rename to Resources/Graphics/Icons/Dark/platform.ico diff --git a/Resources/Graphics/Icons/Gradient/Backup.ico b/Resources/Graphics/Icons/Gradient/backup.ico similarity index 100% rename from Resources/Graphics/Icons/Gradient/Backup.ico rename to Resources/Graphics/Icons/Gradient/backup.ico diff --git a/Resources/Graphics/Icons/Gradient/Config.ico b/Resources/Graphics/Icons/Gradient/config.ico similarity index 100% rename from Resources/Graphics/Icons/Gradient/Config.ico rename to Resources/Graphics/Icons/Gradient/config.ico diff --git a/Resources/Graphics/Icons/Gradient/Game.ico b/Resources/Graphics/Icons/Gradient/game.ico similarity index 100% rename from Resources/Graphics/Icons/Gradient/Game.ico rename to Resources/Graphics/Icons/Gradient/game.ico diff --git a/Resources/Graphics/Icons/Gradient/Logo.ico b/Resources/Graphics/Icons/Gradient/logo.ico similarity index 100% rename from Resources/Graphics/Icons/Gradient/Logo.ico rename to Resources/Graphics/Icons/Gradient/logo.ico diff --git a/Resources/Graphics/Icons/Gradient/Platform.ico b/Resources/Graphics/Icons/Gradient/platform.ico similarity index 100% rename from Resources/Graphics/Icons/Gradient/Platform.ico rename to Resources/Graphics/Icons/Gradient/platform.ico diff --git a/Resources/Graphics/Icons/Light/Backup.ico b/Resources/Graphics/Icons/Light/backup.ico similarity index 100% rename from Resources/Graphics/Icons/Light/Backup.ico rename to Resources/Graphics/Icons/Light/backup.ico diff --git a/Resources/Graphics/Icons/Light/Config.ico b/Resources/Graphics/Icons/Light/config.ico similarity index 100% rename from Resources/Graphics/Icons/Light/Config.ico rename to Resources/Graphics/Icons/Light/config.ico diff --git a/Resources/Graphics/Icons/Light/Game.ico b/Resources/Graphics/Icons/Light/game.ico similarity index 100% rename from Resources/Graphics/Icons/Light/Game.ico rename to Resources/Graphics/Icons/Light/game.ico diff --git a/Resources/Graphics/Icons/Light/Logo.ico b/Resources/Graphics/Icons/Light/logo.ico similarity index 100% rename from Resources/Graphics/Icons/Light/Logo.ico rename to Resources/Graphics/Icons/Light/logo.ico diff --git a/Resources/Graphics/Icons/Light/Module.png b/Resources/Graphics/Icons/Light/module.png similarity index 100% rename from Resources/Graphics/Icons/Light/Module.png rename to Resources/Graphics/Icons/Light/module.png diff --git a/Resources/Graphics/Icons/Light/Platform.ico b/Resources/Graphics/Icons/Light/platform.ico similarity index 100% rename from Resources/Graphics/Icons/Light/Platform.ico rename to Resources/Graphics/Icons/Light/platform.ico diff --git a/Resources/Graphics/Launchpad-256.png b/Resources/Graphics/launchpad-256.png similarity index 100% rename from Resources/Graphics/Launchpad-256.png rename to Resources/Graphics/launchpad-256.png diff --git a/Resources/Graphics/Launchpad-64.png b/Resources/Graphics/launchpad-64.png similarity index 100% rename from Resources/Graphics/Launchpad-64.png rename to Resources/Graphics/launchpad-64.png diff --git a/Resources/Graphics/Launchpad.ico b/Resources/Graphics/launchpad.ico similarity index 100% rename from Resources/Graphics/Launchpad.ico rename to Resources/Graphics/launchpad.ico diff --git a/Resources/Graphics/Logo.png b/Resources/Graphics/logo.png similarity index 100% rename from Resources/Graphics/Logo.png rename to Resources/Graphics/logo.png diff --git a/Resources/Graphics/Spinner-Steam.gif b/Resources/Graphics/spinner-steam.gif similarity index 100% rename from Resources/Graphics/Spinner-Steam.gif rename to Resources/Graphics/spinner-steam.gif diff --git a/Resources/Graphics/Spinner.gif b/Resources/Graphics/spinner.gif similarity index 100% rename from Resources/Graphics/Spinner.gif rename to Resources/Graphics/spinner.gif diff --git a/Resources/Themes/Lightpad.json b/Resources/Themes/Lightpad.json index 2049c352..bcabf3a0 100644 --- a/Resources/Themes/Lightpad.json +++ b/Resources/Themes/Lightpad.json @@ -108,9 +108,9 @@ "transColor": "" }, "themeAssets": { - "icon": "Graphics\\Launchpad.ico", - "logo": "Graphics\\Logo.png", - "spinner": "Graphics\\Spinner.gif" + "icon": "Graphics\\launchpad.ico", + "logo": "Graphics\\logo.png", + "spinner": "Graphics\\spinner.gif" }, "symbols": { "arrowDown": "ArrowDownSymbol", diff --git a/Resources/Themes/Steampad.json b/Resources/Themes/Steampad.json index 9b6eae9f..c4e36465 100644 --- a/Resources/Themes/Steampad.json +++ b/Resources/Themes/Steampad.json @@ -107,7 +107,7 @@ }, "themeAssets": { "logo": "", - "spinner": "Graphics\\Spinner-Steam.gif" + "spinner": "Graphics\\spinner-steam.gif" }, "buttons": { "styles": { diff --git a/Scripts/Build.ahk b/Scripts/Build.ahk index 9d166304..09656b22 100644 --- a/Scripts/Build.ahk +++ b/Scripts/Build.ahk @@ -9,8 +9,7 @@ appVersion := "{{VERSION}}" LaunchpadBuilder(Map( "appDir", appDir, "appName", "Launchpad", - "developer", "Volantis Development", "version", appVersion, - "trayIcon", appDir . "\Resources\Graphics\Launchpad.ico", + "trayIcon", appDir . "\Resources\Graphics\launchpad.ico", "console", true, ))