OpenCPN Partial API docs
Loading...
Searching...
No Matches
catalog_handler.cpp
1/******************************************************************************
2 *
3 * Project: OpenCPN
4 *
5 ***************************************************************************
6 * Copyright (C) 2019 Alec Leamas *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
22 ***************************************************************************
23 */
24
25#include "config.h"
26
27#include <algorithm>
28#include <fstream>
29#include <sstream>
30
31#include <wx/filename.h>
32#include <wx/jsonreader.h>
33#include <wx/log.h>
34
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"
44
45#ifdef _WIN32
46static const std::string SEP("\\");
47#else
48static const std::string SEP("/");
49#endif
50
51static const char* const DOWNLOAD_REPO =
52 "https://raw.githubusercontent.com/OpenCPN/plugins";
53
54static const char* const DOWNLOAD_PATH = "/@branch@/ocpn-plugins.xml";
55
56static const char* const API_ENDPOINT = "https://api.github.com/repos";
57// static const char* const API_PATH = "/leamas/plugins/branches";
58static const char* const API_PATH = "/OpenCPN/plugins/branches";
59
61 : status(ServerStatus::UNKNOWN), m_catalog_status(ServerStatus::UNKNOWN) {
62 if (g_catalog_channel == "") {
63 g_catalog_channel = DEFAULT_CHANNEL;
64 }
65}
66
67CatalogHandler* CatalogHandler::getInstance() {
68 static CatalogHandler* instance = 0;
69 if (!instance) {
70 instance = new (CatalogHandler);
71 }
72 return instance;
73}
74
76 std::string url = std::string(DOWNLOAD_REPO) + DOWNLOAD_PATH;
77 ocpn::replace(url, "@branch@", g_catalog_channel.ToStdString());
78 return url;
79}
80
81catalog_status CatalogHandler::GetCatalogStatus() { return m_catalog_status; }
82
84 if (m_catalog_status == ServerStatus::OK) {
85 return &m_catalogctx;
86 }
87
88 auto path = PluginHandler::getInstance()->getMetadataPath();
89
90 if (!ocpn::exists(path)) {
91 m_catalog_status = ServerStatus::FILE_ERROR;
92 }
93 std::ifstream file;
94 file.open(path, std::ios::in);
95 if (file.is_open()) {
96 std::string xml((std::istreambuf_iterator<char>(file)),
97 std::istreambuf_iterator<char>());
98 file.close();
99 auto status = DoParseCatalog(xml, &m_catalogctx);
100 m_catalog_status = status;
101 return &m_catalogctx;
102 }
103
104 return &m_catalogctx;
105}
106
108 if (m_catalog_status == ServerStatus::OK) {
109 m_catalogctx.plugins.push_back(metadata);
110 return true;
111 } else
112 return false;
113}
114
115catalog_status CatalogHandler::DownloadCatalog(std::ostream* stream) {
116 std::string path(g_catalog_custom_url.ToStdString());
117 if (path == "") {
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());
121 }
122 Downloader downloader(path);
123 bool ok = downloader.download(stream);
124 if (ok) {
125 return ServerStatus::OK;
126 }
127 error_msg = downloader.last_error();
128 return ServerStatus::CURL_ERROR;
129}
130
131catalog_status CatalogHandler::DownloadCatalog(std::ostream* stream,
132 std::string url) {
133 Downloader downloader(url);
134 bool ok = downloader.download(stream);
135 if (ok) {
136 return ServerStatus::OK;
137 }
138 error_msg = downloader.last_error();
139 return ServerStatus::CURL_ERROR;
140}
141
142catalog_status CatalogHandler::DownloadCatalog(std::string& filePath) {
143 if (filePath == "") {
144 filePath = wxFileName::CreateTempFileName("ocpn_dl").ToStdString();
145 }
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;
152 }
153 auto status = DownloadCatalog(&stream);
154 stream.close();
155 return status;
156}
157
158catalog_status CatalogHandler::DownloadCatalog(std::string& filePath,
159 std::string url) {
160 if (filePath == "") {
161 filePath = wxFileName::CreateTempFileName("ocpn_dl").ToStdString();
162 }
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;
169 }
170 auto status = DownloadCatalog(&stream, url);
171 stream.close();
172 return status;
173}
174
175catalog_status CatalogHandler::DoParseCatalog(const std::string xml,
176 CatalogCtx* ctx) {
177 std::string url;
178
179 bool ok = ::ParseCatalog(xml, ctx);
180 for (auto path : PluginHandler::getInstance()->GetImportPaths()) {
181 std::ifstream plugin_xml(path);
182 std::stringstream ss;
183 ss << plugin_xml.rdbuf();
184 PluginMetadata metadata;
185 if (ss.str().size() == 0) {
186 continue;
187 }
188 ::ParsePlugin(ss.str().c_str(), metadata);
189 metadata.is_imported = true;
190 ctx->plugins.push_back(metadata);
191 }
192 while (ctx->meta_urls.size() > 0) {
193 std::ostringstream xml;
194 url = ctx->meta_urls.back();
195 ctx->meta_urls.pop_back();
196
197 // already parsed this meta file?
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()) {
202 continue;
203 }
204 ctx->parsed_metas.push_back(url);
205 if (DownloadCatalog(&xml, url) != ServerStatus::OK) {
206 wxLogMessage("CatalogHandler: Cannot download meta-url: %s", url.c_str());
207 } else {
208 ok = DoParseCatalog(xml.str(), ctx) == ServerStatus::OK;
209 if (!ok) break;
210 }
211 }
212 if (!ok) {
213 wxLogWarning("Cannot parse xml starting with: %s",
214 xml.substr(0, 60).c_str());
215 }
216 return ok ? ServerStatus::OK : ServerStatus::XML_ERROR;
217}
218
219catalog_status CatalogHandler::ParseCatalog(const std::string xml,
220 bool latest) {
221 CatalogCtx ctx;
222 auto status = DoParseCatalog(xml, &ctx);
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;
227 }
228 return status;
229}
230
231std::vector<std::string> CatalogHandler::GetChannels() { return channels; }
232
233bool CatalogHandler::SetActiveChannel(const char* channel) {
234 for (auto c : channels) {
235 if (c == channel) {
236 GlobalVar<wxString> catalog_channel(&g_catalog_channel);
237 catalog_channel.Set(channel);
238 return true;
239 }
240 }
241 wxLogMessage("Attempt to set illegal active channel: %s", channel);
242 return false;
243}
244
246 return g_catalog_channel.ToStdString();
247}
248
249void CatalogHandler::SetCustomUrl(const char* url) {
250 g_catalog_custom_url = url;
251}
252
254 if (latest_data.undef) {
255 std::ostringstream os;
256 if (DownloadCatalog(&os) == ServerStatus::OK) {
257 ParseCatalog(os.str());
258 }
259 }
260 return latest_data;
261}
262
263void CatalogHandler::LoadCatalogData(const std::string& path,
264 CatalogData& data) {
265 if (!ocpn::exists(path)) {
266 data.version = "?";
267 data.date = "?";
268 data.undef = false;
269 return;
270 }
271 std::ifstream file;
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>());
276 file.close();
277 CatalogCtx ctx;
278 auto status = DoParseCatalog(xml, &ctx);
279 if (status == ServerStatus::OK) {
280 data.version = ctx.version;
281 data.date = ctx.date;
282 data.undef = false;
283 }
284 }
285}
286
288 if (user_data.undef) {
289 auto plugin_handler = PluginHandler::getInstance();
290 std::string path = g_BasePlatform->GetPrivateDataDir().ToStdString();
291 path += SEP;
292 path += "ocpn-plugins.xml";
293 LoadCatalogData(path, user_data);
294 }
295 return user_data;
296}
297
299 if (default_data.undef) {
300 auto plugin_handler = PluginHandler::getInstance();
301 std::string path = g_BasePlatform->GetSharedDataDir().ToStdString();
302 path += SEP;
303 path += "ocpn-plugins.xml";
304 LoadCatalogData(path, default_data);
305 }
306 return default_data;
307}
308
310 default_data.undef = true;
311 user_data.undef = true;
312 latest_data.undef = true;
313 m_catalog_status = ServerStatus::UNKNOWN;
314
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();
320}
321
323 return g_catalog_custom_url.ToStdString();
324}
325
326std::string CatalogHandler::LastErrorMsg() { return error_msg; }
327
328catalog_status CatalogHandler::LoadChannels(std::ostream* stream) {
329 Downloader downloader(std::string(API_ENDPOINT) + API_PATH);
330 bool ok = downloader.download(stream);
331 if (ok) {
332 return ServerStatus::OK;
333 }
334 error_msg = downloader.last_error();
335 return ServerStatus::CURL_ERROR;
336}
337
338catalog_status CatalogHandler::LoadChannels(const std::string& json) {
339 wxJSONValue node;
340 wxJSONReader parser;
341 parser.Parse(json.c_str(), &node);
342 if (!node.IsArray()) {
343 wxLogMessage("Cannot parse json (toplevel)");
344 error_msg = parser.GetErrors().Item(0).ToStdString();
345 return ServerStatus::JSON_ERROR;
346 }
347 auto branches = node.AsArray();
348 wxLogMessage("Got %d branches", branches->Count());
349 channels.clear();
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());
353 }
354 if (branches->Count() > 0) {
355 wxLogMessage("First branch: %s", channels[0].c_str());
356 }
357 return ServerStatus::OK;
358}
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.
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
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.
Definition downloader.h:34
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.
The JSON parser.
Definition jsonreader.h:50
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.
Definition jsonval.h:84
bool IsArray() const
Return TRUE if the type of the value stored is an array type.
Definition jsonval.cpp:746
const wxJSONInternalArray * AsArray() const
Return the stored value as an array object.
Definition jsonval.cpp:1281
The result from parsing the xml catalog i.
Overall metadata for the set of plugins used.
Plugin metadata, reflects the xml format directly.