1128 lines
31 KiB
C++
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;
|
|
}
|