OpenCPN Partial API docs
plugin_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 #include <algorithm>
25 #include <cstdio>
26 #include <fstream>
27 #include <iomanip>
28 #include <memory>
29 #include <ostream>
30 #include <regex>
31 #include <set>
32 #include <sstream>
33 #include <stdexcept>
34 #include <streambuf>
35 #include <unordered_map>
36 
37 #include "wx/wxprec.h"
38 
39 #if (defined(OCPN_GHC_FILESYSTEM) || (defined(__clang_major__) && (__clang_major__ < 15)))
40 // MacOS 1.13
41 #include <ghc/filesystem.hpp>
42 namespace fs = ghc::filesystem;
43 #else
44 #include <filesystem>
45 #include <utility>
46 namespace fs = std::filesystem;
47 #endif
48 
49 
50 #ifndef WX_PRECOMP
51 #include "wx/wx.h"
52 #endif
53 
54 #include <wx/dir.h>
55 #include <wx/file.h>
56 #include <wx/filename.h>
57 #include <wx/string.h>
58 #include <wx/tokenzr.h>
59 #include <wx/window.h>
60 #include <wx/uri.h>
61 
62 #include <archive.h>
63 #include <archive_entry.h>
64 typedef __LA_INT64_T la_int64_t; // "older" libarchive versions support
65 
66 #if defined(__MINGW32__) && defined(Yield)
67 #undef Yield // from win.h, conflicts with mingw headers
68 #endif
69 
70 #include "config.h"
71 
72 #include "model/base_platform.h"
73 #include "model/catalog_handler.h"
74 #include "model/catalog_parser.h"
75 #include "model/config_vars.h"
76 #include "model/cmdline.h"
77 #include "model/downloader.h"
78 #include "model/logger.h"
79 #include "model/ocpn_utils.h"
80 #include "model/plugin_cache.h"
81 #include "model/plugin_handler.h"
82 #include "model/plugin_loader.h"
83 #include "model/plugin_paths.h"
84 
85 #ifdef _WIN32
86 static std::string SEP("\\");
87 #else
88 static std::string SEP("/");
89 #endif
90 
91 #ifndef F_OK // windows: missing unistd.h.
92 #define F_OK 0
93 #endif
94 
96 static std::vector<std::string> split(const std::string& s,
97  const std::string& delim) {
98  std::vector<std::string> result;
99  size_t pos = s.find(delim);
100  if (pos == std::string::npos) {
101  result.push_back(s);
102  return result;
103  }
104  result.push_back(s.substr(0, pos));
105  result.push_back(s.substr(pos + delim.length()));
106  return result;
107 }
108 
109 inline std::string basename(const std::string path) {
110  wxFileName wxFile(path);
111  return wxFile.GetFullName().ToStdString();
112 }
113 
114 bool isRegularFile(const char* path) {
115  wxFileName wxFile(path);
116  return wxFile.FileExists() && !wxFile.IsDir();
117 }
118 
119 static void mkdir(const std::string path) {
120 #if defined(_WIN32) && !defined(__MINGW32__)
121  _mkdir(path.c_str());
122 #elif defined(__MINGW32__)
123  mkdir(path.c_str());
124 #else
125  mkdir(path.c_str(), 0755);
126 #endif
127 }
128 
129 static std::vector<std::string> glob_dir(const std::string& dir_path,
130  const std::string& pattern) {
131  std::vector<std::string> found;
132  wxString s;
133  wxDir dir(dir_path);
134  auto match = dir.GetFirst(&s, pattern);
135  while (match) {
136  static const std::string SEP =
137  wxString(wxFileName::GetPathSeparator()).ToStdString();
138  found.push_back(dir_path + SEP + s.ToStdString());
139  match = dir.GetNext(&s);
140  }
141  return found;
142 }
143 
148 static ssize_t PlugInIxByName(const std::string& name,
149  const ArrayOfPlugIns* plugins) {
150  const auto lc_name = ocpn::tolower(name);
151  for (unsigned i = 0; i < plugins->GetCount(); i += 1) {
152  if (lc_name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
153  return i;
154  }
155  }
156  return -1;
157 }
158 
159 static std::string pluginsConfigDir() {
160  auto pluginDataDir = g_BasePlatform->DefaultPrivateDataDir().ToStdString();
161  pluginDataDir += SEP + "plugins";
162  if (!ocpn::exists(pluginDataDir)) {
163  mkdir(pluginDataDir);
164  }
165  pluginDataDir += SEP + "install_data";
166  if (!ocpn::exists(pluginDataDir)) {
167  mkdir(pluginDataDir);
168  }
169  return pluginDataDir;
170 }
171 
172 static std::string importsDir() {
173  auto path = pluginsConfigDir();
174  path = path + SEP + "imports";
175  if (!ocpn::exists(path)) {
176  mkdir(path);
177  }
178  return path;
179 }
180 
181 static std::string dirListPath(std::string name) {
182  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
183  return pluginsConfigDir() + SEP + name + ".dirs";
184 }
185 
187  return pluginsConfigDir();
188 }
189 
190 static std::vector<std::string> LoadLinesFromFile(const std::string& path) {
191  std::vector<std::string> lines;
192  std::ifstream src(path);
193  while (!src.eof()) {
194  char line[256];
195  src.getline(line, sizeof(line));
196  lines.push_back(line);
197  }
198  return lines;
199 }
200 
202 class Plugin {
203 public:
204  Plugin(const PluginMetadata& metadata) {
205  m_abi = metadata.target;
206  m_abi_version = metadata.target_version;
207  m_major_version = ocpn::split(m_abi_version.c_str(), ".")[0];
208  m_name = metadata.name;
209  wxLogDebug("Plugin: setting up, name: %s", m_name);
210  wxLogDebug("Plugin: init: abi: %s, abi_version: %s, major ver: %s", m_abi,
211  m_abi_version, m_major_version);
212  }
213  const std::string& abi() const { return m_abi; }
214  const std::string& abi_version() const { return m_abi_version; }
215  const std::string& major_version() const { return m_major_version; }
216  const std::string& name() const { return m_name; }
217 
218 private:
219  std::string m_abi;
220  std::string m_abi_version;
221  std::string m_major_version;
222  std::string m_name;
223 };
224 
226 class Host {
227 public:
228  Host(CompatOs* compatOs) {
229  m_abi = compatOs->name();
230  m_abi_version = compatOs->version();
231  m_major_version = ocpn::split(m_abi_version.c_str(), ".")[0];
232  wxLogDebug("Host: init: abi: %s, abi_version: %s, major ver: %s", m_abi,
233  m_abi_version, m_major_version);
234  }
235 
236  bool is_version_compatible(const Plugin& plugin) const {
237  if (ocpn::startswith(plugin.abi(), "ubuntu")) {
238  return plugin.abi_version() == m_abi_version;
239  }
240  return plugin.major_version() == m_major_version;
241  }
242 
243  // Test if plugin abi is a Debian version compatible with host's Ubuntu
244  // abi version on a x86_64 platform.
245  bool is_debian_plugin_compatible(const Plugin& plugin) const {
246  if (!ocpn::startswith(m_abi, "ubuntu")) return false;
247  static const std::vector<std::string> compat_versions = {
248  // clang-format: off
249  "debian-x86_64;11;ubuntu-gtk3-x86_64;20.04",
250  "debian-wx32-x86_64;11;ubuntu-wx32-x86_64;22.04",
251  "debian-x86_64;12;ubuntu-x86_64;23.04",
252  "debian-x86_64;12;ubuntu-x86_64;23.10",
253  "debian-x86_64;12;ubuntu-x86_64;24.04",
254  "debian-x86_64;sid;ubuntu-x86_64;24.04",
255 
256  "debian-arm64;11;ubuntu-gtk3-arm64;20.04",
257  "debian-wx32-arm64;11;ubuntu-wx32-arm64;22.04",
258  "debian-arm64;12;ubuntu-arm64;23.04",
259  "debian-arm64;12;ubuntu-arm64;23.10",
260  "debian-arm64;12;ubuntu-arm64;24.04",
261  "debian-arm64;sid;ubuntu-arm64;24.04",
262 
263  "debian-armhf;10;ubuntu-armhf;18.04",
264  "debian-gtk3-armhf;10;ubuntu-gtk3-armhf;18.04",
265  "debian-armhf;11;ubuntu-gtk3-armhf;20.04",
266  "debian-wx32-armhf;11;ubuntu-wx32-armhf;22.04",
267  "debian-armhf;12;ubuntu-armhf;23.04",
268  "debian-armhf;12;ubuntu-armhf;23.10",
269  "debian-armhf;12;ubuntu-armhf;24.04",
270  "debian-armhf;sid;ubuntu-armhf;24.04"}; // clang-format: on
271 
272  if (ocpn::startswith(plugin.abi(), "debian")) {
273  wxLogDebug("Checking for debian plugin on a ubuntu host");
274  const std::string compat_version = plugin.abi() + ";" +
275  plugin.major_version() + ";" + m_abi +
276  ";" + m_abi_version;
277  for (auto& cv : compat_versions) {
278  if (compat_version == cv) {
279  return true;
280  }
281  }
282  }
283  return false;
284  }
285 
286  const std::string& abi() const { return m_abi; }
287 
288  const std::string& abi_version() const { return m_abi_version; }
289 
290  const std::string& major_version() const { return m_major_version; }
291 
292 private:
293  std::string m_abi;
294  std::string m_abi_version;
295  std::string m_major_version;
296 };
297 
298 CompatOs* CompatOs::getInstance() {
299  static std::string last_global_os("");
300  static CompatOs* instance = 0;
301 
302  if (!instance || last_global_os != g_compatOS) {
303  instance = new (CompatOs);
304  last_global_os = g_compatOS;
305  }
306  return instance;
307 };
308 
309 CompatOs::CompatOs() : _name(PKG_TARGET), _version(PKG_TARGET_VERSION) {
310  // Get the specified system definition,
311  // from the environment override,
312  // or the config file override
313  // or the baked in (build system) values.
314 
315  std::string compatOS(_name);
316  std::string compatOsVersion(_version);
317 
318  if (getenv("OPENCPN_COMPAT_TARGET") != 0) {
319  _name = getenv("OPENCPN_COMPAT_TARGET");
320  if (_name.find(':') != std::string::npos) {
321  auto tokens = ocpn::split(_name.c_str(), ":");
322  _name = tokens[0];
323  _version = tokens[1];
324  }
325  } else if (g_compatOS != "") {
326  // CompatOS and CompatOsVersion in opencpn.conf/.ini file.
327  _name = g_compatOS;
328  if (g_compatOsVersion != "") {
329  _version = g_compatOsVersion;
330  }
331  } else if (ocpn::startswith(_name, "ubuntu") && (_version == "22.04")) {
332  int wxv = wxMAJOR_VERSION * 10 + wxMINOR_VERSION;
333  if (wxv >= 32) {
334  auto tokens = ocpn::split(_name.c_str(), "-");
335  _name = std::string(tokens[0]) + std::string("-wx32");
336  if (tokens.size() > 1) _name = _name + std::string("-") + tokens[1];
337  }
338  }
339 
340  _name = ocpn::tolower(_name);
341  _version = ocpn::tolower(_version);
342 }
343 
344 PluginHandler::PluginHandler() {}
345 
346 bool PluginHandler::isCompatible(const PluginMetadata& metadata, const char* os,
347  const char* os_version) {
348  static const SemanticVersion kMinApi = SemanticVersion(1, 16);
349  static const SemanticVersion kMaxApi = SemanticVersion(1, 19);
350  auto plugin_api = SemanticVersion::parse(metadata.api_version);
351  if (plugin_api.major == -1) {
352  DEBUG_LOG << "Cannot parse API version \"" << metadata.api_version << "\"";
353  return false;
354  }
355  if (plugin_api < kMinApi || plugin_api > kMaxApi) {
356  DEBUG_LOG << "Incompatible API version \"" << metadata.api_version << "\"";
357  return false;
358  }
359 
360  static const std::vector<std::string> simple_abis = {
361  "msvc", "msvc-wx32", "android-armhf", "android-arm64"};
362 
363  Plugin plugin(metadata);
364  if (plugin.abi() == "all") {
365  wxLogDebug("Returning true for plugin abi \"all\"");
366  return true;
367  }
368  auto compatOS = CompatOs::getInstance();
369  Host host(compatOS);
370 
371  auto found = std::find(simple_abis.begin(), simple_abis.end(), plugin.abi());
372  if (found != simple_abis.end()) {
373  bool ok = plugin.abi() == host.abi();
374  wxLogDebug("Returning %s for %s", (ok ? "ok" : "fail"), host.abi());
375  wxLogDebug(" ");
376  return ok;
377  }
378  bool rv = false;
379  if (host.abi() == plugin.abi() && host.is_version_compatible(plugin)) {
380  rv = true;
381  wxLogDebug("Found matching abi version %s", plugin.abi_version());
382  } else if (host.is_debian_plugin_compatible(plugin)) {
383  rv = true;
384  wxLogDebug("Found Debian version matching Ubuntu host");
385  }
386  // macOS is an exception as packages with universal binaries can support both x86_64 and arm64 at the same time
387  if (host.abi() == "darwin-wx32" && plugin.abi() == "darwin-wx32") {
388  OCPN_OSDetail *detail = g_BasePlatform->GetOSDetail();
389  auto found = metadata.target_arch.find(detail->osd_arch);
390  if(found != std::string::npos) {
391  rv = true;
392  }
393  }
394  DEBUG_LOG << "Plugin compatibility check Final: "
395  << (rv ? "ACCEPTED: " : "REJECTED: ") << metadata.name;
396  return rv;
397 }
398 
399 std::string PluginHandler::fileListPath(std::string name) {
400  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
401  return pluginsConfigDir() + SEP + name + ".files";
402 }
403 
404 std::string PluginHandler::versionPath(std::string name) {
405  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
406  return pluginsConfigDir() + SEP + name + ".version";
407 }
408 
409 std::string PluginHandler::ImportedMetadataPath(std::string name) {
410  ;
411  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
412  return importsDir() + SEP + name + ".xml";
413 }
414 
415 typedef std::unordered_map<std::string, std::string> pathmap_t;
416 
421 static pathmap_t getInstallPaths() {
422  using namespace std;
423 
424  pathmap_t pathmap;
426  pathmap["bin"] = paths->UserBindir();
427  pathmap["lib"] = paths->UserLibdir();
428  pathmap["lib64"] = paths->UserLibdir();
429  pathmap["share"] = paths->UserDatadir();
430  return pathmap;
431 }
432 
433 static void saveFilelist(std::string filelist, std::string name) {
434  using namespace std;
435  string listpath = PluginHandler::fileListPath(name);
436  ofstream diskfiles(listpath);
437  if (!diskfiles.is_open()) {
438  wxLogWarning("Cannot create installed files list.");
439  return;
440  }
441  diskfiles << filelist;
442 }
443 
444 static void saveDirlist(std::string name) {
445  using namespace std;
446  string path = dirListPath(name);
447  ofstream dirs(path);
448  if (!dirs.is_open()) {
449  wxLogWarning("Cannot create installed files list.");
450  return;
451  }
452  pathmap_t pathmap = getInstallPaths();
453  unordered_map<string, string>::iterator it;
454  for (it = pathmap.begin(); it != pathmap.end(); it++) {
455  dirs << it->first << ": " << it->second << endl;
456  }
457 }
458 
459 static void saveVersion(const std::string& name, const std::string& version) {
460  using namespace std;
461  string path = PluginHandler::versionPath(name);
462  ofstream stream(path);
463  if (!stream.is_open()) {
464  wxLogWarning("Cannot create version file.");
465  return;
466  }
467  stream << version << endl;
468 }
469 
470 static int copy_data(struct archive* ar, struct archive* aw) {
471  int r;
472  const void* buff;
473  size_t size;
474  la_int64_t offset;
475 
476  while (true) {
477  r = archive_read_data_block(ar, &buff, &size, &offset);
478  if (r == ARCHIVE_EOF) return (ARCHIVE_OK);
479  if (r < ARCHIVE_OK) {
480  std::string s(archive_error_string(ar));
481  return (r);
482  }
483  r = archive_write_data_block(aw, buff, size, offset);
484  if (r < ARCHIVE_OK) {
485  std::string s(archive_error_string(aw));
486  wxLogWarning("Error copying install data: %s", archive_error_string(aw));
487  return (r);
488  }
489  }
490 }
491 
492 static bool win_entry_set_install_path(struct archive_entry* entry,
493  pathmap_t installPaths) {
494  using namespace std;
495 
496  string path = archive_entry_pathname(entry);
497 
498  // Check # components, drop the single top-level path
499  int slashes = count(path.begin(), path.end(), '/');
500  if (slashes < 1) {
501  archive_entry_set_pathname(entry, "");
502  return true;
503  }
504  if (ocpn::startswith(path, "./")) {
505  path = path.substr(1);
506  }
507 
508  // Remove top-level directory part
509  int slashpos = path.find_first_of('/', 1);
510  if (slashpos < 0) {
511  archive_entry_set_pathname(entry, "");
512  return true;
513  }
514 
515  string prefix = path.substr(0, slashpos);
516  path = path.substr(prefix.size() + 1);
517 
518  // Map remaining path to installation directory
519  if (ocpn::endswith(path, ".dll") || ocpn::endswith(path, ".exe")) {
520  slashpos = path.find_first_of('/');
521  path = path.substr(slashpos + 1);
522  path = installPaths["bin"] + "\\" + path;
523  } else if (ocpn::startswith(path, "share")) {
524  // The "share" directory should be a direct sibling of "plugins" directory
525  wxFileName fn(installPaths["share"].c_str(),
526  ""); // should point to .../opencpn/plugins
527  fn.RemoveLastDir(); // should point to ".../opencpn
528  path = fn.GetFullPath().ToStdString() + path;
529  } else if (ocpn::startswith(path, "plugins")) {
530  slashpos = path.find_first_of('/');
531  // share path already ends in plugins/, drop prefix from archive entry.
532  path = path.substr(slashpos + 1);
533  path = installPaths["share"] + "\\" + path;
534 
535  } else if (archive_entry_filetype(entry) == AE_IFREG) {
536  wxString msg(_T("PluginHandler::Invalid install path on file: "));
537  msg += wxString(path.c_str());
538  wxLogDebug(msg);
539  return false;
540  }
541  wxString s(path);
542  s.Replace("/", "\\"); // std::regex_replace FTBS on gcc 4.8.4
543  s.Replace("\\\\", "\\");
544  archive_entry_set_pathname(entry, s.c_str());
545  return true;
546 }
547 
548 static bool flatpak_entry_set_install_path(struct archive_entry* entry,
549  pathmap_t installPaths) {
550  using namespace std;
551 
552  string path = archive_entry_pathname(entry);
553  int slashes = count(path.begin(), path.end(), '/');
554  if (slashes < 2) {
555  archive_entry_set_pathname(entry, "");
556  return true;
557  }
558  if (ocpn::startswith(path, "./")) {
559  path = path.substr(2);
560  }
561  int slashpos = path.find_first_of('/', 1);
562  string prefix = path.substr(0, slashpos);
563  path = path.substr(prefix.size() + 1);
564  slashpos = path.find_first_of('/');
565  string location = path.substr(0, slashpos);
566  string suffix = path.substr(slashpos + 1);
567  if (installPaths.find(location) == installPaths.end() &&
568  archive_entry_filetype(entry) == AE_IFREG) {
569  wxString msg(_T("PluginHandler::Invalid install path on file: "));
570  msg += wxString(path.c_str());
571  wxLogDebug(msg);
572  return false;
573  }
574  string dest = installPaths[location] + "/" + suffix;
575  archive_entry_set_pathname(entry, dest.c_str());
576 
577  return true;
578 }
579 
580 static bool linux_entry_set_install_path(struct archive_entry* entry,
581  pathmap_t installPaths) {
582  using namespace std;
583 
584  string path = archive_entry_pathname(entry);
585  int slashes = count(path.begin(), path.end(), '/');
586  if (slashes < 2) {
587  archive_entry_set_pathname(entry, "");
588  return true;
589  }
590 
591  int slashpos = path.find_first_of('/', 1);
592  if (ocpn::startswith(path, "./"))
593  slashpos = path.find_first_of('/', 2); // skip the './'
594 
595  string prefix = path.substr(0, slashpos);
596  path = path.substr(prefix.size() + 1);
597  if (ocpn::startswith(path, "usr/")) {
598  path = path.substr(strlen("usr/"));
599  }
600  if (ocpn::startswith(path, "local/")) {
601  path = path.substr(strlen("local/"));
602  }
603  slashpos = path.find_first_of('/');
604  string location = path.substr(0, slashpos);
605  string suffix = path.substr(slashpos + 1);
606  if (installPaths.find(location) == installPaths.end() &&
607  archive_entry_filetype(entry) == AE_IFREG) {
608  wxString msg(_T("PluginHandler::Invalid install path on file: "));
609  msg += wxString(path.c_str());
610  wxLogDebug(msg);
611  return false;
612  }
613 
614  string dest = installPaths[location] + "/" + suffix;
615 
616  if (g_bportable) {
617  // A data dir?
618  if (ocpn::startswith(location, "share") &&
619  ocpn::startswith(suffix, "opencpn/plugins/")) {
620  slashpos = suffix.find_first_of("opencpn/plugins/");
621  suffix = suffix.substr(16);
622 
623  dest = g_BasePlatform->GetPrivateDataDir().ToStdString() + "/plugins/" +
624  suffix;
625  }
626  if (ocpn::startswith(location, "lib") &&
627  ocpn::startswith(suffix, "opencpn/")) {
628  suffix = suffix.substr(8);
629 
630  dest = g_BasePlatform->GetPrivateDataDir().ToStdString() +
631  "/plugins/lib/" + suffix;
632  }
633  }
634 
635  archive_entry_set_pathname(entry, dest.c_str());
636  return true;
637 }
638 
639 static bool apple_entry_set_install_path(struct archive_entry* entry,
640  pathmap_t installPaths) {
641  using namespace std;
642 
643  const string base = PluginPaths::getInstance()->Homedir() +
644  "/Library/Application Support/OpenCPN";
645 
646  string path = archive_entry_pathname(entry);
647  if (ocpn::startswith(path, "./")) path = path.substr(2);
648 
649  string dest("");
650  size_t slashes = count(path.begin(), path.end(), '/');
651  if (slashes < 3) {
652  archive_entry_set_pathname(entry, "");
653  return true;
654  }
655  auto parts = split(path, "Contents/Resources");
656  if (parts.size() >= 2) {
657  dest = base + "/Contents/Resources" + parts[1];
658  }
659  if (dest == "") {
660  parts = split(path, "Contents/SharedSupport");
661  if (parts.size() >= 2) {
662  dest = base + "/Contents/SharedSupport" + parts[1];
663  }
664  }
665  if (dest == "") {
666  parts = split(path, "Contents/PlugIns");
667  if (parts.size() >= 2) {
668  dest = base + "/Contents/PlugIns" + parts[1];
669  }
670  }
671  if (dest == "" && archive_entry_filetype(entry) == AE_IFREG) {
672  wxString msg(_T("PluginHandler::Invalid install path on file: "));
673  msg += wxString(path.c_str());
674  wxLogDebug(msg);
675  return false;
676  }
677  archive_entry_set_pathname(entry, dest.c_str());
678  return true;
679 }
680 
681 static bool android_entry_set_install_path(struct archive_entry* entry,
682  pathmap_t installPaths) {
683  using namespace std;
684 
685  string path = archive_entry_pathname(entry);
686  int slashes = count(path.begin(), path.end(), '/');
687  if (slashes < 2) {
688  archive_entry_set_pathname(entry, "");
689  return true;
690  ;
691  }
692 
693  int slashpos = path.find_first_of('/', 1);
694  if (ocpn::startswith(path, "./"))
695  slashpos = path.find_first_of('/', 2); // skip the './'
696 
697  string prefix = path.substr(0, slashpos);
698  path = path.substr(prefix.size() + 1);
699  if (ocpn::startswith(path, "usr/")) {
700  path = path.substr(strlen("usr/"));
701  }
702  if (ocpn::startswith(path, "local/")) {
703  path = path.substr(strlen("local/"));
704  }
705  slashpos = path.find_first_of('/');
706  string location = path.substr(0, slashpos);
707  string suffix = path.substr(slashpos + 1);
708  if (installPaths.find(location) == installPaths.end() &&
709  archive_entry_filetype(entry) == AE_IFREG) {
710  wxString msg(_T("PluginHandler::Invalid install path on file: "));
711  msg += wxString(path.c_str());
712  wxLogDebug(msg);
713  return false;
714  }
715 
716  if ((location == "lib") && ocpn::startswith(suffix, "opencpn")) {
717  auto parts = split(suffix, "/");
718  if (parts.size() == 2) suffix = parts[1];
719  }
720 
721  if ((location == "share") && ocpn::startswith(suffix, "opencpn")) {
722  auto parts = split(suffix, "opencpn/");
723  if (parts.size() == 2) suffix = parts[1];
724  }
725 
727  string dest = installPaths[location] + "/" + suffix;
728 
729  archive_entry_set_pathname(entry, dest.c_str());
730  return true;
731 }
732 
733 static bool entry_set_install_path(struct archive_entry* entry,
734  pathmap_t installPaths) {
735  const std::string src = archive_entry_pathname(entry);
736  bool rv;
737 #ifdef __OCPN__ANDROID__
738  rv = android_entry_set_install_path(entry, installPaths);
739 #else
740  const auto osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
741  if (g_BasePlatform->isFlatpacked()) {
742  rv = flatpak_entry_set_install_path(entry, installPaths);
743  } else if (osSystemId & wxOS_UNIX_LINUX) {
744  rv = linux_entry_set_install_path(entry, installPaths);
745  } else if (osSystemId & wxOS_WINDOWS) {
746  rv = win_entry_set_install_path(entry, installPaths);
747  } else if (osSystemId & wxOS_MAC) {
748  rv = apple_entry_set_install_path(entry, installPaths);
749  } else {
750  wxLogMessage("set_install_path() invoked, unsupported platform %s",
751  wxPlatformInfo::Get().GetOperatingSystemDescription());
752  rv = false;
753  }
754 #endif
755  const std::string dest = archive_entry_pathname(entry);
756  if (rv) {
757  if (dest.size()) {
758  DEBUG_LOG << "Installing " << src << " into " << dest << std::endl;
759  }
760  }
761  return rv;
762 }
763 
764 bool PluginHandler::archive_check(int r, const char* msg, struct archive* a) {
765  if (r < ARCHIVE_OK) {
766  std::string s(msg);
767 
768  if (archive_error_string(a)) s = s + ": " + archive_error_string(a);
769  wxLogMessage(s.c_str());
770  last_error_msg = s;
771  }
772  return r >= ARCHIVE_WARN;
773 }
774 
775 bool PluginHandler::explodeTarball(struct archive* src, struct archive* dest,
776  std::string& filelist,
777  const std::string& metadata_path,
778  bool only_metadata) {
779  struct archive_entry* entry = 0;
780  pathmap_t pathmap = getInstallPaths();
781  while (true) {
782  int r = archive_read_next_header(src, &entry);
783  if (r == ARCHIVE_EOF) {
784  return true;
785  }
786  if (!archive_check(r, "archive read header error", src)) {
787  return false;
788  }
789  std::string path = archive_entry_pathname(entry);
790  bool is_metadata = std::string::npos != path.find("metadata.xml");
791  if (is_metadata) {
792  if (metadata_path == "") continue;
793  archive_entry_set_pathname(entry, metadata_path.c_str());
794  } else if (!entry_set_install_path(entry, pathmap))
795  continue;
796  if (strlen(archive_entry_pathname(entry)) == 0) {
797  continue;
798  }
799  if (!is_metadata && only_metadata) {
800  continue;
801  }
802  if (!is_metadata) {
803  filelist.append(std::string(archive_entry_pathname(entry)) + "\n");
804  }
805  r = archive_write_header(dest, entry);
806  archive_check(r, "archive write install header error", dest);
807  if (r >= ARCHIVE_OK && archive_entry_size(entry) > 0) {
808  r = copy_data(src, dest);
809  if (!archive_check(r, "archive copy data error", dest)) {
810  return false;
811  }
812  }
813  r = archive_write_finish_entry(dest);
814  if (!archive_check(r, "archive finish write error", dest)) {
815  return false;
816  }
817  }
818  return false; // notreached
819 }
820 
821 /*
822  * Extract tarball into platform-specific user directories.
823  *
824  * The installed tarball has paths like topdir/dest/suffix_path... e. g.
825  * oesenc_pi_ubuntu_10_64/usr/local/share/opencpn/plugins/oesenc_pi/README.
826  * In this path, the topdir part must exist but is discarded. Next parts
827  * being being standard prefixes like /usr/local or /usr are also
828  * discarded. The remaining path (here share) is mapped to a user
829  * directory. On linux, it ends up in ~/.local/share. The suffix
830  * part is then installed as-is into this directory.
831  *
832  * Windows tarballs has dll and binary files in the top directory. They
833  * go to winInstallDir/Program Files. Message catalogs exists under a
834  * share/ toplevel directory, they go in winInstallDir/share. The
835  * plugin data is installed under winInstallDir/plugins/<plugin name>,
836  * and must be looked up by the plugins using GetPluginDataDir(plugin);
837  * Windows requires that PATH is set to include the binary dir and tha
838  * a bindtextdomain call is invoked to define the message catalog paths.
839  *
840  * For linux, the expected destinations are bin, lib and share.
841  *
842  * @param path path to tarball
843  * @param filelist: On return contains a list of files installed.
844  * @param metadata_path: if non-empty, location where to store metadata,
845  * @param only_metadata: If true don't install any files, just extract
846  * metadata.
847  *
848  */
849 bool PluginHandler::extractTarball(const std::string path,
850  std::string& filelist,
851  const std::string metadata_path,
852  bool only_metadata) {
853  struct archive* src = archive_read_new();
854  archive_read_support_filter_gzip(src);
855  archive_read_support_format_tar(src);
856  int r = archive_read_open_filename(src, path.c_str(), 10240);
857  if (r != ARCHIVE_OK) {
858  std::ostringstream os;
859  os << "Cannot read installation tarball: " << path;
860  wxLogWarning(os.str().c_str());
861  last_error_msg = os.str();
862  return false;
863  }
864  struct archive* dest = archive_write_disk_new();
865  archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
866  bool ok = explodeTarball(src, dest, filelist, metadata_path, only_metadata);
867  archive_read_free(src);
868  archive_write_free(dest);
869  return ok;
870 }
871 
872 PluginHandler* PluginHandler::getInstance() {
873  static PluginHandler* instance = 0;
874  if (!instance) {
875  instance = new (PluginHandler);
876  }
877  return instance;
878 }
879 
880 bool PluginHandler::isPluginWritable(std::string name) {
881  if (isRegularFile(PluginHandler::fileListPath(name).c_str())) {
882  return true;
883  }
884  auto loader = PluginLoader::getInstance();
885  return PlugInIxByName(name, loader->GetPlugInArray()) == -1;
886 }
887 
888 static std::string computeMetadataPath(void) {
889  std::string path = g_BasePlatform->GetPrivateDataDir().ToStdString();
890  path += SEP;
891  path += "ocpn-plugins.xml";
892  if (ocpn::exists(path)) {
893  return path;
894  }
895 
896  // If default location for composit plugin metadata is not found,
897  // we look in the plugin cache directory, which will normally contain
898  // he last "master" catalog downloaded
899  path = ocpn::lookup_metadata();
900  if (path != "") {
901  return path;
902  }
903 
904  // And if that does not work, use the empty metadata file found in the
905  // distribution "data" directory
906  path = g_BasePlatform->GetSharedDataDir();
907  path += SEP;
908  path += "ocpn-plugins.xml";
909  if (!ocpn::exists(path)) {
910  wxLogWarning("Non-existing plugins file: %s", path);
911  }
912  return path;
913 }
914 
915 static void parseMetadata(const std::string path, CatalogCtx& ctx) {
916  using namespace std;
917 
918  wxLogMessage("PluginHandler: using metadata path: %s", path);
919  ctx.depth = 0;
920  if (!ocpn::exists(path)) {
921  wxLogWarning("Non-existing plugins metadata file: %s", path.c_str());
922  return;
923  }
924  ifstream ifpath(path);
925  std::string xml((istreambuf_iterator<char>(ifpath)),
926  istreambuf_iterator<char>());
927  ParseCatalog(xml, &ctx);
928 }
929 
930 bool PluginHandler::InstallPlugin(const std::string& path,
931  std::string& filelist,
932  const std::string metadata_path,
933  bool only_metadata) {
934  if (!extractTarball(path, filelist, metadata_path, only_metadata)) {
935  std::ostringstream os;
936  os << "Cannot unpack plugin tarball at : " << path;
937  if (filelist != "") cleanup(filelist, "unknown_name");
938  last_error_msg = os.str();
939  return false;
940  }
941  if (only_metadata) {
942  return true;
943  }
944  struct CatalogCtx ctx;
945  std::ifstream istream(metadata_path);
946  std::stringstream buff;
947  buff << istream.rdbuf();
948 
949  auto xml = std::string("<plugins>") + buff.str() + "</plugins>";
950  ParseCatalog(xml, &ctx);
951  auto name = ctx.plugins[0].name;
952  auto version = ctx.plugins[0].version;
953  saveFilelist(filelist, name);
954  saveDirlist(name);
955  saveVersion(name, version);
956 
957  return true;
958 }
959 
961  if (metadataPath.size() > 0) {
962  return metadataPath;
963  }
964  metadataPath = computeMetadataPath();
965  wxLogDebug("Using metadata path: %s", metadataPath.c_str());
966  return metadataPath;
967 }
968 
969 const std::map<std::string, int> PluginHandler::getCountByTarget() {
970  auto plugins = getInstalled();
971  auto a = getAvailable();
972  plugins.insert(plugins.end(), a.begin(), a.end());
973  std::map<std::string, int> count_by_target;
974  for (const auto& p : plugins) {
975  if (p.target == "") {
976  continue; // Built-in plugins like dashboard et. al.
977  }
978  auto key = p.target + ":" + p.target_version;
979  if (count_by_target.find(key) == count_by_target.end()) {
980  count_by_target[key] = 1;
981  } else {
982  count_by_target[key] += 1;
983  }
984  }
985  return count_by_target;
986 }
987 
988 std::vector<std::string> PluginHandler::GetImportPaths() {
989  return glob_dir(importsDir(), "*.xml");
990 }
991 
992 void PluginHandler::cleanupFiles(const std::string& manifestFile,
993  const std::string& plugname) {
994  std::ifstream diskfiles(manifestFile);
995  if (diskfiles.is_open()) {
996  std::stringstream buffer;
997  buffer << diskfiles.rdbuf();
998  PluginHandler::cleanup(buffer.str(), plugname);
999  }
1000 }
1001 
1003 static void PurgeEmptyDirs(const std::string& root) {
1004  if (!wxFileName::IsDirWritable(root)) return;
1005  if (ocpn::tolower(root).find("opencpn") == std::string::npos) return;
1006  wxDir rootdir(root);
1007  if (!rootdir.IsOpened()) return;
1008  wxString dirname;
1009  bool cont = rootdir.GetFirst(&dirname, "", wxDIR_DIRS);
1010  while (cont) {
1011  PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1012  cont = rootdir.GetNext(&dirname);
1013  }
1014  rootdir.Close();
1015  rootdir.Open(root);
1016  if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1017  wxFileName::Rmdir(rootdir.GetName());
1018  }
1019 }
1020 
1021 void PluginHandler::cleanup(const std::string& filelist,
1022  const std::string& plugname) {
1023  wxLogMessage("Cleaning up failed install of %s", plugname.c_str());
1024 
1025  std::vector<std::string> paths = LoadLinesFromFile(filelist);
1026  for (const auto& path : paths) {
1027  if (isRegularFile(path.c_str())) {
1028  int r = remove(path.c_str());
1029  if (r != 0) wxLogWarning("Cannot remove file %s: %s", path, strerror(r));
1030  }
1031  }
1032  for (const auto& path : paths) PurgeEmptyDirs(path);
1033 
1034  std::string path = PluginHandler::fileListPath(plugname);
1035  if (ocpn::exists(path)) remove(path.c_str());
1036 
1037  // Best effort tries, failures are non-critical
1038  remove(dirListPath(plugname).c_str());
1039  remove(PluginHandler::versionPath(plugname).c_str());
1040 }
1041 
1046 std::vector<PluginMetadata> PluginHandler::getCompatiblePlugins() {
1048  struct metadata_compare {
1049  bool operator()(const PluginMetadata& lhs,
1050  const PluginMetadata& rhs) const {
1051  return lhs.key() < rhs.key();
1052  }
1053  };
1054 
1055  std::vector<PluginMetadata> returnArray;
1056 
1057  std::set<PluginMetadata, metadata_compare> unique_plugins;
1058  for (const auto& plugin : getAvailable()) {
1059  unique_plugins.insert(plugin);
1060  }
1061  for (const auto& plugin : unique_plugins) {
1062  if (isCompatible(plugin)) {
1063  returnArray.push_back(plugin);
1064  }
1065  }
1066  return returnArray;
1067 }
1068 
1069 const std::vector<PluginMetadata> PluginHandler::getAvailable() {
1070  using namespace std;
1071  CatalogCtx *ctx;
1072 
1073  auto catalogHandler = CatalogHandler::getInstance();
1074 
1075  ctx = catalogHandler->GetActiveCatalogContext();
1076  auto status = catalogHandler->GetCatalogStatus();
1077 
1078  if (status == CatalogHandler::ServerStatus::OK) {
1079  catalogData.undef = false;
1080  catalogData.version = ctx->version;
1081  catalogData.date = ctx->date;
1082  }
1083  return ctx->plugins;
1084 }
1085 
1086 std::vector<std::string> PluginHandler::GetInstalldataPlugins() {
1087  std::vector<std::string> names;
1088  fs::path dirpath(pluginsInstallDataPath());
1089  for (const auto& entry: fs::directory_iterator(dirpath)) {
1090  const std::string name(entry.path().filename().string());
1091  if (ocpn::endswith(name, ".files"))
1092  names.push_back(ocpn::split(name.c_str(), ".")[0]);
1093  }
1094  return names;
1095 }
1096 
1097 const std::vector<PluginMetadata> PluginHandler::getInstalled() {
1098  using namespace std;
1099  vector<PluginMetadata> plugins;
1100 
1101  auto loader = PluginLoader::getInstance();
1102  for (unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1103  const PlugInContainer* p = loader->GetPlugInArray()->Item(i);
1104  PluginMetadata plugin;
1105  auto name = string(p->m_common_name);
1106  // std::transform(name.begin(), name.end(), name.begin(), ::tolower);
1107  plugin.name = name;
1108  std::stringstream ss;
1109  ss << p->m_version_major << "." << p->m_version_minor;
1110  plugin.version = ss.str();
1111  plugin.readonly = !isPluginWritable(plugin.name);
1112  string path = PluginHandler::versionPath(plugin.name);
1113  if (path != "" && wxFileName::IsFileReadable(path)) {
1114  std::ifstream stream;
1115  stream.open(path, ifstream::in);
1116  stream >> plugin.version;
1117  }
1118  plugins.push_back(plugin);
1119  }
1120  return plugins;
1121 }
1122 
1124  auto loader = PluginLoader::getInstance();
1125  ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1126  if (ix == -1) return; // no such plugin
1127 
1128  auto plugins = *loader->GetPlugInArray();
1129  plugins[ix]->m_managed_metadata = pm;
1130 }
1131 
1132 bool PluginHandler::installPlugin(PluginMetadata plugin, std::string path) {
1133  std::string filelist;
1134  if (!extractTarball(path, filelist)) {
1135  std::ostringstream os;
1136  os << "Cannot unpack plugin: " << plugin.name << " at " << path;
1137  last_error_msg = os.str();
1138  PluginHandler::cleanup(filelist, plugin.name);
1139  return false;
1140  }
1141  saveFilelist(filelist, plugin.name);
1142  saveDirlist(plugin.name);
1143  saveVersion(plugin.name, plugin.version);
1144 
1145  return true;
1146 }
1147 
1149  std::string path;
1150  char fname[4096];
1151 
1152  if (tmpnam(fname) == NULL) {
1153  wxLogWarning("Cannot create temporary file");
1154  path = "";
1155  return false;
1156  }
1157  path = std::string(fname);
1158  std::ofstream stream;
1159  stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1160  DEBUG_LOG << "Downloading: " << plugin.name << std::endl;
1161  auto downloader = Downloader(plugin.tarball_url);
1162  downloader.download(&stream);
1163 
1164  return installPlugin(plugin, path);
1165 }
1166 
1167 bool PluginHandler::installPlugin(const std::string& path) {
1168  PluginMetadata metadata;
1169  if (!ExtractMetadata(path, metadata)) {
1170  MESSAGE_LOG << "Cannot extract metadata from tarball";
1171  return false;
1172  }
1173  return installPlugin(metadata, path);
1174 }
1175 
1176 bool PluginHandler::ExtractMetadata(const std::string& path,
1177  PluginMetadata& metadata) {
1178  std::string filelist;
1179  std::string temp_path(tmpnam(0));
1180  if (!extractTarball(path, filelist, temp_path, true)) {
1181  std::ostringstream os;
1182  os << "Cannot unpack plugin tarball at : " << path;
1183  if (filelist != "") cleanup(filelist, "unknown_name");
1184  last_error_msg = os.str();
1185  return false;
1186  }
1187  if (!isRegularFile(temp_path.c_str()))
1188  return false;
1189 
1190  struct CatalogCtx ctx;
1191  std::ifstream istream(temp_path);
1192  std::stringstream buff;
1193  buff << istream.rdbuf();
1194  remove(temp_path.c_str());
1195 
1196  auto xml = std::string("<plugins>") + buff.str() + "</plugins>";
1197  ParseCatalog(xml, &ctx);
1198  metadata = ctx.plugins[0];
1199  return !metadata.name.empty();
1200 }
1201 
1202 bool PluginHandler::ClearInstallData(const std::string plugin_name) {
1203  auto ix = PlugInIxByName(plugin_name,
1204  PluginLoader::getInstance()->GetPlugInArray());
1205  if (ix != -1) {
1206  wxLogWarning("Attempt to remove installation data for loaded plugin");
1207  return false;
1208  }
1209  return DoClearInstallData(plugin_name);
1210 }
1211 
1212 bool PluginHandler::DoClearInstallData(const std::string plugin_name) {
1213  std::string path = PluginHandler::fileListPath(plugin_name);
1214  if (!ocpn::exists(path)) {
1215  wxLogWarning("Cannot find installation data for %s (%s)",
1216  plugin_name.c_str(), path.c_str());
1217  return false;
1218  }
1219  std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1220  for (const auto& p : plug_paths) {
1221  if (isRegularFile(p.c_str())) {
1222  int r = remove(p.c_str());
1223  if (r != 0) {
1224  wxLogWarning("Cannot remove file %s: %s", p.c_str(), strerror(r));
1225  }
1226  }
1227  }
1228  for (const auto& p : plug_paths) PurgeEmptyDirs(p);
1229  int r = remove(path.c_str());
1230  if (r != 0) {
1231  wxLogWarning("Cannot remove file %s: %s", path.c_str(), strerror(r));
1232  }
1233  // Best effort tries, failures are OK.
1234  remove(dirListPath(plugin_name).c_str());
1235  remove(PluginHandler::versionPath(plugin_name).c_str());
1236  remove(PluginHandler::ImportedMetadataPath(plugin_name).c_str());
1237  return true;
1238 }
1239 
1240 bool PluginHandler::uninstall(const std::string plugin_name) {
1241  using namespace std;
1242 
1243  auto loader = PluginLoader::getInstance();
1244  auto ix = PlugInIxByName(plugin_name, loader->GetPlugInArray());
1245  if (ix < 0) {
1246  wxLogMessage("trying to uninstall non-existing plugin %s",
1247  plugin_name.c_str());
1248  return false;
1249  }
1250  auto pic = loader->GetPlugInArray()->Item(ix);
1251 
1252  // Capture library file name before pic dies.
1253  string libfile = pic->m_plugin_file.ToStdString();
1254  loader->UnLoadPlugIn(ix);
1255 
1256  bool ok = DoClearInstallData(plugin_name);
1257 
1258  // If this is an orphan plugin, there may be no installation record
1259  // So make sure that the library file (.so/.dylib/.dll) is removed
1260  // as a minimum best effort requirement
1261  if (isRegularFile(libfile.c_str())) {
1262  remove(libfile.c_str());
1263  }
1264 
1265  return ok;
1266 }
1267 
1268 using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1269 
1275 static std::string FindMatchingDataDir(std::regex name_re) {
1276  using namespace std;
1277  wxString data_dirs(g_BasePlatform->GetPluginDataPath());
1278  wxStringTokenizer tokens(data_dirs, ";");
1279  while (tokens.HasMoreTokens()) {
1280  auto token = tokens.GetNextToken();
1281  wxFileName path(token);
1282  wxDir dir(path.GetFullPath());
1283  if (dir.IsOpened()) {
1284  wxString filename;
1285  bool cont = dir.GetFirst(&filename, "", wxDIR_DIRS);
1286  while (cont) {
1287  smatch sm;
1288  string s(filename);
1289  if (regex_search(s, sm, name_re)) {
1290  stringstream ss;
1291  for (auto c : sm) ss << c;
1292  return ss.str();
1293  }
1294  cont = dir.GetNext(&filename);
1295  }
1296  }
1297  }
1298  return "";
1299 }
1300 
1305 static std::string FindMatchingLibFile(std::regex name_re) {
1306  using namespace std;
1307  for (const auto& lib : PluginPaths::getInstance()->Libdirs()) {
1308  wxDir dir(lib);
1309  wxString filename;
1310  bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
1311  while (cont) {
1312  smatch sm;
1313  string s(filename);
1314  if (regex_search(s, sm, name_re)) {
1315  stringstream ss;
1316  for (auto c : sm) ss << c;
1317  return ss.str();
1318  }
1319  cont = dir.GetNext(&filename);
1320  }
1321  }
1322  return "";
1323 }
1324 
1326 static std::string PluginNameCase(const std::string& name) {
1327  using namespace std;
1328  const string lc_name = ocpn::tolower(name);
1329  regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1330 
1331  // Look for matching plugin in list of installed and available.
1332  // This often fails since the lists are not yet available when
1333  // plugins are loaded, but is otherwise a safe bet.
1334  for (const auto& plugin : PluginHandler::getInstance()->getInstalled()) {
1335  if (ocpn::tolower(plugin.name) == lc_name) return plugin.name;
1336  }
1337  for (const auto& plugin : PluginHandler::getInstance()->getAvailable()) {
1338  if (ocpn::tolower(plugin.name) == lc_name) return plugin.name;
1339  }
1340 
1341  string match = FindMatchingDataDir(name_re);
1342  if (match != "") return match;
1343 
1344  match = FindMatchingLibFile(name_re);
1345  return match != "" ? match : name;
1346 }
1347 
1349 static void LoadPluginMapFile(PluginMap& map, const std::string& path) {
1350  std::ifstream f;
1351  f.open(path);
1352  if (f.fail()) {
1353  wxLogWarning("Cannot open %s: %s", path.c_str(), strerror(errno));
1354  return;
1355  }
1356  std::stringstream buf;
1357  buf << f.rdbuf();
1358  auto filelist = ocpn::split(buf.str().c_str(), "\n");
1359  for (auto& file : filelist) {
1360  file = wxFileName(file).GetFullName().ToStdString();
1361  }
1362 
1363  // key is basename with removed .files suffix and correct case.
1364  auto key = wxFileName(path).GetFullName().ToStdString();
1365  key = ocpn::split(key.c_str(), ".")[0];
1366  key = PluginNameCase(key);
1367  map[key] = filelist;
1368 }
1369 
1371 static void LoadPluginMap(PluginMap& map) {
1372  map.clear();
1374  if (!root.IsOpened()) return;
1375  wxString filename;
1376  bool cont = root.GetFirst(&filename, "*.files", wxDIR_FILES);
1377  while (cont) {
1378  auto path = root.GetNameWithSep() + filename;
1379  LoadPluginMapFile(map, path.ToStdString());
1380  cont = root.GetNext(&filename);
1381  }
1382 }
1383 
1384 std::string PluginHandler::getPluginByLibrary(const std::string& filename) {
1385  auto basename = wxFileName(filename).GetFullName().ToStdString();
1386  if (files_by_plugin.size() == 0) LoadPluginMap(files_by_plugin);
1387  for (const auto& it : files_by_plugin) {
1388  auto found = std::find(it.second.begin(), it.second.end(), basename);
1389  if (found != it.second.end()) return it.first;
1390  }
1391  return "";
1392 }
1393 
1395  // Look for the desired file
1396  wxURI uri(wxString(plugin.tarball_url.c_str()));
1397  wxFileName fn(uri.GetPath());
1398  wxString tarballFile = fn.GetFullName();
1399  std::string cacheFile = ocpn::lookup_tarball(tarballFile);
1400 
1401 #ifdef __WXOSX__
1402  // Depending on the browser settings, MacOS will sometimes automatically
1403  // de-compress the tar.gz file, leaving a simple ".tar" file in its expected
1404  // place. Check for this case, and "do the right thing"
1405  if (cacheFile == "") {
1406  fn.ClearExt();
1407  wxFileName fn1(fn.GetFullName());
1408  if (fn1.GetExt().IsSameAs("tar")) {
1409  tarballFile = fn.GetFullName();
1410  cacheFile = ocpn::lookup_tarball(tarballFile);
1411  }
1412  }
1413 #endif
1414 
1415  if (cacheFile != "") {
1416  wxLogMessage("Installing %s from local cache", tarballFile.c_str());
1417  bool bOK = installPlugin(plugin, cacheFile);
1418  if (!bOK) {
1419  wxLogWarning("Cannot install tarball file %s", cacheFile.c_str());
1420  evt_download_failed.Notify(cacheFile);
1421  return false;
1422  }
1423  evt_download_ok.Notify(plugin.name + " " + plugin.version);
1424  return true;
1425  }
1426  return false;
1427 }
wxString & DefaultPrivateDataDir()
Return dir path for opencpn.log, etc., does not respect -c option.
wxString GetPluginDataPath()
Return ';'-separated list of base directories for plugin data.
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
Handle downloading of files from remote urls.
Definition: downloader.h:34
const void Notify()
Notify all listeners, no data supplied.
Host ABI encapsulation and plugin compatibility checks.
Data for a loaded plugin, including dl-loaded library.
Definition: plugin_loader.h:99
wxString m_common_name
A common name string for the plugin.
Definition: plugin_loader.h:79
const std::vector< PluginMetadata > getInstalled()
Return list of all installed and loaded plugins.
static void cleanup(const std::string &filelist, const std::string &plugname)
Cleanup failed installation attempt using filelist for plugin.
static std::vector< std::string > GetImportPaths()
List of paths for imported plugins metadata.
bool installPluginFromCache(PluginMetadata plugin)
Install plugin tarball from local cache.
const std::map< std::string, int > getCountByTarget()
Map of available plugin targets -> number of occurences.
std::vector< PluginMetadata > getCompatiblePlugins()
Return list of available, unique and compatible plugins from configured XML catalog.
static std::string ImportedMetadataPath(std::string name)
Return path to imported metadata for given plugin.
std::string getPluginByLibrary(const std::string &filename)
Return plugin containing given filename or "" if not found.
bool isPluginWritable(std::string name)
Check if given plugin can be installed/updated.
static std::string pluginsInstallDataPath()
Return base directory for installation data.
std::vector< std::string > GetInstalldataPlugins()
Return list of installed plugins lower case names, not necessarily loaded.
static std::string fileListPath(std::string name)
Return path to installation manifest for given plugin.
static std::string versionPath(std::string name)
Return path to file containing version for given plugin.
bool uninstall(const std::string plugin)
Uninstall an installed and loaded plugin.
bool installPlugin(PluginMetadata plugin)
Download and install a new, not installed plugin.
bool ClearInstallData(const std::string plugin_name)
Remove installation data for not loaded plugin.
std::string getMetadataPath()
Return path to metadata XML file.
void SetInstalledMetadata(const PluginMetadata &pm)
Set metadata for an installed plugin.
bool ExtractMetadata(const std::string &path, PluginMetadata &metadata)
Extract metadata in given tarball path.
const std::vector< PluginMetadata > getAvailable()
Update catalog and return list of available, not installed plugins.
static bool isCompatible(const PluginMetadata &metadata, const char *os=PKG_TARGET, const char *os_version=PKG_TARGET_VERSION)
Return true if given plugin is loadable on given os/version.
std::string UserLibdir()
The single, user-writable directory for installing .dll files.
Definition: plugin_paths.h:14
std::string Homedir() const
home directory, convenience stuff.
Definition: plugin_paths.h:38
std::string UserDatadir()
The single, user-writable common parent for plugin data directories, typically ending in 'plugins'.
Definition: plugin_paths.h:23
std::string UserBindir()
The single, user-writable directory for installing helper binaries.
Definition: plugin_paths.h:17
static PluginPaths * getInstance()
Return the singleton instance.
Plugin ABI encapsulation.
Global variables reflecting command line options and arguments.
std::string lookup_tarball(const char *uri)
Get path to tarball in cache for given filename.
std::string lookup_metadata(const char *name)
Get metadata path for a given name defaulting to ocpn-plugins.xml)
The result from parsing the xml catalog i.
Plugin metadata, reflects the xml format directly.
bool readonly
Can plugin be removed?
Versions uses a modified semantic versioning scheme: major.minor.revision.post-tag+build.
Definition: semantic_vers.h:51
static SemanticVersion parse(std::string s)
Parse a version string, sets major == -1 on errors.