OpenCPN Partial API docs
Loading...
Searching...
No Matches
catalog_handler.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2019 Alec Leamas *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
25#include "config.h"
26
27#include <algorithm>
28#include <fstream>
29#include <sstream>
30
31#include <wx/filename.h>
32#include <wx/log.h>
33
34#include "nlohmann/json.hpp"
35#include "observable/evtvar.h"
36#include "observable/global_var.h"
37
38#include "model/base_platform.h"
41#include "model/config_vars.h"
42#include "model/downloader.h"
43#include "model/ocpn_utils.h"
45
46#ifdef _WIN32
47static const std::string SEP("\\");
48#else
49static const std::string SEP("/");
50#endif
51
52static const char* const DOWNLOAD_REPO =
53 "https://raw.githubusercontent.com/OpenCPN/plugins";
54
55static const char* const DOWNLOAD_PATH = "/@branch@/ocpn-plugins.xml";
56
57static const char* const API_ENDPOINT = "https://api.github.com/repos";
58// static const char* const API_PATH = "/leamas/plugins/branches";
59static const char* const API_PATH = "/OpenCPN/plugins/branches";
60
62 : status(ServerStatus::UNKNOWN), m_catalog_status(ServerStatus::UNKNOWN) {
63 if (g_catalog_channel == "") {
64 g_catalog_channel = DEFAULT_CHANNEL;
65 }
66}
67
68CatalogHandler* CatalogHandler::GetInstance() {
69 static CatalogHandler* instance = 0;
70 if (!instance) {
71 instance = new (CatalogHandler);
72 }
73 return instance;
74}
75
77 std::string url = std::string(DOWNLOAD_REPO) + DOWNLOAD_PATH;
78 ocpn::replace(url, "@branch@", g_catalog_channel.ToStdString());
79 return url;
80}
81
82catalog_status CatalogHandler::GetCatalogStatus() { return m_catalog_status; }
83
85 if (m_catalog_status == ServerStatus::OK) {
86 return &m_catalogctx;
87 }
88
90
91 if (!ocpn::exists(path)) {
92 m_catalog_status = ServerStatus::FILE_ERROR;
93 }
94 std::ifstream file;
95 file.open(path, std::ios::in);
96 if (file.is_open()) {
97 std::string xml((std::istreambuf_iterator<char>(file)),
98 std::istreambuf_iterator<char>());
99 file.close();
100 auto status = DoParseCatalog(xml, &m_catalogctx);
101 m_catalog_status = status;
102 return &m_catalogctx;
103 }
104
105 return &m_catalogctx;
106}
107
109 if (m_catalog_status == ServerStatus::OK) {
110 m_catalogctx.plugins.push_back(metadata);
111 return true;
112 } else
113 return false;
114}
115
116catalog_status CatalogHandler::DownloadCatalog(std::ostream* stream) {
117 std::string path(g_catalog_custom_url.ToStdString());
118 if (path == "") {
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());
122 }
123 Downloader downloader(path);
124 bool ok = downloader.download(stream);
125 if (ok) {
126 return ServerStatus::OK;
127 }
128 error_msg = downloader.last_error();
129 return ServerStatus::CURL_ERROR;
130}
131
132catalog_status CatalogHandler::DownloadCatalog(std::ostream* stream,
133 std::string url) {
134 Downloader downloader(url);
135 bool ok = downloader.download(stream);
136 if (ok) {
137 return ServerStatus::OK;
138 }
139 error_msg = downloader.last_error();
140 return ServerStatus::CURL_ERROR;
141}
142
143catalog_status CatalogHandler::DownloadCatalog(std::string& filePath) {
144 if (filePath == "") {
145 filePath = wxFileName::CreateTempFileName("ocpn_dl").ToStdString();
146 }
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;
153 }
154 auto status = DownloadCatalog(&stream);
155 stream.close();
156 return status;
157}
158
159catalog_status CatalogHandler::DownloadCatalog(std::string& filePath,
160 std::string url) {
161 if (filePath == "") {
162 filePath = wxFileName::CreateTempFileName("ocpn_dl").ToStdString();
163 }
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;
170 }
171 auto status = DownloadCatalog(&stream, url);
172 stream.close();
173 return status;
174}
175
176catalog_status CatalogHandler::DoParseCatalog(const std::string xml,
177 CatalogCtx* ctx) {
178 std::string url;
179
180 bool ok = ::ParseCatalog(xml, ctx);
181 for (auto path : PluginHandler::GetInstance()->GetImportPaths()) {
182 std::ifstream plugin_xml(path);
183 std::stringstream ss;
184 ss << plugin_xml.rdbuf();
185 PluginMetadata metadata;
186 if (ss.str().size() == 0) {
187 continue;
188 }
189 ::ParsePlugin(ss.str().c_str(), metadata);
190 metadata.is_imported = true;
191 ctx->plugins.push_back(metadata);
192 }
193 while (ctx->meta_urls.size() > 0) {
194 std::ostringstream xml;
195 url = ctx->meta_urls.back();
196 ctx->meta_urls.pop_back();
197
198 // already parsed this meta file?
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()) {
203 continue;
204 }
205 ctx->parsed_metas.push_back(url);
206 if (DownloadCatalog(&xml, url) != ServerStatus::OK) {
207 wxLogMessage("CatalogHandler: Cannot download meta-url: %s", url.c_str());
208 } else {
209 ok = DoParseCatalog(xml.str(), ctx) == ServerStatus::OK;
210 if (!ok) break;
211 }
212 }
213 if (!ok) {
214 wxLogWarning("Cannot parse xml starting with: %s",
215 xml.substr(0, 60).c_str());
216 }
217 return ok ? ServerStatus::OK : ServerStatus::XML_ERROR;
218}
219
220catalog_status CatalogHandler::ParseCatalog(const std::string xml,
221 bool latest) {
222 CatalogCtx ctx;
223 auto status = DoParseCatalog(xml, &ctx);
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;
228 }
229 return status;
230}
231
232std::vector<std::string> CatalogHandler::GetChannels() { return channels; }
233
234bool CatalogHandler::SetActiveChannel(const char* channel) {
235 for (auto c : channels) {
236 if (c == channel) {
237 obs::GlobalVar<wxString> catalog_channel(&g_catalog_channel);
238 catalog_channel.Set(channel);
239 return true;
240 }
241 }
242 wxLogMessage("Attempt to set illegal active channel: %s", channel);
243 return false;
244}
245
247 return g_catalog_channel.ToStdString();
248}
249
250void CatalogHandler::SetCustomUrl(const char* url) {
251 g_catalog_custom_url = url;
252}
253
255 if (latest_data.undef) {
256 std::ostringstream os;
257 if (DownloadCatalog(&os) == ServerStatus::OK) {
258 ParseCatalog(os.str());
259 }
260 }
261 return latest_data;
262}
263
264void CatalogHandler::LoadCatalogData(const std::string& path,
265 CatalogData& data) {
266 if (!ocpn::exists(path)) {
267 data.version = "?";
268 data.date = "?";
269 data.undef = false;
270 return;
271 }
272 std::ifstream file;
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>());
277 file.close();
278 CatalogCtx ctx;
279 auto status = DoParseCatalog(xml, &ctx);
280 if (status == ServerStatus::OK) {
281 data.version = ctx.version;
282 data.date = ctx.date;
283 data.undef = false;
284 }
285 }
286}
287
289 if (user_data.undef) {
290 auto plugin_handler = PluginHandler::GetInstance();
291 std::string path = g_BasePlatform->GetPrivateDataDir().ToStdString();
292 path += SEP;
293 path += "ocpn-plugins.xml";
294 LoadCatalogData(path, user_data);
295 }
296 return user_data;
297}
298
300 if (default_data.undef) {
301 auto plugin_handler = PluginHandler::GetInstance();
302 std::string path = g_BasePlatform->GetSharedDataDir().ToStdString();
303 path += SEP;
304 path += "ocpn-plugins.xml";
305 LoadCatalogData(path, default_data);
306 }
307 return default_data;
308}
309
311 default_data.undef = true;
312 user_data.undef = true;
313 latest_data.undef = true;
314 m_catalog_status = ServerStatus::UNKNOWN;
315
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();
321}
322
324 return g_catalog_custom_url.ToStdString();
325}
326
327std::string CatalogHandler::LastErrorMsg() { return error_msg; }
328
329catalog_status CatalogHandler::LoadChannels(std::ostream* stream) {
330 Downloader downloader(std::string(API_ENDPOINT) + API_PATH);
331 bool ok = downloader.download(stream);
332 if (ok) {
333 return ServerStatus::OK;
334 }
335 error_msg = downloader.last_error();
336 return ServerStatus::CURL_ERROR;
337}
338
339catalog_status CatalogHandler::LoadChannels(const std::string& json) {
340 nlohmann::json branches;
341 try {
342 branches = nlohmann::json::parse(json);
343 } catch (nlohmann::json::exception&) {
344 }
345 if (!branches.is_array()) {
346 wxLogMessage("Cannot parse json (toplevel)");
347 return ServerStatus::JSON_ERROR;
348 }
349 wxLogMessage("Got %d branches", branches.size());
350 channels.clear();
351 for (const auto& branch : branches) {
352 channels.push_back(branch["name"].get<std::string>());
353 }
354 if (branches.size() > 0) {
355 wxLogMessage("First branch: %s", channels[0].c_str());
356 }
357 return ServerStatus::OK;
358}
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Basic platform specific support utilities without GUI deps.
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.
Default downloader, usable in a CLI context.
Definition downloader.h:35
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.
Plugin metadata, reflects the xml format directly.