diff --git a/src/Interface/Tabs/Plugin.as b/src/Interface/Tabs/Plugin.as index b452ca4..7a02a7a 100644 --- a/src/Interface/Tabs/Plugin.as +++ b/src/Interface/Tabs/Plugin.as @@ -2,6 +2,7 @@ class PluginTab : Tab { Net::HttpRequest@ m_requestMain; Net::HttpRequest@ m_requestChangelog; + Net::HttpRequest@ m_requestDependencies; bool m_error = false; string m_errorMessage; @@ -61,6 +62,15 @@ class PluginTab : Tab } } + void CheckRequestDependencies() + { + // If there's a request, check if it has finished + if (m_requestDependencies !is null && m_requestDependencies.Finished()) { + API::GetPluginListPost(m_requestDependencies); + @m_requestDependencies = null; + } + } + void CheckRequestChangelog() { // If there's a request, check if it has finished @@ -88,6 +98,10 @@ class PluginTab : Tab void HandleResponse(const Json::Value &in js) { @m_plugin = PluginInfo(js); + string[] missingDeps = m_plugin.GetMissingDeps(); + if (missingDeps.Length > 0) { + @m_requestDependencies = API::GetPluginList(missingDeps); + } } void HandleErrorResponse(const string &in message, int code) @@ -121,15 +135,43 @@ class PluginTab : Tab return true; } + bool HasMissingRequirements() + { + if (m_plugin.m_dep_req.Length == 0) return false; + + for (uint i = 0; i < m_plugin.m_dep_req.Length; i++) { + auto plug = Meta::GetPluginFromID(m_plugin.m_dep_req[i]); + if (plug is null) return true; + } + return false; + } + void InstallAsync() { m_updating = true; + // install any required dependents first + string[] missingDeps = m_plugin.GetMissingDeps(); + for (uint i = 0; i < missingDeps.Length; i++) { + PluginInfo@ dep = API::getCachedPluginInfo(missingDeps[i]); + + if (dep is null) { + error("Unable to find required plugin info: " + missingDeps[i]); + continue; + } + + PluginInstallAsync(dep.m_siteID, dep.m_id, dep.m_version); + } + PluginInstallAsync(m_plugin.m_siteID, m_plugin.m_id, m_plugin.m_version); m_plugin.m_downloads++; m_plugin.CheckIfInstalled(); m_updating = false; + + if (missingDeps.Length > 0) { + @m_requestDependencies = API::GetPluginList(missingDeps); + } } void UpdateAsync() @@ -195,6 +237,11 @@ class PluginTab : Tab if (UI::GreenButton(Icons::Download + " Install")) { startnew(CoroutineFunc(InstallAsync)); } + if (UI::IsItemHovered() && HasMissingRequirements()) { + UI::BeginTooltip(); + UI::Text("Note: this will also install any missing required dependencies listed below."); + UI::EndTooltip(); + } return; } @@ -212,12 +259,58 @@ class PluginTab : Tab } } + void RenderDependency(const string &in dep) + { + // is the plugin installed? + auto plug = Meta::GetPluginFromID(dep); + if (plug !is null) { + if (plug.Source == Meta::PluginSource::ApplicationFolder) { // openplanet bundled + UI::Text("\\$f39" + Icons::Heartbeat + "\\$z" + plug.Name + " \\$666(built-in)"); + } else { + UI::Text(plug.Name + " \\$666(installed)"); + if (UI::IsItemClicked()) { + g_window.AddTab(PluginTab(plug.SiteID), true); + } + if (UI::IsItemHovered()) { + UI::SetMouseCursor(UI::MouseCursor::Hand); + UI::BeginTooltip(); + UI::Text(plug.Name + " \\$999by " + plug.Author + " \\$666(" + plug.Version + " installed)"); + UI::Text("Click to open this plugin in a new tab."); + UI::EndTooltip(); + } + } + } else { + // plugin not installed, let's see what info we have on it... + PluginInfo@ x = API::getCachedPluginInfo(dep); + if (x !is null) { + // not installed but we have info + UI::Text("\\$999"+x.m_name); + if (UI::IsItemClicked()) { + g_window.AddTab(PluginTab(x.m_siteID), true); + } + if (UI::IsItemHovered()) { + UI::SetMouseCursor(UI::MouseCursor::Hand); + UI::BeginTooltip(); + UI::Text(x.m_name + " \\$999by " + x.m_author); + UI::Text("Click to open this plugin in a new tab."); + UI::EndTooltip(); + } + + } else { + // no fukken clue + UI::Text("Unknown plugin '"+dep+"'"); + } + + } + } + void Render() override { float scale = UI::GetScale(); CheckRequestMain(); CheckRequestChangelog(); + CheckRequestDependencies(); if (m_requestMain !is null) { UI::Text("Loading plugin.."); @@ -271,6 +364,36 @@ class PluginTab : Tab OpenBrowserURL(m_plugin.m_donateURL); } + if (m_plugin.m_dep_req.Length + m_plugin.m_dep_opt.Length > 0) { + UI::Separator(); + if (m_plugin.m_dep_req.Length > 0) { + if (UI::TreeNode("Required dependencies:", UI::TreeNodeFlags::DefaultOpen)) { + if (UI::IsItemHovered()) { + UI::BeginTooltip(); + UI::Text("This plugin will not work without all of these plugins installed"); + UI::EndTooltip(); + } + for (uint i = 0; i < m_plugin.m_dep_req.Length; i++) { + RenderDependency(m_plugin.m_dep_req[i]); + } + UI::TreePop(); + } + } + if (m_plugin.m_dep_opt.Length > 0) { + if (UI::TreeNode("Optional dependencies:", UI::TreeNodeFlags::DefaultOpen)) { + if (UI::IsItemHovered()) { + UI::BeginTooltip(); + UI::Text("This plugin provides enhanced functionality if these plugins are installed"); + UI::EndTooltip(); + } + for (uint i = 0; i < m_plugin.m_dep_opt.Length; i++) { + RenderDependency(m_plugin.m_dep_opt[i]); + } + UI::TreePop(); + } + } + } + UI::EndChild(); // Right side of the window diff --git a/src/Update.as b/src/Update.as index 715babf..cabe2d2 100644 --- a/src/Update.as +++ b/src/Update.as @@ -60,6 +60,18 @@ void PluginUpdateAsync(ref@ update) return; } + // install any unmet dependencies + for (uint i = 0; i < au.m_requirements.Length; i++) { + auto plug = Meta::GetPluginFromID(au.m_requirements[i]); + if (plug is null) { + PluginInfo@ dep = API::getCachedPluginInfo(au.m_requirements[i]); + if (dep is null) { + error("Unable to find required plugin info: " + au.m_requirements[i]); + continue; + } + PluginInstallAsync(dep.m_siteID, dep.m_id, dep.m_version); + } + } // If the plugin is currently loaded auto installedPlugin = Meta::GetPluginFromSiteID(au.m_siteID); if (installedPlugin !is null) { diff --git a/src/UpdateCheck.as b/src/UpdateCheck.as index 2eb7fb2..b0b2fb4 100644 --- a/src/UpdateCheck.as +++ b/src/UpdateCheck.as @@ -5,6 +5,18 @@ class AvailableUpdate string m_identifier; string m_oldVersion; string m_newVersion; + string[] m_requirements; + + bool HasMissingRequirements() + { + if (m_requirements.Length == 0) return false; + + for (uint i = 0; i < m_requirements.Length; i++) { + auto plug = Meta::GetPluginFromID(m_requirements[i]); + if (plug is null) return true; + } + return false; + } } array g_availableUpdates; @@ -65,6 +77,8 @@ void CheckForUpdatesAsync() return; } + string[] needDepInfo; + // Go through returned list of versions for (uint i = 0; i < js.Length; i++) { auto jsVersion = js[i]; @@ -97,9 +111,23 @@ void CheckForUpdatesAsync() au.m_identifier = siteIdentifier; au.m_oldVersion = info.m_version.ToString(); au.m_newVersion = siteVersion; + + if (jsVersion["required_dependencies"] == Json::Type::Array) { + for (uint d = 0; d < jsVersion["required_dependencies"].Length; d++) { + au.m_requirements.InsertLast(jsVersion["required_dependencies"][d]); + if (API::getCachedPluginInfo(jsVersion["required_dependencies"][d]) is null) { + needDepInfo.InsertLast(jsVersion["required_dependencies"][d]); + } + } + } + g_availableUpdates.InsertLast(au); } + if (needDepInfo.Length > 0) { + API::GetPluginListAsync(needDepInfo); + } + if (g_availableUpdates.Length == 0) { trace("No plugin updates found!"); } diff --git a/src/Utils/API.as b/src/Utils/API.as index b91f33b..83555ee 100644 --- a/src/Utils/API.as +++ b/src/Utils/API.as @@ -24,4 +24,78 @@ namespace API } return Json::Parse(req.String()); } + + void GetPluginListAsync(string[] missing) + { + uint pages = 1; + + string fetchMissing = string::Join(missing, ','); + if (fetchMissing.Length > 0) { + for (uint i = 0; i < pages; i++) { + Json::Value req = GetAsync("plugins?idents=" + fetchMissing + "&page=" + i); + + if (pages == 1 && req["pages"] != 1) { + pages = req["pages"]; + } + + for (uint ii = 0; ii < req["items"].Length; ii++) { + // kill any existing values + for (uint iii = 0; iii < g_cachedAPIPluginList.Length; iii++) { // how deep can we go + if (string(g_cachedAPIPluginList[iii]['identifier']) == string(req["items"][ii]['identifier'])) { + g_cachedAPIPluginList.RemoveAt(iii); + } + } + + g_cachedAPIPluginList.InsertLast(req["items"][ii]); + } + + // nap a bit so we dont wreck the Openplanet API... + sleep(500); + } + } + } + + /** + * special version of GetPluginListAsync for use in Render contexts + * + * only supports a single page, but its only use case is in PluginTab + * and if there's enough deps to cause pagination you can fuck right off. + */ + Net::HttpRequest@ GetPluginList(string[] missing) + { + string fetchMissing = string::Join(missing, ','); + Net::HttpRequest@ req = Get("plugins?idents=" + fetchMissing); + return req; + } + + void GetPluginListPost(Net::HttpRequest@ request) + { + Json::Value req = Json::Parse(request.String()); + for (uint ii = 0; ii < req["items"].Length; ii++) { + // kill any existing values + for (uint iii = 0; iii < g_cachedAPIPluginList.Length; iii++) { // how deep can we go + if (string(g_cachedAPIPluginList[iii]['identifier']) == string(req["items"][ii]['identifier'])) { + g_cachedAPIPluginList.RemoveAt(iii); + } + } + + g_cachedAPIPluginList.InsertLast(req["items"][ii]); + } + } + + PluginInfo@ getCachedPluginInfo(const string &in ident) + { + Json::Value@ js; + for (uint i = 0; i < g_cachedAPIPluginList.Length; i++) { + if (string(g_cachedAPIPluginList[i]['identifier']).ToLower() == ident.ToLower()) { + @js = g_cachedAPIPluginList[i]; + } + } + if (js !is null) { + // not installed but we have info + return PluginInfo(js); + } else { + return null; + } + } } diff --git a/src/Utils/PluginInfo.as b/src/Utils/PluginInfo.as index 270edbf..056dbe6 100644 --- a/src/Utils/PluginInfo.as +++ b/src/Utils/PluginInfo.as @@ -28,6 +28,11 @@ class PluginInfo array m_tags; array m_changelogs; + array m_dep_req; + array m_dep_opt; + + Json::Value@ m_dep_info; + bool m_isInstalled; Net::HttpRequest@ m_changelogRequest; @@ -50,12 +55,24 @@ class PluginInfo m_description = js["description"]; } - if (js.HasKey("links")) { - auto jsLinks = js["links"]; - m_donateURL = jsLinks["donate"].GetType() == Json::Type::String ? jsLinks["donate"] : ""; // setting to null instead of "" crashes openplanet compiler - m_sourceURL = jsLinks["source"].GetType() == Json::Type::String ? jsLinks["source"] : ""; - m_issuesURL = jsLinks["issues"].GetType() == Json::Type::String ? jsLinks["issues"] : ""; - } + if (js.HasKey("links")) { + auto jsLinks = js["links"]; + m_donateURL = jsLinks["donate"].GetType() == Json::Type::String ? jsLinks["donate"] : ""; // setting to null instead of "" crashes openplanet compiler + m_sourceURL = jsLinks["source"].GetType() == Json::Type::String ? jsLinks["source"] : ""; + m_issuesURL = jsLinks["issues"].GetType() == Json::Type::String ? jsLinks["issues"] : ""; + } + + if (js.HasKey("dependencies") && js["dependencies"].GetType() == Json::Type::Array) { + for (uint i = 0; i < js["dependencies"].Length; i++) { + m_dep_req.InsertLast(js["dependencies"][i]); + } + } + + if (js.HasKey("optional_dependencies") && js["optional_dependencies"].GetType() == Json::Type::Array) { + for (uint i = 0; i < js["optional_dependencies"].Length; i++) { + m_dep_opt.InsertLast(js["optional_dependencies"][i]); + } + } m_filesize = js["filesize"]; m_signed = js["signed"]; @@ -121,6 +138,32 @@ class PluginInfo } } + void LoadDependencyInfo() + { + string[] missingDeps = GetMissingDeps(); + if (missingDeps.Length > 0) { + API::GetPluginListAsync(missingDeps); + } + } + + string[] GetMissingDeps() + { + string[] missingDeps; + for (uint i = 0; i < m_dep_req.Length; i++) { + if (Meta::GetPluginFromID(m_dep_req[i]) is null) { + missingDeps.InsertLast(m_dep_req[i]); + } + } + + // copy paste for optionals + for (uint i = 0; i < m_dep_opt.Length; i++) { + if (Meta::GetPluginFromID(m_dep_opt[i]) is null) { + missingDeps.InsertLast(m_dep_opt[i]); + } + } + return missingDeps; + } + void LoadChangelog() { @m_changelogRequest = API::Get("plugin/" + m_siteID + "/versions"); diff --git a/src/main.as b/src/main.as index 53cc537..988b30a 100644 --- a/src/main.as +++ b/src/main.as @@ -1,4 +1,5 @@ UI::Font@ g_fontHeader; +Json::Value@[] g_cachedAPIPluginList; UI::Font@ g_fontSubHeader; void Main()