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