31#include <wx/filename.h>
32#include <wx/jsonreader.h>
35#include "model/base_platform.h"
38#include "model/config_vars.h"
39#include "model/downloader.h"
40#include "model/ocpn_utils.h"
41#include "model/plugin_handler.h"
42#include "observable_evtvar.h"
43#include "observable_globvar.h"
46static const std::string SEP(
"\\");
48static const std::string SEP(
"/");
51static const char*
const DOWNLOAD_REPO =
52 "https://raw.githubusercontent.com/OpenCPN/plugins";
54static const char*
const DOWNLOAD_PATH =
"/@branch@/ocpn-plugins.xml";
56static const char*
const API_ENDPOINT =
"https://api.github.com/repos";
58static const char*
const API_PATH =
"/OpenCPN/plugins/branches";
61 : status(ServerStatus::UNKNOWN), m_catalog_status(ServerStatus::UNKNOWN) {
62 if (g_catalog_channel ==
"") {
63 g_catalog_channel = DEFAULT_CHANNEL;
76 std::string url = std::string(DOWNLOAD_REPO) + DOWNLOAD_PATH;
77 ocpn::replace(url,
"@branch@", g_catalog_channel.ToStdString());
84 if (m_catalog_status == ServerStatus::OK) {
90 if (!ocpn::exists(path)) {
91 m_catalog_status = ServerStatus::FILE_ERROR;
94 file.open(path, std::ios::in);
96 std::string xml((std::istreambuf_iterator<char>(file)),
97 std::istreambuf_iterator<char>());
100 m_catalog_status = status;
101 return &m_catalogctx;
104 return &m_catalogctx;
108 if (m_catalog_status == ServerStatus::OK) {
109 m_catalogctx.plugins.push_back(metadata);
116 std::string path(g_catalog_custom_url.ToStdString());
118 path = std::string(DOWNLOAD_REPO) + DOWNLOAD_PATH;
119 ocpn::replace(path,
"@branch@", g_catalog_channel.ToStdString());
120 wxLogMessage(
"Effective catalog path: %s", path.c_str());
123 bool ok = downloader.
download(stream);
125 return ServerStatus::OK;
128 return ServerStatus::CURL_ERROR;
134 bool ok = downloader.
download(stream);
136 return ServerStatus::OK;
139 return ServerStatus::CURL_ERROR;
143 if (filePath ==
"") {
144 filePath = wxFileName::CreateTempFileName(
"ocpn_dl").ToStdString();
146 std::ofstream stream;
147 stream.open(filePath.c_str(), std::ios::out | std::ios::trunc);
148 if (!stream.is_open()) {
149 wxLogMessage(
"CatalogHandler: Cannot open %s for write", filePath);
150 error_msg = strerror(errno);
151 return ServerStatus::OS_ERROR;
160 if (filePath ==
"") {
161 filePath = wxFileName::CreateTempFileName(
"ocpn_dl").ToStdString();
163 std::ofstream stream;
164 stream.open(filePath.c_str(), std::ios::out | std::ios::trunc);
165 if (!stream.is_open()) {
166 wxLogMessage(
"CatalogHandler: Cannot open %s for write", filePath);
167 error_msg = strerror(errno);
168 return ServerStatus::OS_ERROR;
180 for (
auto path : PluginHandler::getInstance()->
GetImportPaths()) {
181 std::ifstream plugin_xml(path);
182 std::stringstream ss;
183 ss << plugin_xml.rdbuf();
185 if (ss.str().size() == 0) {
188 ::ParsePlugin(ss.str().c_str(), metadata);
189 metadata.is_imported =
true;
190 ctx->plugins.push_back(metadata);
192 while (ctx->meta_urls.size() > 0) {
193 std::ostringstream xml;
194 url = ctx->meta_urls.back();
195 ctx->meta_urls.pop_back();
198 auto match = [url](
const std::string& s) {
return url == s; };
199 const auto& haystack = ctx->parsed_metas;
200 auto found = std::find_if(haystack.begin(), haystack.end(), match);
201 if (found != haystack.end()) {
204 ctx->parsed_metas.push_back(url);
206 wxLogMessage(
"CatalogHandler: Cannot download meta-url: %s", url.c_str());
213 wxLogWarning(
"Cannot parse xml starting with: %s",
214 xml.substr(0, 60).c_str());
216 return ok ? ServerStatus::OK : ServerStatus::XML_ERROR;
223 if (status == ServerStatus::OK && latest) {
224 this->latest_data.version = ctx.version;
225 this->latest_data.date = ctx.date;
226 this->latest_data.undef =
false;
234 for (
auto c : channels) {
237 catalog_channel.Set(channel);
241 wxLogMessage(
"Attempt to set illegal active channel: %s", channel);
246 return g_catalog_channel.ToStdString();
250 g_catalog_custom_url = url;
254 if (latest_data.undef) {
255 std::ostringstream os;
263void CatalogHandler::LoadCatalogData(
const std::string& path,
265 if (!ocpn::exists(path)) {
272 file.open(path, std::ios::in);
273 if (file.is_open()) {
274 std::string xml((std::istreambuf_iterator<char>(file)),
275 std::istreambuf_iterator<char>());
279 if (status == ServerStatus::OK) {
280 data.version = ctx.version;
281 data.date = ctx.date;
288 if (user_data.undef) {
289 auto plugin_handler = PluginHandler::getInstance();
292 path +=
"ocpn-plugins.xml";
293 LoadCatalogData(path, user_data);
299 if (default_data.undef) {
300 auto plugin_handler = PluginHandler::getInstance();
301 std::string path = g_BasePlatform->GetSharedDataDir().ToStdString();
303 path +=
"ocpn-plugins.xml";
304 LoadCatalogData(path, default_data);
310 default_data.undef =
true;
311 user_data.undef =
true;
312 latest_data.undef =
true;
313 m_catalog_status = ServerStatus::UNKNOWN;
315 m_catalogctx.plugins.clear();
316 m_catalogctx.meta_urls.clear();
317 m_catalogctx.parsed_metas.clear();
318 m_catalogctx.version.clear();
319 m_catalogctx.date.clear();
323 return g_catalog_custom_url.ToStdString();
329 Downloader downloader(std::string(API_ENDPOINT) + API_PATH);
330 bool ok = downloader.
download(stream);
332 return ServerStatus::OK;
335 return ServerStatus::CURL_ERROR;
341 parser.
Parse(json.c_str(), &node);
343 wxLogMessage(
"Cannot parse json (toplevel)");
344 error_msg = parser.
GetErrors().Item(0).ToStdString();
345 return ServerStatus::JSON_ERROR;
347 auto branches = node.
AsArray();
348 wxLogMessage(
"Got %d branches", branches->Count());
350 for (
size_t i = 0; i < branches->Count(); i += 1) {
351 auto branch = branches->Item(i);
352 channels.push_back(branch[
"name"].AsString().ToStdString());
354 if (branches->Count() > 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.
Handle downloading of files from remote urls.
bool download(std::ostream *stream)
Download url into stream, return false on errors.
std::string last_error()
Last Curl error message.
Wrapper for global variable, supports notification events when value changes.
static std::vector< std::string > GetImportPaths()
List of paths for imported plugins metadata.
std::string getMetadataPath()
Return path to metadata XML file.
const wxArrayString & GetErrors() const
Return a reference to the error message's array.
int Parse(const wxString &doc, wxJSONValue *val)
Parse the JSON document.
The JSON value class implementation.
bool IsArray() const
Return TRUE if the type of the value stored is an array type.
const wxJSONInternalArray * AsArray() const
Return the stored value as an array object.
The result from parsing the xml catalog i.
Overall metadata for the set of plugins used.