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