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