OpenCPN Partial API docs
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"
36 #include "model/catalog_handler.h"
37 #include "model/catalog_parser.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
46 static const std::string SEP("\\");
47 #else
48 static const std::string SEP("/");
49 #endif
50 
51 static const char* const DOWNLOAD_REPO =
52  "https://raw.githubusercontent.com/OpenCPN/plugins";
53 
54 static const char* const DOWNLOAD_PATH = "/@branch@/ocpn-plugins.xml";
55 
56 static const char* const API_ENDPOINT = "https://api.github.com/repos";
57 // static const char* const API_PATH = "/leamas/plugins/branches";
58 static const char* const API_PATH = "/OpenCPN/plugins/branches";
59 
61  status(ServerStatus::UNKNOWN),
62  m_catalog_status(ServerStatus::UNKNOWN){
63  if (g_catalog_channel == "") {
64  g_catalog_channel = DEFAULT_CHANNEL;
65  }
66 }
67 
68 CatalogHandler* 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 
83  return m_catalog_status;
84 }
85 
87 
88  if (m_catalog_status == ServerStatus::OK){
89  return &m_catalogctx;
90  }
91 
92  auto path = PluginHandler::getInstance()->getMetadataPath();
93 
94  if (!ocpn::exists(path)) {
95  m_catalog_status = ServerStatus::FILE_ERROR;
96  }
97  std::ifstream file;
98  file.open(path, std::ios::in);
99  if (file.is_open()) {
100  std::string xml((std::istreambuf_iterator<char>(file)),
101  std::istreambuf_iterator<char>());
102  file.close();
103  auto status = DoParseCatalog(xml, &m_catalogctx);
104  m_catalog_status = status;
105  return &m_catalogctx;
106  }
107 
108  return &m_catalogctx;
109 }
110 
112  if (m_catalog_status == ServerStatus::OK) {
113  m_catalogctx.plugins.push_back(metadata);
114  return true;
115  }
116  else return false;
117 }
118 
119 
120 catalog_status CatalogHandler::DownloadCatalog(std::ostream* stream) {
121  std::string path(g_catalog_custom_url.ToStdString());
122  if (path == "") {
123  path = std::string(DOWNLOAD_REPO) + DOWNLOAD_PATH;
124  ocpn::replace(path, "@branch@", g_catalog_channel.ToStdString());
125  wxLogMessage("Effective catalog path: %s", path.c_str());
126  }
127  Downloader downloader(path);
128  bool ok = downloader.download(stream);
129  if (ok) {
130  return ServerStatus::OK;
131  }
132  error_msg = downloader.last_error();
133  return ServerStatus::CURL_ERROR;
134 }
135 
136 catalog_status CatalogHandler::DownloadCatalog(std::ostream* stream,
137  std::string url) {
138  Downloader downloader(url);
139  bool ok = downloader.download(stream);
140  if (ok) {
141  return ServerStatus::OK;
142  }
143  error_msg = downloader.last_error();
144  return ServerStatus::CURL_ERROR;
145 }
146 
147 catalog_status CatalogHandler::DownloadCatalog(std::string& filePath) {
148  if (filePath == "") {
149  filePath = wxFileName::CreateTempFileName("ocpn_dl").ToStdString();
150  }
151  std::ofstream stream;
152  stream.open(filePath.c_str(), std::ios::out | std::ios::trunc);
153  if (!stream.is_open()) {
154  wxLogMessage("CatalogHandler: Cannot open %s for write", filePath);
155  error_msg = strerror(errno);
156  return ServerStatus::OS_ERROR;
157  }
158  auto status = DownloadCatalog(&stream);
159  stream.close();
160  return status;
161 }
162 
163 catalog_status CatalogHandler::DownloadCatalog(std::string& filePath,
164  std::string url) {
165  if (filePath == "") {
166  filePath = wxFileName::CreateTempFileName("ocpn_dl").ToStdString();
167  }
168  std::ofstream stream;
169  stream.open(filePath.c_str(), std::ios::out | std::ios::trunc);
170  if (!stream.is_open()) {
171  wxLogMessage("CatalogHandler: Cannot open %s for write", filePath);
172  error_msg = strerror(errno);
173  return ServerStatus::OS_ERROR;
174  }
175  auto status = DownloadCatalog(&stream, url);
176  stream.close();
177  return status;
178 }
179 
180 catalog_status CatalogHandler::DoParseCatalog(const std::string xml,
181  CatalogCtx* ctx) {
182  std::string url;
183 
184  bool ok = ::ParseCatalog(xml, ctx);
185  for (auto path : PluginHandler::getInstance()->GetImportPaths()) {
186  std::ifstream plugin_xml(path);
187  std::stringstream ss;
188  ss << plugin_xml.rdbuf();
189  PluginMetadata metadata;
190  if (ss.str().size() == 0) {
191  continue;
192  }
193  ::ParsePlugin(ss.str().c_str(), metadata);
194  metadata.is_imported = true;
195  ctx->plugins.push_back(metadata);
196  }
197  while (ctx->meta_urls.size() > 0) {
198  std::ostringstream xml;
199  url = ctx->meta_urls.back();
200  ctx->meta_urls.pop_back();
201 
202  // already parsed this meta file?
203  auto match = [url](const std::string& s) { return url == s; };
204  const auto& haystack = ctx->parsed_metas;
205  auto found = std::find_if(haystack.begin(), haystack.end(), match);
206  if (found != haystack.end()) {
207  continue;
208  }
209  ctx->parsed_metas.push_back(url);
210  if (DownloadCatalog(&xml, url) != ServerStatus::OK) {
211  wxLogMessage("CatalogHandler: Cannot download meta-url: %s",
212  url.c_str());
213  } else {
214  ok = DoParseCatalog(xml.str(), ctx) == ServerStatus::OK;
215  if (!ok) break;
216  }
217  }
218  if (!ok) {
219  wxLogWarning("Cannot parse xml starting with: %s",
220  xml.substr(0, 60).c_str());
221  }
222  return ok ? ServerStatus::OK : ServerStatus::XML_ERROR;
223 }
224 
225 catalog_status CatalogHandler::ParseCatalog(const std::string xml,
226  bool latest) {
227  CatalogCtx ctx;
228  auto status = DoParseCatalog(xml, &ctx);
229  if (status == ServerStatus::OK && latest) {
230  this->latest_data.version = ctx.version;
231  this->latest_data.date = ctx.date;
232  this->latest_data.undef = false;
233  }
234  return status;
235 }
236 
237 std::vector<std::string> CatalogHandler::GetChannels() { return channels; }
238 
239 bool CatalogHandler::SetActiveChannel(const char* channel) {
240  for (auto c : channels) {
241  if (c == channel) {
242  GlobalVar<wxString> catalog_channel(&g_catalog_channel);
243  catalog_channel.Set(channel);
244  return true;
245  }
246  }
247  wxLogMessage("Attempt to set illegal active channel: %s", channel);
248  return false;
249 }
250 
252  return g_catalog_channel.ToStdString();
253 }
254 
255 void CatalogHandler::SetCustomUrl(const char* url) {
256  g_catalog_custom_url = url;
257 }
258 
260  if (latest_data.undef) {
261  std::ostringstream os;
262  if (DownloadCatalog(&os) == ServerStatus::OK) {
263  ParseCatalog(os.str());
264  }
265  }
266  return latest_data;
267 }
268 
269 void CatalogHandler::LoadCatalogData(const std::string& path,
270  CatalogData& data) {
271  if (!ocpn::exists(path)) {
272  data.version = "?";
273  data.date = "?";
274  data.undef = false;
275  return;
276  }
277  std::ifstream file;
278  file.open(path, std::ios::in);
279  if (file.is_open()) {
280  std::string xml((std::istreambuf_iterator<char>(file)),
281  std::istreambuf_iterator<char>());
282  file.close();
283  CatalogCtx ctx;
284  auto status = DoParseCatalog(xml, &ctx);
285  if (status == ServerStatus::OK) {
286  data.version = ctx.version;
287  data.date = ctx.date;
288  data.undef = false;
289  }
290  }
291 }
292 
294  if (user_data.undef) {
295  auto plugin_handler = PluginHandler::getInstance();
296  std::string path = g_BasePlatform->GetPrivateDataDir().ToStdString();
297  path += SEP;
298  path += "ocpn-plugins.xml";
299  LoadCatalogData(path, user_data);
300  }
301  return user_data;
302 }
303 
305  if (default_data.undef) {
306  auto plugin_handler = PluginHandler::getInstance();
307  std::string path = g_BasePlatform->GetSharedDataDir().ToStdString();
308  path += SEP;
309  path += "ocpn-plugins.xml";
310  LoadCatalogData(path, default_data);
311  }
312  return default_data;
313 }
314 
316  default_data.undef = true;
317  user_data.undef = true;
318  latest_data.undef = true;
319  m_catalog_status = ServerStatus::UNKNOWN;
320 
321  m_catalogctx.plugins.clear();
322  m_catalogctx.meta_urls.clear();
323  m_catalogctx.parsed_metas.clear();
324  m_catalogctx.version.clear();
325  m_catalogctx.date.clear();
326 
327 
328 }
329 
331  return g_catalog_custom_url.ToStdString();
332 }
333 
334 std::string CatalogHandler::LastErrorMsg() { return error_msg; }
335 
336 catalog_status CatalogHandler::LoadChannels(std::ostream* stream) {
337  Downloader downloader(std::string(API_ENDPOINT) + API_PATH);
338  bool ok = downloader.download(stream);
339  if (ok) {
340  return ServerStatus::OK;
341  }
342  error_msg = downloader.last_error();
343  return ServerStatus::CURL_ERROR;
344 }
345 
346 catalog_status CatalogHandler::LoadChannels(const std::string& json) {
347  wxJSONValue node;
348  wxJSONReader parser;
349  parser.Parse(json.c_str(), &node);
350  if (!node.IsArray()) {
351  wxLogMessage("Cannot parse json (toplevel)");
352  error_msg = parser.GetErrors().Item(0).ToStdString();
353  return ServerStatus::JSON_ERROR;
354  }
355  auto branches = node.AsArray();
356  wxLogMessage("Got %d branches", branches->Count());
357  channels.clear();
358  for (size_t i = 0; i < branches->Count(); i += 1) {
359  auto branch = branches->Item(i);
360  channels.push_back(branch["name"].AsString().ToStdString());
361  }
362  if (branches->Count() > 0) {
363  wxLogMessage("First branch: %s", channels[0].c_str());
364  }
365  return ServerStatus::OK;
366 }
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
Plugin catalog management: Check for available versions and branches, download as required.
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.
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()
Set a custom url, overrides also channel settings.
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.
Definition: downloader.cpp:55
std::string last_error()
Last Curl error message.
Definition: downloader.cpp:49
Wrapper for global variable, supports notification events when value changes.
std::string getMetadataPath()
Return path to metadata XML file.
The result from parsing the xml catalog i.
Datatypes and methods to parse ocpn-plugins.xml XML data, either complete catalog or a single plugin.
Plugin metadata, reflects the xml format directly.