31#include <wx/filename.h>
34#include "nlohmann/json.hpp"
35#include "observable/evtvar.h"
36#include "observable/global_var.h"
47static const std::string SEP(
"\\");
49static const std::string SEP(
"/");
52static const char*
const DOWNLOAD_REPO =
53 "https://raw.githubusercontent.com/OpenCPN/plugins";
55static const char*
const DOWNLOAD_PATH =
"/@branch@/ocpn-plugins.xml";
57static const char*
const API_ENDPOINT =
"https://api.github.com/repos";
59static const char*
const API_PATH =
"/OpenCPN/plugins/branches";
62 : status(ServerStatus::UNKNOWN), m_catalog_status(ServerStatus::UNKNOWN) {
63 if (g_catalog_channel ==
"") {
64 g_catalog_channel = DEFAULT_CHANNEL;
77 std::string url = std::string(DOWNLOAD_REPO) + DOWNLOAD_PATH;
78 ocpn::replace(url,
"@branch@", g_catalog_channel.ToStdString());
85 if (m_catalog_status == ServerStatus::OK) {
92 m_catalog_status = ServerStatus::FILE_ERROR;
95 file.open(path, std::ios::in);
97 std::string xml((std::istreambuf_iterator<char>(file)),
98 std::istreambuf_iterator<char>());
101 m_catalog_status = status;
102 return &m_catalogctx;
105 return &m_catalogctx;
109 if (m_catalog_status == ServerStatus::OK) {
110 m_catalogctx.plugins.push_back(metadata);
117 std::string path(g_catalog_custom_url.ToStdString());
119 path = std::string(DOWNLOAD_REPO) + DOWNLOAD_PATH;
120 ocpn::replace(path,
"@branch@", g_catalog_channel.ToStdString());
121 wxLogMessage(
"Effective catalog path: %s", path.c_str());
124 bool ok = downloader.
download(stream);
126 return ServerStatus::OK;
129 return ServerStatus::CURL_ERROR;
135 bool ok = downloader.
download(stream);
137 return ServerStatus::OK;
140 return ServerStatus::CURL_ERROR;
144 if (filePath ==
"") {
145 filePath = wxFileName::CreateTempFileName(
"ocpn_dl").ToStdString();
147 std::ofstream stream;
148 stream.open(filePath.c_str(), std::ios::out | std::ios::trunc);
149 if (!stream.is_open()) {
150 wxLogMessage(
"CatalogHandler: Cannot open %s for write", filePath);
151 error_msg = strerror(errno);
152 return ServerStatus::OS_ERROR;
161 if (filePath ==
"") {
162 filePath = wxFileName::CreateTempFileName(
"ocpn_dl").ToStdString();
164 std::ofstream stream;
165 stream.open(filePath.c_str(), std::ios::out | std::ios::trunc);
166 if (!stream.is_open()) {
167 wxLogMessage(
"CatalogHandler: Cannot open %s for write", filePath);
168 error_msg = strerror(errno);
169 return ServerStatus::OS_ERROR;
182 std::ifstream plugin_xml(path);
183 std::stringstream ss;
184 ss << plugin_xml.rdbuf();
186 if (ss.str().size() == 0) {
189 ::ParsePlugin(ss.str().c_str(), metadata);
190 metadata.is_imported =
true;
191 ctx->plugins.push_back(metadata);
193 while (ctx->meta_urls.size() > 0) {
194 std::ostringstream xml;
195 url = ctx->meta_urls.back();
196 ctx->meta_urls.pop_back();
199 auto match = [url](
const std::string& s) {
return url == s; };
200 const auto& haystack = ctx->parsed_metas;
201 auto found = std::find_if(haystack.begin(), haystack.end(), match);
202 if (found != haystack.end()) {
205 ctx->parsed_metas.push_back(url);
207 wxLogMessage(
"CatalogHandler: Cannot download meta-url: %s", url.c_str());
214 wxLogWarning(
"Cannot parse xml starting with: %s",
215 xml.substr(0, 60).c_str());
217 return ok ? ServerStatus::OK : ServerStatus::XML_ERROR;
224 if (status == ServerStatus::OK && latest) {
225 this->latest_data.version = ctx.version;
226 this->latest_data.date = ctx.date;
227 this->latest_data.undef =
false;
235 for (
auto c : channels) {
237 obs::GlobalVar<wxString> catalog_channel(&g_catalog_channel);
238 catalog_channel.Set(channel);
242 wxLogMessage(
"Attempt to set illegal active channel: %s", channel);
247 return g_catalog_channel.ToStdString();
251 g_catalog_custom_url = url;
255 if (latest_data.undef) {
256 std::ostringstream os;
264void CatalogHandler::LoadCatalogData(
const std::string& path,
273 file.open(path, std::ios::in);
274 if (file.is_open()) {
275 std::string xml((std::istreambuf_iterator<char>(file)),
276 std::istreambuf_iterator<char>());
280 if (status == ServerStatus::OK) {
281 data.version = ctx.version;
282 data.date = ctx.date;
289 if (user_data.undef) {
293 path +=
"ocpn-plugins.xml";
294 LoadCatalogData(path, user_data);
300 if (default_data.undef) {
302 std::string path =
g_BasePlatform->GetSharedDataDir().ToStdString();
304 path +=
"ocpn-plugins.xml";
305 LoadCatalogData(path, default_data);
311 default_data.undef =
true;
312 user_data.undef =
true;
313 latest_data.undef =
true;
314 m_catalog_status = ServerStatus::UNKNOWN;
316 m_catalogctx.plugins.clear();
317 m_catalogctx.meta_urls.clear();
318 m_catalogctx.parsed_metas.clear();
319 m_catalogctx.version.clear();
320 m_catalogctx.date.clear();
324 return g_catalog_custom_url.ToStdString();
330 Downloader downloader(std::string(API_ENDPOINT) + API_PATH);
331 bool ok = downloader.
download(stream);
333 return ServerStatus::OK;
336 return ServerStatus::CURL_ERROR;
340 nlohmann::json branches;
342 branches = nlohmann::json::parse(json);
343 }
catch (nlohmann::json::exception&) {
345 if (!branches.is_array()) {
346 wxLogMessage(
"Cannot parse json (toplevel)");
347 return ServerStatus::JSON_ERROR;
349 wxLogMessage(
"Got %d branches", branches.size());
351 for (
const auto& branch : branches) {
352 channels.push_back(branch[
"name"].get<std::string>());
354 if (branches.size() > 0) {
355 wxLogMessage(
"First branch: %s", channels[0].c_str());
357 return ServerStatus::OK;
Plugin catalog management: Build the runtime catalog, handling downloads as required.
Datatypes and methods to parse ocpn-plugins.xml XML data, either complete catalog or a single plugin.
Local proxy for the catalog server and other catalog sources.
std::vector< std::string > GetChannels()
Get the downloaded list of channels, empty on errors.
ServerStatus LoadChannels(std::ostream *json)
Download channel json data, possibly return error code.
CatalogHandler()
Initiate the handler.
CatalogData DefaultCatalogData()
Data for default version, installed with main opencpn.
std::string GetDefaultUrl()
Get the default URL, with actual channel included.
CatalogData LatestCatalogData()
Data for latest parsed data marked as latest.
CatalogCtx * GetActiveCatalogContext()
Return a pointer to the currently active plugin catalog context.
std::string LastErrorMsg()
Last error message, free format.
ServerStatus DoParseCatalog(const std::string xml, CatalogCtx *ctx)
Parse the catalog by merging data from imported metadata, meta-urls and the standard url.
CatalogData UserCatalogData()
Data for user catalog which overrides the default one.
bool AddMetadataToActiveContext(PluginMetadata metadata)
Add an abritrary stub metadata netry to the active catalog context.
ServerStatus ParseCatalog(const std::string xml, bool latest=false)
Parse XML contents, save as latest data if latest is true.
std::string GetActiveChannel()
Get the branch (a.
void SetCustomUrl(const char *url)
Set a custom url, overrides also channel settings.
ServerStatus DownloadCatalog(std::ostream *stream)
Download the latest catalog to given stream.
void ClearCatalogData()
Invalidate *CatalogData caches.
std::string GetCustomUrl()
Get the custom url set by SetCustomUrl.
ServerStatus GetCatalogStatus()
Retrieve status of currently active plugin catalog
bool SetActiveChannel(const char *channel)
Set the active channel used when downloading catalog.
Default downloader, usable in a CLI context.
bool download(std::ostream *stream)
Download url into stream, return false on errors.
std::string last_error()
Last Curl error message.
static std::vector< std::string > GetImportPaths()
List of paths for imported plugins metadata.
std::string GetMetadataPath()
Return path to metadata XML file.
static PluginHandler * GetInstance()
Singleton factory.
Global variables stored in configuration file.
Handle downloading of files from remote urls.
bool replace(std::string &str, const std::string &from, const std::string &to)
Perform in place substitution in str, replacing "from" with "to".
bool exists(const std::string &name)
Miscellaneous utilities, many of which string related.
Plugin remote repositories installation and Uninstall/list operations.
The result from parsing the xml catalog i.
Overall metadata for the set of plugins used.