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, 19);
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
527 // Check # components, drop the single top-level path
528 int slashes = count(path.begin(), path.end(), '/');
529 if (slashes < 1) {
530 archive_entry_set_pathname(entry, "");
531 return true;
532 }
533 if (ocpn::startswith(path, "./")) {
534 path = path.substr(1);
535 }
536
537 // Remove top-level directory part
538 int slashpos = path.find_first_of('/', 1);
539 if (slashpos < 0) {
540 archive_entry_set_pathname(entry, "");
541 return true;
542 }
543
544 string prefix = path.substr(0, slashpos);
545 path = path.substr(prefix.size() + 1);
546
547 // Map remaining path to installation directory
548 if (ocpn::endswith(path, ".dll") || ocpn::endswith(path, ".exe")) {
549 slashpos = path.find_first_of('/');
550 path = path.substr(slashpos + 1);
551 path = installPaths["bin"] + "\\" + path;
552 } else if (ocpn::startswith(path, "share")) {
553 // The "share" directory should be a direct sibling of "plugins" directory
554 wxFileName fn(installPaths["share"].c_str(),
555 ""); // should point to .../opencpn/plugins
556 fn.RemoveLastDir(); // should point to ".../opencpn
557 path = fn.GetFullPath().ToStdString() + path;
558 } else if (ocpn::startswith(path, "plugins")) {
559 slashpos = path.find_first_of('/');
560 // share path already ends in plugins/, drop prefix from archive entry.
561 path = path.substr(slashpos + 1);
562 path = installPaths["share"] + "\\" + path;
563
564 } else if (archive_entry_filetype(entry) == AE_IFREG) {
565 wxString msg(_T("PluginHandler::Invalid install path on file: "));
566 msg += wxString(path.c_str());
567 DEBUG_LOG << msg;
568 return false;
569 }
570 wxString s(path);
571 s.Replace("/", "\\"); // std::regex_replace FTBS on gcc 4.8.4
572 s.Replace("\\\\", "\\");
573 archive_entry_set_pathname(entry, s.c_str());
574 return true;
575}
576
577static bool flatpak_entry_set_install_path(struct archive_entry* entry,
578 pathmap_t installPaths) {
579 using namespace std;
580
581 string path = archive_entry_pathname(entry);
582 int slashes = count(path.begin(), path.end(), '/');
583 if (slashes < 2) {
584 archive_entry_set_pathname(entry, "");
585 return true;
586 }
587 if (ocpn::startswith(path, "./")) {
588 path = path.substr(2);
589 }
590 int slashpos = path.find_first_of('/', 1);
591 string prefix = path.substr(0, slashpos);
592 path = path.substr(prefix.size() + 1);
593 slashpos = path.find_first_of('/');
594 string location = path.substr(0, slashpos);
595 string suffix = path.substr(slashpos + 1);
596 if (installPaths.find(location) == installPaths.end() &&
597 archive_entry_filetype(entry) == AE_IFREG) {
598 wxString msg(_T("PluginHandler::Invalid install path on file: "));
599 msg += wxString(path.c_str());
600 DEBUG_LOG << msg;
601 return false;
602 }
603 string dest = installPaths[location] + "/" + suffix;
604 archive_entry_set_pathname(entry, dest.c_str());
605
606 return true;
607}
608
609static bool linux_entry_set_install_path(struct archive_entry* entry,
610 pathmap_t installPaths) {
611 using namespace std;
612
613 string path = archive_entry_pathname(entry);
614 int slashes = count(path.begin(), path.end(), '/');
615 if (slashes < 2) {
616 archive_entry_set_pathname(entry, "");
617 return true;
618 }
619
620 int slashpos = path.find_first_of('/', 1);
621 if (ocpn::startswith(path, "./"))
622 slashpos = path.find_first_of('/', 2); // skip the './'
623
624 string prefix = path.substr(0, slashpos);
625 path = path.substr(prefix.size() + 1);
626 if (ocpn::startswith(path, "usr/")) {
627 path = path.substr(strlen("usr/"));
628 }
629 if (ocpn::startswith(path, "local/")) {
630 path = path.substr(strlen("local/"));
631 }
632 slashpos = path.find_first_of('/');
633 string location = path.substr(0, slashpos);
634 string suffix = path.substr(slashpos + 1);
635 if (installPaths.find(location) == installPaths.end() &&
636 archive_entry_filetype(entry) == AE_IFREG) {
637 wxString msg(_T("PluginHandler::Invalid install path on file: "));
638 msg += wxString(path.c_str());
639 DEBUG_LOG << msg;
640 return false;
641 }
642
643 string dest = installPaths[location] + "/" + suffix;
644
645 if (g_bportable) {
646 // A data dir?
647 if (ocpn::startswith(location, "share") &&
648 ocpn::startswith(suffix, "opencpn/plugins/")) {
649 slashpos = suffix.find_first_of("opencpn/plugins/");
650 suffix = suffix.substr(16);
651
652 dest = g_BasePlatform->GetPrivateDataDir().ToStdString() + "/plugins/" +
653 suffix;
654 }
655 if (ocpn::startswith(location, "lib") &&
656 ocpn::startswith(suffix, "opencpn/")) {
657 suffix = suffix.substr(8);
658
659 dest = g_BasePlatform->GetPrivateDataDir().ToStdString() +
660 "/plugins/lib/" + suffix;
661 }
662 }
663
664 archive_entry_set_pathname(entry, dest.c_str());
665 return true;
666}
667
668static bool apple_entry_set_install_path(struct archive_entry* entry,
669 pathmap_t installPaths) {
670 using namespace std;
671
672 const string base = PluginPaths::getInstance()->Homedir() +
673 "/Library/Application Support/OpenCPN";
674
675 string path = archive_entry_pathname(entry);
676 if (ocpn::startswith(path, "./")) path = path.substr(2);
677
678 string dest("");
679 size_t slashes = count(path.begin(), path.end(), '/');
680 if (slashes < 3) {
681 archive_entry_set_pathname(entry, "");
682 return true;
683 }
684 auto parts = split(path, "Contents/Resources");
685 if (parts.size() >= 2) {
686 dest = base + "/Contents/Resources" + parts[1];
687 }
688 if (dest == "") {
689 parts = split(path, "Contents/SharedSupport");
690 if (parts.size() >= 2) {
691 dest = base + "/Contents/SharedSupport" + parts[1];
692 }
693 }
694 if (dest == "") {
695 parts = split(path, "Contents/PlugIns");
696 if (parts.size() >= 2) {
697 dest = base + "/Contents/PlugIns" + parts[1];
698 }
699 }
700 if (dest == "" && archive_entry_filetype(entry) == AE_IFREG) {
701 wxString msg(_T("PluginHandler::Invalid install path on file: "));
702 msg += wxString(path.c_str());
703 DEBUG_LOG << msg;
704 return false;
705 }
706 archive_entry_set_pathname(entry, dest.c_str());
707 return true;
708}
709
710static bool android_entry_set_install_path(struct archive_entry* entry,
711 pathmap_t installPaths) {
712 using namespace std;
713
714 string path = archive_entry_pathname(entry);
715 int slashes = count(path.begin(), path.end(), '/');
716 if (slashes < 2) {
717 archive_entry_set_pathname(entry, "");
718 return true;
719 ;
720 }
721
722 int slashpos = path.find_first_of('/', 1);
723 if (ocpn::startswith(path, "./"))
724 slashpos = path.find_first_of('/', 2); // skip the './'
725
726 string prefix = path.substr(0, slashpos);
727 path = path.substr(prefix.size() + 1);
728 if (ocpn::startswith(path, "usr/")) {
729 path = path.substr(strlen("usr/"));
730 }
731 if (ocpn::startswith(path, "local/")) {
732 path = path.substr(strlen("local/"));
733 }
734 slashpos = path.find_first_of('/');
735 string location = path.substr(0, slashpos);
736 string suffix = path.substr(slashpos + 1);
737 if (installPaths.find(location) == installPaths.end() &&
738 archive_entry_filetype(entry) == AE_IFREG) {
739 wxString msg(_T("PluginHandler::Invalid install path on file: "));
740 msg += wxString(path.c_str());
741 DEBUG_LOG << msg;
742 return false;
743 }
744
745 if ((location == "lib") && ocpn::startswith(suffix, "opencpn")) {
746 auto parts = split(suffix, "/");
747 if (parts.size() == 2) suffix = parts[1];
748 }
749
750 if ((location == "share") && ocpn::startswith(suffix, "opencpn")) {
751 auto parts = split(suffix, "opencpn/");
752 if (parts.size() == 2) suffix = parts[1];
753 }
754
756 string dest = installPaths[location] + "/" + suffix;
757
758 archive_entry_set_pathname(entry, dest.c_str());
759 return true;
760}
761
762static bool entry_set_install_path(struct archive_entry* entry,
763 pathmap_t installPaths) {
764 const std::string src = archive_entry_pathname(entry);
765 bool rv;
766#ifdef __OCPN__ANDROID__
767 rv = android_entry_set_install_path(entry, installPaths);
768#else
769 const auto osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
770 if (g_BasePlatform->isFlatpacked()) {
771 rv = flatpak_entry_set_install_path(entry, installPaths);
772 } else if (osSystemId & wxOS_UNIX_LINUX) {
773 rv = linux_entry_set_install_path(entry, installPaths);
774 } else if (osSystemId & wxOS_WINDOWS) {
775 rv = win_entry_set_install_path(entry, installPaths);
776 } else if (osSystemId & wxOS_MAC) {
777 rv = apple_entry_set_install_path(entry, installPaths);
778 } else {
779 MESSAGE_LOG << "set_install_path() invoked, unsupported platform "
780 << wxPlatformInfo::Get().GetOperatingSystemDescription();
781 rv = false;
782 }
783#endif
784 const std::string dest = archive_entry_pathname(entry);
785 if (rv) {
786 if (dest.size()) {
787 DEBUG_LOG << "Installing " << src << " into " << dest << std::endl;
788 }
789 }
790 return rv;
791}
792
793bool PluginHandler::archive_check(int r, const char* msg, struct archive* a) {
794 if (r < ARCHIVE_OK) {
795 std::string s(msg);
796
797 if (archive_error_string(a)) s = s + ": " + archive_error_string(a);
798 MESSAGE_LOG << s;
799 last_error_msg = s;
800 }
801 return r >= ARCHIVE_WARN;
802}
803
804bool PluginHandler::explodeTarball(struct archive* src, struct archive* dest,
805 std::string& filelist,
806 const std::string& metadata_path,
807 bool only_metadata) {
808 struct archive_entry* entry = 0;
809 pathmap_t pathmap = getInstallPaths();
810 bool is_metadata_ok = false;
811 while (true) {
812 int r = archive_read_next_header(src, &entry);
813 if (r == ARCHIVE_EOF) {
814 if (!is_metadata_ok) {
815 MESSAGE_LOG << "Plugin tarball does not contain metadata.xml";
816 }
817 return is_metadata_ok;
818 }
819 if (!archive_check(r, "archive read header error", src)) {
820 return false;
821 }
822 std::string path = archive_entry_pathname(entry);
823 bool is_metadata = std::string::npos != path.find("metadata.xml");
824 if (is_metadata) {
825 is_metadata_ok = true;
826 if (metadata_path == "") {
827 continue;
828 } else {
829 archive_entry_set_pathname(entry, metadata_path.c_str());
830 DEBUG_LOG << "Extracted metadata.xml to " << metadata_path;
831 }
832 } else if (!entry_set_install_path(entry, pathmap))
833 continue;
834 if (strlen(archive_entry_pathname(entry)) == 0) {
835 continue;
836 }
837 if (!is_metadata && only_metadata) {
838 continue;
839 }
840 if (!is_metadata) {
841 filelist.append(std::string(archive_entry_pathname(entry)) + "\n");
842 }
843 r = archive_write_header(dest, entry);
844 archive_check(r, "archive write install header error", dest);
845 if (r >= ARCHIVE_OK && archive_entry_size(entry) > 0) {
846 r = copy_data(src, dest);
847 if (!archive_check(r, "archive copy data error", dest)) {
848 return false;
849 }
850 }
851 r = archive_write_finish_entry(dest);
852 if (!archive_check(r, "archive finish write error", dest)) {
853 return false;
854 }
855 }
856 return false; // notreached
857}
858
859/*
860 * Extract tarball into platform-specific user directories.
861 *
862 * The installed tarball has paths like topdir/dest/suffix_path... e. g.
863 * oesenc_pi_ubuntu_10_64/usr/local/share/opencpn/plugins/oesenc_pi/README.
864 * In this path, the topdir part must exist but is discarded. Next parts
865 * being being standard prefixes like /usr/local or /usr are also
866 * discarded. The remaining path (here share) is mapped to a user
867 * directory. On linux, it ends up in ~/.local/share. The suffix
868 * part is then installed as-is into this directory.
869 *
870 * Windows tarballs has dll and binary files in the top directory. They
871 * go to winInstallDir/Program Files. Message catalogs exists under a
872 * share/ toplevel directory, they go in winInstallDir/share. The
873 * plugin data is installed under winInstallDir/plugins/<plugin name>,
874 * and must be looked up by the plugins using GetPluginDataDir(plugin);
875 * Windows requires that PATH is set to include the binary dir and tha
876 * a bindtextdomain call is invoked to define the message catalog paths.
877 *
878 * For linux, the expected destinations are bin, lib and share.
879 *
880 * @param path path to tarball
881 * @param filelist: On return contains a list of files installed.
882 * @param metadata_path: if non-empty, location where to store metadata,
883 * @param only_metadata: If true don't install any files, just extract
884 * metadata.
885 * @return true if tarball contains metadata.xml file, false otherwise.
886 *
887 */
888bool PluginHandler::extractTarball(const std::string path,
889 std::string& filelist,
890 const std::string metadata_path,
891 bool only_metadata) {
892 struct archive* src = archive_read_new();
893 archive_read_support_filter_gzip(src);
894 archive_read_support_format_tar(src);
895 int r = archive_read_open_filename(src, path.c_str(), 10240);
896 if (r != ARCHIVE_OK) {
897 std::ostringstream os;
898 os << "Cannot read installation tarball: " << path;
899 MESSAGE_LOG << os.str();
900 last_error_msg = os.str();
901 return false;
902 }
903 struct archive* dest = archive_write_disk_new();
904 archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
905 bool ok = explodeTarball(src, dest, filelist, metadata_path, only_metadata);
906 archive_read_free(src);
907 archive_write_free(dest);
908 return ok;
909}
910
911PluginHandler* PluginHandler::getInstance() {
912 static PluginHandler* instance = 0;
913 if (!instance) {
914 instance = new (PluginHandler);
915 }
916 return instance;
917}
918
919bool PluginHandler::isPluginWritable(std::string name) {
920 if (isRegularFile(PluginHandler::fileListPath(name).c_str())) {
921 return true;
922 }
923 auto loader = PluginLoader::getInstance();
924 return PlugInIxByName(name, loader->GetPlugInArray()) == -1;
925}
926
927static std::string computeMetadataPath(void) {
928 std::string path = g_BasePlatform->GetPrivateDataDir().ToStdString();
929 path += SEP;
930 path += "ocpn-plugins.xml";
931 if (ocpn::exists(path)) {
932 return path;
933 }
934
935 // If default location for composit plugin metadata is not found,
936 // we look in the plugin cache directory, which will normally contain
937 // he last "master" catalog downloaded
938 path = ocpn::lookup_metadata();
939 if (path != "") {
940 return path;
941 }
942
943 // And if that does not work, use the empty metadata file found in the
944 // distribution "data" directory
945 path = g_BasePlatform->GetSharedDataDir();
946 path += SEP;
947 path += "ocpn-plugins.xml";
948 if (!ocpn::exists(path)) {
949 MESSAGE_LOG << "Non-existing plugins file: " << path;
950 }
951 return path;
952}
953
954static void parseMetadata(const std::string path, CatalogCtx& ctx) {
955 using namespace std;
956
957 MESSAGE_LOG << "PluginHandler: using metadata path: " << path;
958 ctx.depth = 0;
959 if (!ocpn::exists(path)) {
960 MESSAGE_LOG << "Non-existing plugins metadata file: " << path;
961 return;
962 }
963 ifstream ifpath(path);
964 std::string xml((istreambuf_iterator<char>(ifpath)),
965 istreambuf_iterator<char>());
966 ParseCatalog(xml, &ctx);
967}
968
969bool PluginHandler::InstallPlugin(const std::string& path,
970 std::string& filelist,
971 const std::string metadata_path,
972 bool only_metadata) {
973 if (!extractTarball(path, filelist, metadata_path, only_metadata)) {
974 std::ostringstream os;
975 os << "Cannot unpack plugin tarball at : " << path;
976 MESSAGE_LOG << os.str();
977 if (filelist != "") cleanup(filelist, "unknown_name");
978 last_error_msg = os.str();
979 return false;
980 }
981 if (only_metadata) {
982 return true;
983 }
984 struct CatalogCtx ctx;
985 std::ifstream istream(metadata_path);
986 std::stringstream buff;
987 buff << istream.rdbuf();
988
989 auto xml = std::string("<plugins>") + buff.str() + "</plugins>";
990 ParseCatalog(xml, &ctx);
991 auto name = ctx.plugins[0].name;
992 auto version = ctx.plugins[0].version;
993 saveFilelist(filelist, name);
994 saveDirlist(name);
995 saveVersion(name, version);
996
997 return true;
998}
999
1001 if (metadataPath.size() > 0) {
1002 return metadataPath;
1003 }
1004 metadataPath = computeMetadataPath();
1005 DEBUG_LOG << "Using metadata path: " << metadataPath;
1006 return metadataPath;
1007}
1008
1009const std::map<std::string, int> PluginHandler::getCountByTarget() {
1010 auto plugins = getInstalled();
1011 auto a = getAvailable();
1012 plugins.insert(plugins.end(), a.begin(), a.end());
1013 std::map<std::string, int> count_by_target;
1014 for (const auto& p : plugins) {
1015 if (p.target == "") {
1016 continue; // Built-in plugins like dashboard et. al.
1017 }
1018 auto key = p.target + ":" + p.target_version;
1019 if (count_by_target.find(key) == count_by_target.end()) {
1020 count_by_target[key] = 1;
1021 } else {
1022 count_by_target[key] += 1;
1023 }
1024 }
1025 return count_by_target;
1026}
1027
1028std::vector<std::string> PluginHandler::GetImportPaths() {
1029 return glob_dir(importsDir(), "*.xml");
1030}
1031
1032void PluginHandler::cleanupFiles(const std::string& manifestFile,
1033 const std::string& plugname) {
1034 std::ifstream diskfiles(manifestFile);
1035 if (diskfiles.is_open()) {
1036 std::stringstream buffer;
1037 buffer << diskfiles.rdbuf();
1038 PluginHandler::cleanup(buffer.str(), plugname);
1039 }
1040}
1041
1043static void PurgeEmptyDirs(const std::string& root) {
1044 if (!wxFileName::IsDirWritable(root)) return;
1045 if (ocpn::tolower(root).find("opencpn") == std::string::npos) return;
1046 wxDir rootdir(root);
1047 if (!rootdir.IsOpened()) return;
1048 wxString dirname;
1049 bool cont = rootdir.GetFirst(&dirname, "", wxDIR_DIRS);
1050 while (cont) {
1051 PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1052 cont = rootdir.GetNext(&dirname);
1053 }
1054 rootdir.Close();
1055 rootdir.Open(root);
1056 if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1057 wxFileName::Rmdir(rootdir.GetName());
1058 }
1059}
1060
1061void PluginHandler::cleanup(const std::string& filelist,
1062 const std::string& plugname) {
1063 MESSAGE_LOG << "Cleaning up failed install of " << plugname;
1064
1065 std::vector<std::string> paths = LoadLinesFromFile(filelist);
1066 for (const auto& path : paths) {
1067 if (isRegularFile(path.c_str())) {
1068 int r = remove(path.c_str());
1069 if (r != 0) {
1070 MESSAGE_LOG << "Cannot remove file " << path << ": " << strerror(r);
1071 }
1072 }
1073 }
1074 for (const auto& path : paths) PurgeEmptyDirs(path);
1075
1076 std::string path = PluginHandler::fileListPath(plugname);
1077 if (ocpn::exists(path)) remove(path.c_str());
1078
1079 // Best effort tries, failures are non-critical
1080 remove(dirListPath(plugname).c_str());
1081 remove(PluginHandler::versionPath(plugname).c_str());
1082}
1083
1088std::vector<PluginMetadata> PluginHandler::getCompatiblePlugins() {
1090 struct metadata_compare {
1091 bool operator()(const PluginMetadata& lhs,
1092 const PluginMetadata& rhs) const {
1093 return lhs.key() < rhs.key();
1094 }
1095 };
1096
1097 std::vector<PluginMetadata> returnArray;
1098
1099 std::set<PluginMetadata, metadata_compare> unique_plugins;
1100 for (const auto& plugin : getAvailable()) {
1101 unique_plugins.insert(plugin);
1102 }
1103 for (const auto& plugin : unique_plugins) {
1104 if (isCompatible(plugin)) {
1105 returnArray.push_back(plugin);
1106 }
1107 }
1108 return returnArray;
1109}
1110
1111const std::vector<PluginMetadata> PluginHandler::getAvailable() {
1112 using namespace std;
1113 CatalogCtx* ctx;
1114
1115 auto catalogHandler = CatalogHandler::getInstance();
1116
1117 ctx = catalogHandler->GetActiveCatalogContext();
1118 auto status = catalogHandler->GetCatalogStatus();
1119
1120 if (status == CatalogHandler::ServerStatus::OK) {
1121 catalogData.undef = false;
1122 catalogData.version = ctx->version;
1123 catalogData.date = ctx->date;
1124 }
1125 return ctx->plugins;
1126}
1127
1128std::vector<std::string> PluginHandler::GetInstalldataPlugins() {
1129 std::vector<std::string> names;
1130 fs::path dirpath(pluginsInstallDataPath());
1131 for (const auto& entry : fs::directory_iterator(dirpath)) {
1132 const std::string name(entry.path().filename().string());
1133 if (ocpn::endswith(name, ".files"))
1134 names.push_back(ocpn::split(name.c_str(), ".")[0]);
1135 }
1136 return names;
1137}
1138
1139const std::vector<PluginMetadata> PluginHandler::getInstalled() {
1140 using namespace std;
1141 vector<PluginMetadata> plugins;
1142
1143 auto loader = PluginLoader::getInstance();
1144 for (unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1145 const PlugInContainer* p = loader->GetPlugInArray()->Item(i);
1146 PluginMetadata plugin;
1147 auto name = string(p->m_common_name);
1148 // std::transform(name.begin(), name.end(), name.begin(), ::tolower);
1149 plugin.name = name;
1150 std::stringstream ss;
1151 ss << p->m_version_major << "." << p->m_version_minor;
1152 plugin.version = ss.str();
1153 plugin.readonly = !isPluginWritable(plugin.name);
1154 string path = PluginHandler::versionPath(plugin.name);
1155 if (path != "" && wxFileName::IsFileReadable(path)) {
1156 std::ifstream stream;
1157 stream.open(path, ifstream::in);
1158 stream >> plugin.version;
1159 }
1160 plugins.push_back(plugin);
1161 }
1162 return plugins;
1163}
1164
1166 auto loader = PluginLoader::getInstance();
1167 ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1168 if (ix == -1) return; // no such plugin
1169
1170 auto plugins = *loader->GetPlugInArray();
1171 plugins[ix]->m_managed_metadata = pm;
1172}
1173
1174bool PluginHandler::installPlugin(PluginMetadata plugin, std::string path) {
1175 std::string filelist;
1176 if (!extractTarball(path, filelist)) {
1177 std::ostringstream os;
1178 os << "Cannot unpack plugin: " << plugin.name << " at " << path;
1179 MESSAGE_LOG << os.str();
1180 last_error_msg = os.str();
1181 PluginHandler::cleanup(filelist, plugin.name);
1182 return false;
1183 }
1184 saveFilelist(filelist, plugin.name);
1185 saveDirlist(plugin.name);
1186 saveVersion(plugin.name, plugin.version);
1187
1188 return true;
1189}
1190
1192 std::string path = tmpfile_path();
1193 if (path.empty()) {
1194 MESSAGE_LOG << "Cannot create temporary file";
1195 path = "";
1196 return false;
1197 }
1198 std::ofstream stream;
1199 stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1200 DEBUG_LOG << "Downloading: " << plugin.name << std::endl;
1201 auto downloader = Downloader(plugin.tarball_url);
1202 downloader.download(&stream);
1203
1204 return installPlugin(plugin, path);
1205}
1206
1207bool PluginHandler::installPlugin(const std::string& path) {
1208 PluginMetadata metadata;
1209 if (!ExtractMetadata(path, metadata)) {
1210 MESSAGE_LOG << "Cannot extract metadata from tarball";
1211 return false;
1212 }
1213 return installPlugin(metadata, path);
1214}
1215
1216bool PluginHandler::ExtractMetadata(const std::string& path,
1217 PluginMetadata& metadata) {
1218 std::string filelist;
1219 std::string temp_path = tmpfile_path();
1220 if (!extractTarball(path, filelist, temp_path, true)) {
1221 std::ostringstream os;
1222 os << "Cannot unpack plugin " << metadata.name << " tarball at: " << path;
1223 MESSAGE_LOG << os.str();
1224 if (filelist != "") cleanup(filelist, "unknown_name");
1225 last_error_msg = os.str();
1226 return false;
1227 }
1228 if (!isRegularFile(temp_path.c_str())) {
1229 // This could happen if the tarball does not contain the metadata.xml file
1230 // or the metadata.xml file could not be extracted.
1231 return false;
1232 }
1233
1234 struct CatalogCtx ctx;
1235 std::ifstream istream(temp_path);
1236 std::stringstream buff;
1237 buff << istream.rdbuf();
1238 int r = remove(temp_path.c_str());
1239 if (r != 0) {
1240 MESSAGE_LOG << "Cannot remove file " << temp_path << ":" << strerror(r);
1241 }
1242 auto xml = std::string("<plugins>") + buff.str() + "</plugins>";
1243 ParseCatalog(xml, &ctx);
1244 metadata = ctx.plugins[0];
1245 if (metadata.name.empty()) {
1246 MESSAGE_LOG << "Plugin metadata is empty";
1247 }
1248 return !metadata.name.empty();
1249}
1250
1251bool PluginHandler::ClearInstallData(const std::string plugin_name) {
1252 auto ix = PlugInIxByName(plugin_name,
1253 PluginLoader::getInstance()->GetPlugInArray());
1254 if (ix != -1) {
1255 MESSAGE_LOG << "Attempt to remove installation data for loaded plugin";
1256 return false;
1257 }
1258 return DoClearInstallData(plugin_name);
1259}
1260
1261bool PluginHandler::DoClearInstallData(const std::string plugin_name) {
1262 std::string path = PluginHandler::fileListPath(plugin_name);
1263 if (!ocpn::exists(path)) {
1264 MESSAGE_LOG << "Cannot find installation data for " << plugin_name << " ("
1265 << path << ")";
1266 return false;
1267 }
1268 std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1269 for (const auto& p : plug_paths) {
1270 if (isRegularFile(p.c_str())) {
1271 int r = remove(p.c_str());
1272 if (r != 0) {
1273 MESSAGE_LOG << "Cannot remove file " << p << ": " << strerror(r);
1274 }
1275 }
1276 }
1277 for (const auto& p : plug_paths) PurgeEmptyDirs(p);
1278 int r = remove(path.c_str());
1279 if (r != 0) {
1280 MESSAGE_LOG << "Cannot remove file " << path << ": " << strerror(r);
1281 }
1282 // Best effort tries, failures are OK.
1283 remove(dirListPath(plugin_name).c_str());
1284 remove(PluginHandler::versionPath(plugin_name).c_str());
1285 remove(PluginHandler::ImportedMetadataPath(plugin_name).c_str());
1286 return true;
1287}
1288
1289bool PluginHandler::uninstall(const std::string plugin_name) {
1290 using namespace std;
1291
1292 auto loader = PluginLoader::getInstance();
1293 auto ix = PlugInIxByName(plugin_name, loader->GetPlugInArray());
1294 if (ix < 0) {
1295 MESSAGE_LOG << "trying to uninstall non-existing plugin " << plugin_name;
1296 return false;
1297 }
1298 auto pic = loader->GetPlugInArray()->Item(ix);
1299
1300 // Capture library file name before pic dies.
1301 string libfile = pic->m_plugin_file.ToStdString();
1302 loader->UnLoadPlugIn(ix);
1303
1304 bool ok = DoClearInstallData(plugin_name);
1305
1306 // If this is an orphan plugin, there may be no installation record
1307 // So make sure that the library file (.so/.dylib/.dll) is removed
1308 // as a minimum best effort requirement
1309 if (isRegularFile(libfile.c_str())) {
1310 remove(libfile.c_str());
1311 }
1312
1313 return ok;
1314}
1315
1316using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1317
1323static std::string FindMatchingDataDir(std::regex name_re) {
1324 using namespace std;
1325 wxString data_dirs(g_BasePlatform->GetPluginDataPath());
1326 wxStringTokenizer tokens(data_dirs, ";");
1327 while (tokens.HasMoreTokens()) {
1328 auto token = tokens.GetNextToken();
1329 wxFileName path(token);
1330 wxDir dir(path.GetFullPath());
1331 if (dir.IsOpened()) {
1332 wxString filename;
1333 bool cont = dir.GetFirst(&filename, "", wxDIR_DIRS);
1334 while (cont) {
1335 smatch sm;
1336 string s(filename);
1337 if (regex_search(s, sm, name_re)) {
1338 stringstream ss;
1339 for (auto c : sm) ss << c;
1340 return ss.str();
1341 }
1342 cont = dir.GetNext(&filename);
1343 }
1344 }
1345 }
1346 return "";
1347}
1348
1353static std::string FindMatchingLibFile(std::regex name_re) {
1354 using namespace std;
1355 for (const auto& lib : PluginPaths::getInstance()->Libdirs()) {
1356 wxDir dir(lib);
1357 wxString filename;
1358 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
1359 while (cont) {
1360 smatch sm;
1361 string s(filename);
1362 if (regex_search(s, sm, name_re)) {
1363 stringstream ss;
1364 for (auto c : sm) ss << c;
1365 return ss.str();
1366 }
1367 cont = dir.GetNext(&filename);
1368 }
1369 }
1370 return "";
1371}
1372
1374static std::string PluginNameCase(const std::string& name) {
1375 using namespace std;
1376 const string lc_name = ocpn::tolower(name);
1377 regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1378
1379 // Look for matching plugin in list of installed and available.
1380 // This often fails since the lists are not yet available when
1381 // plugins are loaded, but is otherwise a safe bet.
1382 for (const auto& plugin : PluginHandler::getInstance()->getInstalled()) {
1383 if (ocpn::tolower(plugin.name) == lc_name) return plugin.name;
1384 }
1385 for (const auto& plugin : PluginHandler::getInstance()->getAvailable()) {
1386 if (ocpn::tolower(plugin.name) == lc_name) return plugin.name;
1387 }
1388
1389 string match = FindMatchingDataDir(name_re);
1390 if (match != "") return match;
1391
1392 match = FindMatchingLibFile(name_re);
1393 return match != "" ? match : name;
1394}
1395
1397static void LoadPluginMapFile(PluginMap& map, const std::string& path) {
1398 std::ifstream f;
1399 f.open(path);
1400 if (f.fail()) {
1401 MESSAGE_LOG << "Cannot open " << path << ": " << strerror(errno);
1402 return;
1403 }
1404 std::stringstream buf;
1405 buf << f.rdbuf();
1406 auto filelist = ocpn::split(buf.str().c_str(), "\n");
1407 for (auto& file : filelist) {
1408 file = wxFileName(file).GetFullName().ToStdString();
1409 }
1410
1411 // key is basename with removed .files suffix and correct case.
1412 auto key = wxFileName(path).GetFullName().ToStdString();
1413 key = ocpn::split(key.c_str(), ".")[0];
1414 key = PluginNameCase(key);
1415 map[key] = filelist;
1416}
1417
1419static void LoadPluginMap(PluginMap& map) {
1420 map.clear();
1422 if (!root.IsOpened()) return;
1423 wxString filename;
1424 bool cont = root.GetFirst(&filename, "*.files", wxDIR_FILES);
1425 while (cont) {
1426 auto path = root.GetNameWithSep() + filename;
1427 LoadPluginMapFile(map, path.ToStdString());
1428 cont = root.GetNext(&filename);
1429 }
1430}
1431
1432std::string PluginHandler::getPluginByLibrary(const std::string& filename) {
1433 auto basename = wxFileName(filename).GetFullName().ToStdString();
1434 if (files_by_plugin.size() == 0) LoadPluginMap(files_by_plugin);
1435 for (const auto& it : files_by_plugin) {
1436 auto found = std::find(it.second.begin(), it.second.end(), basename);
1437 if (found != it.second.end()) return it.first;
1438 }
1439 return "";
1440}
1441
1443 // Look for the desired file
1444 wxURI uri(wxString(plugin.tarball_url.c_str()));
1445 wxFileName fn(uri.GetPath());
1446 wxString tarballFile = fn.GetFullName();
1447 std::string cacheFile = ocpn::lookup_tarball(tarballFile);
1448
1449#ifdef __WXOSX__
1450 // Depending on the browser settings, MacOS will sometimes automatically
1451 // de-compress the tar.gz file, leaving a simple ".tar" file in its expected
1452 // place. Check for this case, and "do the right thing"
1453 if (cacheFile == "") {
1454 fn.ClearExt();
1455 wxFileName fn1(fn.GetFullName());
1456 if (fn1.GetExt().IsSameAs("tar")) {
1457 tarballFile = fn.GetFullName();
1458 cacheFile = ocpn::lookup_tarball(tarballFile);
1459 }
1460 }
1461#endif
1462
1463 if (cacheFile != "") {
1464 MESSAGE_LOG << "Installing " << tarballFile << " from local cache";
1465 bool bOK = installPlugin(plugin, cacheFile);
1466 if (!bOK) {
1467 evt_download_failed.Notify(cacheFile);
1468 return false;
1469 }
1470 evt_download_ok.Notify(plugin.name + " " + plugin.version);
1471 return true;
1472 }
1473 return false;
1474}
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.
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)
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.