OVMS3/OVMS.V3/components/ovms_plugins/src/ovms_plugins.cpp

1128 lines
31 KiB
C++

/*
; 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 <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string>
#include <string.h>
#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,"<plugin>",1,1);
cmd_plugin->RegisterCommand("install","Install a plugin",plugin_install,"<plugin>",1,1);
cmd_plugin->RegisterCommand("remove","Remove a plugin",plugin_remove,"<plugin>",1,1);
cmd_plugin->RegisterCommand("enable","Enable a plugin",plugin_enable,"<plugin>",1,1);
cmd_plugin->RegisterCommand("disable","Disable a plugin",plugin_disable,"<plugin>",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,"<repo> <path>",2,2);
cmd_repo->RegisterCommand("remove","Remove a repository",repo_remove,"<repo>",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 <name>=<url> for installed repositories
// plugin.enabled
// A list of <name>=<version> for installed and enabled plugins
// plugin.disabled
// A list of <name>=<version> 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;
}