/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #include "ovms_log.h" static const char *TAG = "pluginstore"; #include #include #include #include #include #include "esp_idf_version.h" #if ESP_IDF_VERSION_MAJOR < 4 #include "strverscmp.h" #endif #include "ovms_plugins.h" #include "ovms_command.h" #include "ovms_config.h" #include "ovms_http.h" #include "ovms_buffer.h" #include "ovms_netmanager.h" #include "ovms_duktape.h" #ifdef CONFIG_OVMS_COMP_WEBSERVER #include "ovms_webserver.h" #endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER OvmsPluginStore MyPluginStore __attribute__ ((init_priority (7100))); void repo_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.RepoList(writer); } void repo_install(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.RepoInstall(writer,std::string(argv[0]),std::string(argv[0])); } void repo_remove(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.RepoRemove(writer,std::string(argv[0])); } void repo_refresh(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.RepoRefresh(writer); } void plugin_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.Summarise(writer); } void plugin_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.PluginList(writer); } void plugin_show(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.PluginShow(writer,std::string(argv[0])); } void plugin_install(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.PluginInstall(writer,std::string(argv[0])); } void plugin_remove(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.PluginRemove(writer,std::string(argv[0])); } void plugin_enable(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.PluginEnable(writer,std::string(argv[0])); } void plugin_disable(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { MyPluginStore.PluginDisable(writer,std::string(argv[0])); } void plugin_update(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (argc == 0) { MyPluginStore.PluginUpdateAll(writer); } else { MyPluginStore.PluginUpdate(writer,std::string(argv[0])); } } //////////////////////////////////////////////////////////////////////////////// // OvmsPluginStore OvmsPluginStore::OvmsPluginStore() { ESP_LOGI(TAG, "Initialising PLUGINS (7100)"); OvmsCommand* cmd_plugin = MyCommandApp.RegisterCommand("plugin","PLUGIN framework", plugin_status, "", 0, 0, false); cmd_plugin->RegisterCommand("status","Show status of plugins",plugin_status,"[nocheck]",0,1); cmd_plugin->RegisterCommand("list","Show list of plugins",plugin_list,"",0,0); cmd_plugin->RegisterCommand("show","Show plugin details",plugin_show,"",1,1); cmd_plugin->RegisterCommand("install","Install a plugin",plugin_install,"",1,1); cmd_plugin->RegisterCommand("remove","Remove a plugin",plugin_remove,"",1,1); cmd_plugin->RegisterCommand("enable","Enable a plugin",plugin_enable,"",1,1); cmd_plugin->RegisterCommand("disable","Disable a plugin",plugin_disable,"",1,1); cmd_plugin->RegisterCommand("update","Update all or a specific plugin",plugin_update,"[plugin]",0,1); OvmsCommand* cmd_repo = cmd_plugin->RegisterCommand("repo","PLUGIN Repositories", repo_list, "", 0, 0); cmd_repo->RegisterCommand("list","List repositories",repo_list,"",0,0); cmd_repo->RegisterCommand("install","Install a repository",repo_install," ",2,2); cmd_repo->RegisterCommand("remove","Remove a repository",repo_remove,"",1,1); cmd_repo->RegisterCommand("refresh","Refresh repository metadata",repo_refresh,"",0,0); MyConfig.RegisterParam("plugin", "PLUGIN store setup and status", true, true); MyConfig.RegisterParam("plugin.repos", "PLUGIN repositories", false, true); MyConfig.RegisterParam("plugin.enabled", "PLUGIN enabled", false, true); MyConfig.RegisterParam("plugin.disabled", "PLUGIN disabled", false, true); // Config Parameters: // plugin: // repo.refresh: Time (seconds) to cache repository data // plugin.repos: // A list of = for installed repositories // plugin.enabled // A list of = for installed and enabled plugins // plugin.disabled // A list of = for installed but disabled plugins } OvmsPluginStore::~OvmsPluginStore() { for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it) { delete it->second; } m_plugins.clear(); for (auto it = m_repos.begin(); it != m_repos.end(); ++it) { delete it->second; } m_repos.clear(); } void OvmsPluginStore::Summarise(OvmsWriter* writer) { MyPluginStore.LoadRepoPlugins(); writer->printf("Plugins: %d found\n",m_plugins.size()); for (auto it = m_repos.begin(); it != m_repos.end(); ++it) { writer->printf("Repository: %s version %s\n", it->first.c_str(), it->second->m_version.c_str()); } } void OvmsPluginStore::RepoList(OvmsWriter* writer) { MyPluginStore.LoadRepoPlugins(); writer->puts("Repository Version"); for (auto it = m_repos.begin(); it != m_repos.end(); ++it) { writer->printf("%-16.16s %s\n", it->first.c_str(), it->second->m_version.c_str()); } } void OvmsPluginStore::RepoInstall(OvmsWriter* writer, std::string name, std::string path) { auto search = m_repos.find(name); if (search != m_repos.end()) { writer->printf("Error: Repository '%s' was already installed\n",name.c_str()); return; } OvmsRepository* r = new OvmsRepository(name, path); m_repos[name] = r; if (r->UpdateRepo()) { writer->printf("Installed repository: %s\n", name.c_str()); } else { writer->printf("Installed, but could not refresh, repository: %s\n", name.c_str()); } } void OvmsPluginStore::RepoRemove(OvmsWriter* writer, std::string name) { auto search = m_repos.find(name); if (search == m_repos.end()) { writer->printf("Error: Repository '%s' is not installed\n",name.c_str()); return; } delete search->second; m_repos.erase(search); writer->printf("Removed repository: %s\n", name.c_str()); } void OvmsPluginStore::RepoRefresh(OvmsWriter* writer) { for (auto it = m_repos.begin(); it != m_repos.end(); ++it) { writer->printf("Refreshing repository: %s\n", it->first.c_str()); if (!it->second->UpdateRepo()) { writer->printf(" Error: Failed to refresh repository: %s\n", it->first.c_str()); } } } void OvmsPluginStore::PluginList(OvmsWriter* writer) { MyPluginStore.LoadRepoPlugins(); writer->puts("Plugin Repo Version Description"); for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it) { OvmsPlugin *p = it->second; printf("%-20.20s %-16.16s %-10.10s %s\n", p->m_name.c_str(), p->m_repo.c_str(), p->m_version.c_str(), p->m_description.c_str()); } } void OvmsPluginStore::PluginShow(OvmsWriter* writer, std::string plugin) { MyPluginStore.LoadRepoPlugins(); auto search = m_plugins.find(plugin); if (search == m_plugins.end()) { writer->puts("Error: Cannot find that plugin in repository"); return; } OvmsPlugin *p = search->second; p->Summarise(writer); } void OvmsPluginStore::PluginInstall(OvmsWriter* writer, std::string plugin) { MyPluginStore.LoadRepoPlugins(); OvmsPlugin* p = FindPlugin(plugin); if (p == NULL) { writer->printf("Error: Plugin '%s' not found\n", plugin.c_str()); return; } if (!p->Download()) { writer->printf("Error: Plugin '%s' could not be downloaded\n", plugin.c_str()); return; } if (!p->Enable()) { writer->printf("Error: Plugin '%s' could not be enabled\n", plugin.c_str()); return; } writer->printf("Plugin: %s installed ok\n", plugin.c_str()); } void OvmsPluginStore::PluginRemove(OvmsWriter* writer, std::string plugin) { MyPluginStore.LoadRepoPlugins(); writer->puts("Error: Not yet implemented"); } void OvmsPluginStore::PluginEnable(OvmsWriter* writer, std::string plugin) { if (MyConfig.IsDefined("plugin.disabled", plugin)) { std::string version = MyConfig.GetParamValue("plugin.disabled", plugin); MyConfig.DeleteInstance("plugin.disabled", plugin); MyConfig.SetParamValue("plugin.enabled", plugin, version); writer->printf("Plugin: %s enabled\n", plugin.c_str()); } else { writer->printf("Error: Plugin '%s' is not disabled\n", plugin.c_str()); } } void OvmsPluginStore::PluginDisable(OvmsWriter* writer, std::string plugin) { if (MyConfig.IsDefined("plugin.enabled", plugin)) { std::string version = MyConfig.GetParamValue("plugin.enabled", plugin); MyConfig.DeleteInstance("plugin.enabled", plugin); MyConfig.SetParamValue("plugin.disabled", plugin, version); writer->printf("Plugin: %s disabled\n", plugin.c_str()); } else { writer->printf("Error: Plugin '%s' is not enabled\n", plugin.c_str()); } } void OvmsPluginStore::PluginUpdate(OvmsWriter* writer, std::string plugin) { MyPluginStore.LoadRepoPlugins(); OvmsPlugin* p = FindPlugin(plugin); if (p == NULL) { writer->printf("Error: Plugin '%s' not found\n", plugin.c_str()); return; } std::string version = p->InstalledVersion(); if (version.empty()) { writer->printf("Error: Plugin '%s' is not installed\n", plugin.c_str()); return; } int cmp = strverscmp(p->m_version.c_str(), version.c_str()); if (cmp <= 0) { writer->printf("Plugin '%s' is already version %s (repo has %s)\n", plugin.c_str(), version.c_str(), p->m_version.c_str()); return; } if (!p->Download()) { writer->printf("Error: Plugin '%s' could not be downloaded\n", plugin.c_str()); return; } writer->printf("Plugin: %s updated ok\n", plugin.c_str()); } void OvmsPluginStore::PluginUpdateAll(OvmsWriter* writer) { MyPluginStore.LoadRepoPlugins(); for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it) { OvmsPlugin *p = it->second; std::string version = p->InstalledVersion(); if (! version.empty()) { int cmp = strverscmp(p->m_version.c_str(), version.c_str()); if (cmp > 0) { if (!p->Download()) { writer->printf("Error: Plugin '%s' could not be downloaded\n", p->m_name.c_str()); } else { writer->printf("Plugin: %s updated ok to %s\n", p->m_name.c_str(), p->m_version.c_str()); } } } } } bool OvmsPluginStore::LoadRepoPlugins() { const ConfigParamMap cpm = MyConfig.GetParamMap("plugin.repos"); if (cpm.size() == 0) { // Configure the default repo MyConfig.SetParamValue("plugin.repos","openvehicles","http://api.openvehicles.com/plugins"); } if (m_repos.size() == 0) { // Load the repos from config for (auto it = cpm.begin(); it != cpm.end(); ++it) { OvmsRepository* r = new OvmsRepository(it->first, it->second); m_repos[it->first] = r; } } for (auto it = m_repos.begin(); it != m_repos.end(); ++it) { OvmsRepository* r = it->second; if (! r->CacheRepo()) { ESP_LOGE(TAG, "Plugin repository '%s' failed to refresh", it->first.c_str()); return false; } } return true; } std::string OvmsPluginStore::RepoPath(std::string repo) { auto search = m_repos.find(repo); if (search == m_repos.end()) { return std::string(""); } else { return search->second->m_path; } } OvmsPlugin* OvmsPluginStore::FindPlugin(std::string plugin) { auto search = m_plugins.find(plugin); if (search == m_plugins.end()) { return NULL; } else { return search->second; } } void OvmsPluginStore::LoadEnabledModules(plugin_element_type_t type) { const ConfigParamMap enabled = MyConfig.GetParamMap("plugin.enabled"); if (enabled.size() > 0) { ESP_LOGI(TAG, "Loading enabled plugins (%d)",type); for (auto it=enabled.begin(); it!=enabled.end(); ++it) { std::string path("/store/plugins/"); path.append(it->first); path.append("/"); path.append(it->first); path.append(".json"); FILE *pf = fopen(path.c_str(), "r"); if (pf == NULL) { ESP_LOGE(TAG,"Plugin %s: could not be found on disk",it->first.c_str()); } else { fseek(pf,0,SEEK_END); long plen = ftell(pf); fseek(pf,0,SEEK_SET); char *pd = new char[plen+1]; memset(pd,0,plen+1); fread(pd,1,plen,pf); fclose(pf); cJSON *json = cJSON_Parse(pd); delete [] pd; if (json == NULL) { ESP_LOGE(TAG, "Plugin %s: could not parse metadata", it->first.c_str()); } else { OvmsPlugin p; p.LoadJSON(std::string("LoadEnabledModules"), json); // Iterate over the elements... for (OvmsPluginElement* e : p.m_elements) { if ((type==EL_MODULE) && (e->m_type == EL_MODULE)) { // Load an enabled script module const char* filename = "LoadEnabledModules"; duk_context* ctx = MyDuktape.DukTapeContext(); // Prepare the javascript command std::string cmd(p.m_name); cmd.append(" = require(\""); cmd.append("plugin/"); cmd.append(p.m_name); cmd.append("/"); cmd.append(e->m_path); cmd.erase(cmd.length()-3,cmd.length()); cmd.append("\");"); // We are running within Duktape task so need to eval here duk_push_string(ctx, cmd.c_str()); duk_push_string(ctx, filename); if (duk_pcompile(ctx, DUK_COMPILE_EVAL) != 0 || duk_pcall(ctx, 0) != 0) { DukOvmsErrorHandler(ctx, -1, NULL, filename); } duk_pop(ctx); ESP_LOGI(TAG," Load %s script %s", p.m_name.c_str(), e->m_path.c_str()); } #ifdef CONFIG_OVMS_COMP_WEBSERVER else if ((type==EL_WEB_PAGE) && (e->m_type == EL_WEB_PAGE)) { // Load an enabled web page MyWebServer.m_plugin_pages.insert({ e->GetAttribute("page"), PagePluginContent(p.m_name + "/" + e->m_path, true) }); MyWebServer.RegisterPage( e->GetAttribute("page"), e->GetAttribute("label"), OvmsWebServer::PluginHandler, Code2PageMenu(e->GetAttribute("menu")), Code2PageAuth(e->GetAttribute("auth")), -1); ESP_LOGI(TAG," Load %s web page %s", p.m_name.c_str(), e->GetAttribute("page").c_str()); } else if ((type==EL_WEB_HOOK) && (e->m_type == EL_WEB_HOOK)) { // Load an enabled web hook MyWebServer.m_plugin_parts.insert({ e->GetAttribute("page") + ":" + e->GetAttribute("hook"), PagePluginContent(p.m_name + "/" + e->m_path, true) }); MyWebServer.RegisterCallback( "http.plugin", e->GetAttribute("page"), OvmsWebServer::PluginCallback, -1); ESP_LOGI(TAG," Load %s web hook %s:%s", p.m_name.c_str(), e->GetAttribute("page").c_str(), e->GetAttribute("hook").c_str()); } #endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER } // Clean up cJSON_Delete(json); } } } } } //////////////////////////////////////////////////////////////////////////////// // OvmsPluginPrerequisite OvmsPluginPrerequisite::OvmsPluginPrerequisite() { } OvmsPluginPrerequisite::~OvmsPluginPrerequisite() { } void OvmsPluginPrerequisite::Summarise(OvmsWriter* writer) { writer->printf(" %s ", m_target.c_str()); switch (m_operator) { case OP_EQUAL: writer->printf("= %s\n",m_value.c_str()); break; case OP_NOTEQUAL: writer->printf("!= %s\n",m_value.c_str()); break; case OP_LESSTHAN: writer->printf("< %s\n",m_value.c_str()); break; case OP_LESSTHANEQUAL: writer->printf("<= %s\n",m_value.c_str()); break; case OP_GREATERTHAN: writer->printf("> %s\n",m_value.c_str()); break; case OP_GREATERTHANEQUAL: writer->printf(">= %s\n",m_value.c_str()); break; case OP_EXISTS: writer->puts(m_value.c_str()); break; } } bool OvmsPluginPrerequisite::LoadJSON(cJSON *json) { if (json->type != cJSON_String) { ESP_LOGE(TAG, "Prerequisite type %d unexpected",json->type); return false; } m_target = std::string(json->valuestring); size_t p = m_target.find("!="); if (p != std::string::npos) { m_value = m_target.substr(p+2); m_target.resize(p); m_operator = OP_NOTEQUAL; return true; } p = m_target.find(">="); if (p != std::string::npos) { m_value = m_target.substr(p+2); m_target.resize(p); m_operator = OP_GREATERTHANEQUAL; return true; } p = m_target.find("<="); if (p != std::string::npos) { m_value = m_target.substr(p+2); m_target.resize(p); m_operator = OP_LESSTHANEQUAL; return true; } p = m_target.find("<"); if (p != std::string::npos) { m_value = m_target.substr(p+1); m_target.resize(p); m_operator = OP_LESSTHAN; return true; } p = m_target.find(">"); if (p != std::string::npos) { m_value = m_target.substr(p+1); m_target.resize(p); m_operator = OP_GREATERTHAN; return true; } p = m_target.find("="); if (p != std::string::npos) { m_value = m_target.substr(p+1); m_target.resize(p); m_operator = OP_EQUAL; return true; } m_operator = OP_EXISTS; ESP_LOGI(TAG,"Prerequisite '%s' T=%s O=%d V=%s", json->valuestring, m_target.c_str(), m_operator, m_value.c_str()); return true; } //////////////////////////////////////////////////////////////////////////////// // OvmsPluginElement OvmsPluginElement::OvmsPluginElement() { } OvmsPluginElement::OvmsPluginElement(plugin_element_type_t type, std::string name, std::string path) { m_type = type; m_name = name; m_path = path; if ((m_path.empty())&&(m_type == EL_JSON)) { // Helper for the JSON element m_path = name; m_path.append(".json"); } } OvmsPluginElement::~OvmsPluginElement() { } void OvmsPluginElement::Summarise(OvmsWriter* writer) { writer->printf(" Name: %s\n", m_name.c_str()); writer->printf(" Path: %s\n", m_path.c_str()); writer->printf(" Type: "); switch (m_type) { case EL_JSON: writer->puts("json"); break; case EL_MODULE: writer->puts("module"); break; case EL_WEB_PAGE: writer->puts("webpage"); break; case EL_WEB_HOOK: writer->puts("webhook"); break; case EL_WEB_RSC: writer->puts("webrsc"); break; } if (m_attr.size() > 0) { writer->puts(" Attr:"); for (auto it = m_attr.begin(); it != m_attr.end(); ++it) { writer->printf(" %s:%s\n", it->first.c_str(), it->second.c_str()); } } } bool OvmsPluginElement::LoadJSON(cJSON *json) { if (json->type != cJSON_Object) { ESP_LOGE(TAG, "Element type %d unexpected",json->type); return false; } cJSON *el = json->child; while (el != NULL) { if ((el->type == cJSON_String) && (strcmp(el->string,"name")==0)) { m_name = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"path")==0)) { m_path = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"type")==0)) { if (strcmp(el->valuestring,"module")==0) { m_type = EL_MODULE; } else if (strcmp(el->valuestring,"webpage")==0) { m_type = EL_WEB_PAGE; } else if (strcmp(el->valuestring,"webhook")==0) { m_type = EL_WEB_HOOK; } else { m_type = EL_WEB_RSC; } } else { // Treat it as an attribute m_attr[std::string(el->string)] = std::string(el->valuestring); } el = el->next; } return true; } std::string OvmsPluginElement::GetAttribute(std::string name) { auto search = m_attr.find(name); if (search != m_attr.end()) { return search->second; } else { return std::string(""); } } //////////////////////////////////////////////////////////////////////////////// // OvmsPlugin OvmsPlugin::OvmsPlugin() { } OvmsPlugin::~OvmsPlugin() { for (OvmsPluginPrerequisite *p : m_prerequisites) { delete p; } m_prerequisites.clear(); for (OvmsPluginElement *p : m_elements) { delete p; } m_elements.clear(); } void OvmsPlugin::Summarise(OvmsWriter* writer) { writer->printf("Plugin: %s\n\n",m_name.c_str()); writer->printf(" Title: %s\n",m_title.c_str()); writer->printf(" Description: %s\n",m_description.c_str()); writer->printf(" Group: %s\n",m_group.c_str()); writer->printf(" Info: %s\n",m_info.c_str()); writer->printf(" Maintainer: %s\n",m_maintainer.c_str()); writer->printf(" Repository: %s\n",m_repo.c_str()); writer->printf(" Version: %s\n",m_version.c_str()); if (m_prerequisites.size() > 0) { writer->printf("\n Prerequisites:\n"); for (OvmsPluginPrerequisite *p : m_prerequisites) { p->Summarise(writer); } } if (m_elements.size() > 0) { writer->printf("\n Elements:\n"); for (OvmsPluginElement *p : m_elements) { p->Summarise(writer); } } } bool OvmsPlugin::LoadJSON(std::string repo, cJSON *json) { m_repo = repo; cJSON* el = json->child; while (el != NULL) { // Process the given element if ((el->type == cJSON_String) && (strcmp(el->string,"name")==0)) { m_name = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"title")==0)) { m_title = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"description")==0)) { m_description = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"group")==0)) { m_group = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"info")==0)) { m_info = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"maintainer")==0)) { m_maintainer = std::string(el->valuestring); } else if ((el->type == cJSON_String) && (strcmp(el->string,"version")==0)) { m_version = std::string(el->valuestring); } else if ((el->type == cJSON_Array) && (strcmp(el->string,"prerequisites")==0)) { cJSON* cel = el->child; while (cel != NULL) { OvmsPluginPrerequisite *p = new OvmsPluginPrerequisite(); if (!p->LoadJSON(cel)) { ESP_LOGE(TAG, "Invalid plugin prerequisite in metadata"); delete p; return false; } m_prerequisites.push_back(p); cel = cel->next; } } else if ((el->type == cJSON_Array) && (strcmp(el->string,"elements")==0)) { cJSON* cel = el->child; while (cel != NULL) { OvmsPluginElement *p = new OvmsPluginElement(); if (!p->LoadJSON(cel)) { ESP_LOGE(TAG, "Invalid plugin element in metadata"); delete p; return false; } m_elements.push_back(p); cel = cel->next; } } else { return false; } el = el->next; } // Add on the JSON plugin descriptor file m_elements.push_back(new OvmsPluginElement(EL_JSON,m_name,std::string(""))); return true; } bool OvmsPlugin::Download() { std::string repopath = MyPluginStore.RepoPath(m_repo); if (repopath.empty()) { ESP_LOGE(TAG, "Error: cannot find path for repository: %s",m_repo.c_str()); return false; } ESP_LOGI(TAG, "Downloading plugin: %s, with %d element(s)", m_name.c_str(), m_elements.size()); std::string pluginpath("/store/plugins"); mkdir(pluginpath.c_str(),0); pluginpath.append("/"); pluginpath.append(m_name.c_str()); mkdir(pluginpath.c_str(),0); for (OvmsPluginElement *p : m_elements) { std::string url(repopath); url.append("/"); url.append(m_name); url.append("/"); url.append(p->m_path); OvmsHttpClient http; if (!http.Request(url)) { ESP_LOGE(TAG, "Element %s: HTTP request failed",p->m_path.c_str()); return false; } std::string body = http.GetBodyAsString(); std::string dest(pluginpath); dest.append("/"); dest.append(p->m_path); FILE *pf = fopen(dest.c_str(), "w"); size_t written = fwrite(body.c_str(), 1, body.length(), pf); fclose(pf); if (written != body.length()) { ESP_LOGE(TAG, "Element: %s: VFS body size %d doesn't match %d", p->m_path.c_str(), written, body.length()); return false; } ESP_LOGI(TAG, "Element: %s downloaded ok", p->m_path.c_str()); } ESP_LOGI(TAG, "Plugin: %s downloaded ok", m_name.c_str()); return true; } bool OvmsPlugin::Enable() { if (MyConfig.IsDefined("plugin.disabled", m_name)) { MyConfig.DeleteInstance("plugin.disabled", m_name); } MyConfig.SetParamValue("plugin.enabled", m_name, m_version); return true; } bool OvmsPlugin::Disable() { if (MyConfig.IsDefined("plugin.enabled", m_name)) { MyConfig.DeleteInstance("plugin.enabled", m_name); } MyConfig.SetParamValue("plugin.disabled", m_name, m_version); return true; } std::string OvmsPlugin::InstalledVersion() { std::string version; if (MyConfig.IsDefined("plugin.enabled", m_name)) { version = MyConfig.GetParamValue("plugin.enabled", m_name); } else if (MyConfig.IsDefined("plugin.disabled", m_name)) { version = MyConfig.GetParamValue("plugin.disabled", m_name); } return version; } //////////////////////////////////////////////////////////////////////////////// // OvmsRepository OvmsRepository::OvmsRepository(std::string name, std::string path) { m_name = name; m_path = path; m_lastrefresh = 0; } OvmsRepository::~OvmsRepository() { } bool OvmsRepository::CacheRepo() { if ((m_lastrefresh > 0) && ((m_lastrefresh + MyConfig.GetParamValueInt("plugin","repo.refresh",3600)) > monotonictime)) { return true; } // Repository does not need a refresh return UpdateRepo(); } bool OvmsRepository::UpdateRepo() { std::string url = m_path; url.append("/plugins.rev"); OvmsHttpClient http; if (!http.Request(url)) { ESP_LOGE(TAG, "Repo %s: HTTP request failed",m_name.c_str()); return false; } if (!http.IsOpen() || (http.ResponseCode() != 200) || !http.BodyHasLine()) { ESP_LOGE(TAG, "Repo %s: HTTP response invalid",m_name.c_str()); return false; } std::string version = http.BodyReadLine(); if (version.compare(m_version) == 0) { ESP_LOGI(TAG, "Plugin repository %s version: %s",m_name.c_str(), version.c_str()); return true; } ESP_LOGI(TAG, "Plugin repository %s has changed, retrieving latest version", m_name.c_str()); url = m_path; url.append("/plugins.json"); http.Reset(); if (!http.Request(url)) { ESP_LOGE(TAG, "Repo %s: HTTP request failed",m_name.c_str()); return false; } std::string body = http.GetBodyAsString(); ESP_LOGD(TAG, "Retrieved %d byte(s) of store metadata",body.length()); const char *bp = body.c_str(); cJSON *json = cJSON_Parse(bp); if (json == NULL) { ESP_LOGE(TAG, "Repo %s: Could not parse metadata (%d)", m_name.c_str(), cJSON_GetErrorPtr()-bp); return false; } for (auto it = MyPluginStore.m_plugins.begin(); it != MyPluginStore.m_plugins.end(); ++it) { if (it->second->m_repo.compare(m_name) == 0) { delete it->second; MyPluginStore.m_plugins.erase(it); } } if (!LoadPlugins(json)) { ESP_LOGE(TAG, "Repo %s: Could not log plugins from repository metadata", m_name.c_str()); cJSON_Delete(json); return false; } // Free up the cJSON structure cJSON_Delete(json); // Update version, and quit ESP_LOGI(TAG, "Plugin repository %s version: %s",m_name.c_str(), version.c_str()); m_version = version; m_lastrefresh = monotonictime; return true; } bool OvmsRepository::LoadPlugins(cJSON *json) { // Load the plugins from the provided cJSON structure if (json == NULL) { ESP_LOGE(TAG, "Repo %s: Plugin metadata is not defined",m_name.c_str()); return false; } if (json->type != cJSON_Array) { ESP_LOGE(TAG, "Repo %s: Unexpected root type %d for plugin metadata",m_name.c_str(),json->type); return false; } if (json->child == NULL) { ESP_LOGE(TAG, "Repo %s: Unexpected empty list of plugins in metadata",m_name.c_str()); return false; } cJSON* el = json->child; while (el != NULL) { // Process the given element if (el->type != cJSON_Object) { ESP_LOGE(TAG, "Repo %s: Invalid plugin type %d in metadata",m_name.c_str(),el->type); return false; } OvmsPlugin *p = new OvmsPlugin(); if (!p->LoadJSON(m_name,el)) { ESP_LOGE(TAG, "Repo %s: Invalid plugin in metadata",m_name.c_str()); delete p; return false; } MyPluginStore.m_plugins[p->m_name] = p; el = el->next; } return true; }