35 #include <unordered_map>
37 #include "wx/wxprec.h"
39 #if (defined(OCPN_GHC_FILESYSTEM) || (defined(__clang_major__) && (__clang_major__ < 15)))
41 #include <ghc/filesystem.hpp>
42 namespace fs = ghc::filesystem;
46 namespace fs = std::filesystem;
56 #include <wx/filename.h>
57 #include <wx/string.h>
58 #include <wx/tokenzr.h>
59 #include <wx/window.h>
63 #include <archive_entry.h>
64 typedef __LA_INT64_T la_int64_t;
66 #if defined(__MINGW32__) && defined(Yield)
72 #include "model/base_platform.h"
73 #include "model/catalog_handler.h"
74 #include "model/catalog_parser.h"
75 #include "model/config_vars.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"
86 static std::string SEP(
"\\");
88 static std::string SEP(
"/");
96 static std::vector<std::string> split(
const std::string& s,
97 const std::string& delim) {
98 std::vector<std::string> result;
99 size_t pos = s.find(delim);
100 if (pos == std::string::npos) {
104 result.push_back(s.substr(0, pos));
105 result.push_back(s.substr(pos + delim.length()));
109 inline std::string basename(
const std::string path) {
110 wxFileName wxFile(path);
111 return wxFile.GetFullName().ToStdString();
114 bool isRegularFile(
const char* path) {
115 wxFileName wxFile(path);
116 return wxFile.FileExists() && !wxFile.IsDir();
119 static void mkdir(
const std::string path) {
120 #if defined(_WIN32) && !defined(__MINGW32__)
121 _mkdir(path.c_str());
122 #elif defined(__MINGW32__)
125 mkdir(path.c_str(), 0755);
129 static std::vector<std::string> glob_dir(
const std::string& dir_path,
130 const std::string& pattern) {
131 std::vector<std::string> found;
134 auto match = dir.GetFirst(&s, pattern);
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);
148 static ssize_t PlugInIxByName(
const std::string& name,
149 const ArrayOfPlugIns* plugins) {
150 const auto lc_name = ocpn::tolower(name);
151 for (
unsigned i = 0; i < plugins->GetCount(); i += 1) {
152 if (lc_name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
159 static std::string pluginsConfigDir() {
161 pluginDataDir += SEP +
"plugins";
162 if (!ocpn::exists(pluginDataDir)) {
163 mkdir(pluginDataDir);
165 pluginDataDir += SEP +
"install_data";
166 if (!ocpn::exists(pluginDataDir)) {
167 mkdir(pluginDataDir);
169 return pluginDataDir;
172 static std::string importsDir() {
173 auto path = pluginsConfigDir();
174 path = path + SEP +
"imports";
175 if (!ocpn::exists(path)) {
181 static std::string dirListPath(std::string name) {
182 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
183 return pluginsConfigDir() + SEP + name +
".dirs";
187 return pluginsConfigDir();
190 static std::vector<std::string> LoadLinesFromFile(
const std::string& path) {
191 std::vector<std::string> lines;
192 std::ifstream src(path);
195 src.getline(line,
sizeof(line));
196 lines.push_back(line);
205 m_abi = metadata.target;
206 m_abi_version = metadata.target_version;
207 m_major_version = ocpn::split(m_abi_version.c_str(),
".")[0];
208 m_name = metadata.name;
209 wxLogDebug(
"Plugin: setting up, name: %s", m_name);
210 wxLogDebug(
"Plugin: init: abi: %s, abi_version: %s, major ver: %s", m_abi,
211 m_abi_version, m_major_version);
213 const std::string& abi()
const {
return m_abi; }
214 const std::string& abi_version()
const {
return m_abi_version; }
215 const std::string& major_version()
const {
return m_major_version; }
216 const std::string& name()
const {
return m_name; }
220 std::string m_abi_version;
221 std::string m_major_version;
229 m_abi = compatOs->name();
230 m_abi_version = compatOs->version();
231 m_major_version = ocpn::split(m_abi_version.c_str(),
".")[0];
232 wxLogDebug(
"Host: init: abi: %s, abi_version: %s, major ver: %s", m_abi,
233 m_abi_version, m_major_version);
236 bool is_version_compatible(
const Plugin& plugin)
const {
237 if (ocpn::startswith(plugin.abi(),
"ubuntu")) {
238 return plugin.abi_version() == m_abi_version;
240 return plugin.major_version() == m_major_version;
245 bool is_debian_plugin_compatible(
const Plugin& plugin)
const {
246 if (!ocpn::startswith(m_abi,
"ubuntu"))
return false;
247 static const std::vector<std::string> compat_versions = {
249 "debian-x86_64;11;ubuntu-gtk3-x86_64;20.04",
250 "debian-wx32-x86_64;11;ubuntu-wx32-x86_64;22.04",
251 "debian-x86_64;12;ubuntu-x86_64;23.04",
252 "debian-x86_64;12;ubuntu-x86_64;23.10",
253 "debian-x86_64;12;ubuntu-x86_64;24.04",
254 "debian-x86_64;sid;ubuntu-x86_64;24.04",
256 "debian-arm64;11;ubuntu-gtk3-arm64;20.04",
257 "debian-wx32-arm64;11;ubuntu-wx32-arm64;22.04",
258 "debian-arm64;12;ubuntu-arm64;23.04",
259 "debian-arm64;12;ubuntu-arm64;23.10",
260 "debian-arm64;12;ubuntu-arm64;24.04",
261 "debian-arm64;sid;ubuntu-arm64;24.04",
263 "debian-armhf;10;ubuntu-armhf;18.04",
264 "debian-gtk3-armhf;10;ubuntu-gtk3-armhf;18.04",
265 "debian-armhf;11;ubuntu-gtk3-armhf;20.04",
266 "debian-wx32-armhf;11;ubuntu-wx32-armhf;22.04",
267 "debian-armhf;12;ubuntu-armhf;23.04",
268 "debian-armhf;12;ubuntu-armhf;23.10",
269 "debian-armhf;12;ubuntu-armhf;24.04",
270 "debian-armhf;sid;ubuntu-armhf;24.04"};
272 if (ocpn::startswith(plugin.abi(),
"debian")) {
273 wxLogDebug(
"Checking for debian plugin on a ubuntu host");
274 const std::string compat_version = plugin.abi() +
";" +
275 plugin.major_version() +
";" + m_abi +
277 for (
auto& cv : compat_versions) {
278 if (compat_version == cv) {
286 const std::string& abi()
const {
return m_abi; }
288 const std::string& abi_version()
const {
return m_abi_version; }
290 const std::string& major_version()
const {
return m_major_version; }
294 std::string m_abi_version;
295 std::string m_major_version;
299 static std::string last_global_os(
"");
302 if (!instance || last_global_os != g_compatOS) {
304 last_global_os = g_compatOS;
309 CompatOs::CompatOs() : _name(PKG_TARGET), _version(PKG_TARGET_VERSION) {
315 std::string compatOS(_name);
316 std::string compatOsVersion(_version);
318 if (getenv(
"OPENCPN_COMPAT_TARGET") != 0) {
319 _name = getenv(
"OPENCPN_COMPAT_TARGET");
320 if (_name.find(
':') != std::string::npos) {
321 auto tokens = ocpn::split(_name.c_str(),
":");
323 _version = tokens[1];
325 }
else if (g_compatOS !=
"") {
328 if (g_compatOsVersion !=
"") {
329 _version = g_compatOsVersion;
331 }
else if (ocpn::startswith(_name,
"ubuntu") && (_version ==
"22.04")) {
332 int wxv = wxMAJOR_VERSION * 10 + wxMINOR_VERSION;
334 auto tokens = ocpn::split(_name.c_str(),
"-");
335 _name = std::string(tokens[0]) + std::string(
"-wx32");
336 if (tokens.size() > 1) _name = _name + std::string(
"-") + tokens[1];
340 _name = ocpn::tolower(_name);
341 _version = ocpn::tolower(_version);
344 PluginHandler::PluginHandler() {}
347 const char* os_version) {
351 if (plugin_api.major == -1) {
352 DEBUG_LOG <<
"Cannot parse API version \"" << metadata.api_version <<
"\"";
355 if (plugin_api < kMinApi || plugin_api > kMaxApi) {
356 DEBUG_LOG <<
"Incompatible API version \"" << metadata.api_version <<
"\"";
360 static const std::vector<std::string> simple_abis = {
361 "msvc",
"msvc-wx32",
"android-armhf",
"android-arm64"};
364 if (plugin.abi() ==
"all") {
365 wxLogDebug(
"Returning true for plugin abi \"all\"");
368 auto compatOS = CompatOs::getInstance();
371 auto found = std::find(simple_abis.begin(), simple_abis.end(), plugin.abi());
372 if (found != simple_abis.end()) {
373 bool ok = plugin.abi() == host.abi();
374 wxLogDebug(
"Returning %s for %s", (ok ?
"ok" :
"fail"), host.abi());
379 if (host.abi() == plugin.abi() && host.is_version_compatible(plugin)) {
381 wxLogDebug(
"Found matching abi version %s", plugin.abi_version());
382 }
else if (host.is_debian_plugin_compatible(plugin)) {
384 wxLogDebug(
"Found Debian version matching Ubuntu host");
387 if (host.abi() ==
"darwin-wx32" && plugin.abi() ==
"darwin-wx32") {
389 auto found = metadata.target_arch.find(detail->osd_arch);
390 if(found != std::string::npos) {
394 DEBUG_LOG <<
"Plugin compatibility check Final: "
395 << (rv ?
"ACCEPTED: " :
"REJECTED: ") << metadata.name;
400 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
401 return pluginsConfigDir() + SEP + name +
".files";
405 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
406 return pluginsConfigDir() + SEP + name +
".version";
411 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
412 return importsDir() + SEP + name +
".xml";
415 typedef std::unordered_map<std::string, std::string> pathmap_t;
421 static pathmap_t getInstallPaths() {
433 static void saveFilelist(std::string filelist, std::string name) {
436 ofstream diskfiles(listpath);
437 if (!diskfiles.is_open()) {
438 wxLogWarning(
"Cannot create installed files list.");
441 diskfiles << filelist;
444 static void saveDirlist(std::string name) {
446 string path = dirListPath(name);
448 if (!dirs.is_open()) {
449 wxLogWarning(
"Cannot create installed files list.");
452 pathmap_t pathmap = getInstallPaths();
453 unordered_map<string, string>::iterator it;
454 for (it = pathmap.begin(); it != pathmap.end(); it++) {
455 dirs << it->first <<
": " << it->second << endl;
459 static void saveVersion(
const std::string& name,
const std::string& version) {
462 ofstream stream(path);
463 if (!stream.is_open()) {
464 wxLogWarning(
"Cannot create version file.");
467 stream << version << endl;
470 static int copy_data(
struct archive* ar,
struct archive* aw) {
477 r = archive_read_data_block(ar, &buff, &size, &offset);
478 if (r == ARCHIVE_EOF)
return (ARCHIVE_OK);
479 if (r < ARCHIVE_OK) {
480 std::string s(archive_error_string(ar));
483 r = archive_write_data_block(aw, buff, size, offset);
484 if (r < ARCHIVE_OK) {
485 std::string s(archive_error_string(aw));
486 wxLogWarning(
"Error copying install data: %s", archive_error_string(aw));
492 static bool win_entry_set_install_path(
struct archive_entry* entry,
493 pathmap_t installPaths) {
496 string path = archive_entry_pathname(entry);
499 int slashes = count(path.begin(), path.end(),
'/');
501 archive_entry_set_pathname(entry,
"");
504 if (ocpn::startswith(path,
"./")) {
505 path = path.substr(1);
509 int slashpos = path.find_first_of(
'/', 1);
511 archive_entry_set_pathname(entry,
"");
515 string prefix = path.substr(0, slashpos);
516 path = path.substr(prefix.size() + 1);
519 if (ocpn::endswith(path,
".dll") || ocpn::endswith(path,
".exe")) {
520 slashpos = path.find_first_of(
'/');
521 path = path.substr(slashpos + 1);
522 path = installPaths[
"bin"] +
"\\" + path;
523 }
else if (ocpn::startswith(path,
"share")) {
525 wxFileName fn(installPaths[
"share"].c_str(),
528 path = fn.GetFullPath().ToStdString() + path;
529 }
else if (ocpn::startswith(path,
"plugins")) {
530 slashpos = path.find_first_of(
'/');
532 path = path.substr(slashpos + 1);
533 path = installPaths[
"share"] +
"\\" + path;
535 }
else if (archive_entry_filetype(entry) == AE_IFREG) {
536 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
537 msg += wxString(path.c_str());
542 s.Replace(
"/",
"\\");
543 s.Replace(
"\\\\",
"\\");
544 archive_entry_set_pathname(entry, s.c_str());
548 static bool flatpak_entry_set_install_path(
struct archive_entry* entry,
549 pathmap_t installPaths) {
552 string path = archive_entry_pathname(entry);
553 int slashes = count(path.begin(), path.end(),
'/');
555 archive_entry_set_pathname(entry,
"");
558 if (ocpn::startswith(path,
"./")) {
559 path = path.substr(2);
561 int slashpos = path.find_first_of(
'/', 1);
562 string prefix = path.substr(0, slashpos);
563 path = path.substr(prefix.size() + 1);
564 slashpos = path.find_first_of(
'/');
565 string location = path.substr(0, slashpos);
566 string suffix = path.substr(slashpos + 1);
567 if (installPaths.find(location) == installPaths.end() &&
568 archive_entry_filetype(entry) == AE_IFREG) {
569 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
570 msg += wxString(path.c_str());
574 string dest = installPaths[location] +
"/" + suffix;
575 archive_entry_set_pathname(entry, dest.c_str());
580 static bool linux_entry_set_install_path(
struct archive_entry* entry,
581 pathmap_t installPaths) {
584 string path = archive_entry_pathname(entry);
585 int slashes = count(path.begin(), path.end(),
'/');
587 archive_entry_set_pathname(entry,
"");
591 int slashpos = path.find_first_of(
'/', 1);
592 if (ocpn::startswith(path,
"./"))
593 slashpos = path.find_first_of(
'/', 2);
595 string prefix = path.substr(0, slashpos);
596 path = path.substr(prefix.size() + 1);
597 if (ocpn::startswith(path,
"usr/")) {
598 path = path.substr(strlen(
"usr/"));
600 if (ocpn::startswith(path,
"local/")) {
601 path = path.substr(strlen(
"local/"));
603 slashpos = path.find_first_of(
'/');
604 string location = path.substr(0, slashpos);
605 string suffix = path.substr(slashpos + 1);
606 if (installPaths.find(location) == installPaths.end() &&
607 archive_entry_filetype(entry) == AE_IFREG) {
608 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
609 msg += wxString(path.c_str());
614 string dest = installPaths[location] +
"/" + suffix;
618 if (ocpn::startswith(location,
"share") &&
619 ocpn::startswith(suffix,
"opencpn/plugins/")) {
620 slashpos = suffix.find_first_of(
"opencpn/plugins/");
621 suffix = suffix.substr(16);
626 if (ocpn::startswith(location,
"lib") &&
627 ocpn::startswith(suffix,
"opencpn/")) {
628 suffix = suffix.substr(8);
631 "/plugins/lib/" + suffix;
635 archive_entry_set_pathname(entry, dest.c_str());
639 static bool apple_entry_set_install_path(
struct archive_entry* entry,
640 pathmap_t installPaths) {
644 "/Library/Application Support/OpenCPN";
646 string path = archive_entry_pathname(entry);
647 if (ocpn::startswith(path,
"./")) path = path.substr(2);
650 size_t slashes = count(path.begin(), path.end(),
'/');
652 archive_entry_set_pathname(entry,
"");
655 auto parts = split(path,
"Contents/Resources");
656 if (parts.size() >= 2) {
657 dest = base +
"/Contents/Resources" + parts[1];
660 parts = split(path,
"Contents/SharedSupport");
661 if (parts.size() >= 2) {
662 dest = base +
"/Contents/SharedSupport" + parts[1];
666 parts = split(path,
"Contents/PlugIns");
667 if (parts.size() >= 2) {
668 dest = base +
"/Contents/PlugIns" + parts[1];
671 if (dest ==
"" && archive_entry_filetype(entry) == AE_IFREG) {
672 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
673 msg += wxString(path.c_str());
677 archive_entry_set_pathname(entry, dest.c_str());
681 static bool android_entry_set_install_path(
struct archive_entry* entry,
682 pathmap_t installPaths) {
685 string path = archive_entry_pathname(entry);
686 int slashes = count(path.begin(), path.end(),
'/');
688 archive_entry_set_pathname(entry,
"");
693 int slashpos = path.find_first_of(
'/', 1);
694 if (ocpn::startswith(path,
"./"))
695 slashpos = path.find_first_of(
'/', 2);
697 string prefix = path.substr(0, slashpos);
698 path = path.substr(prefix.size() + 1);
699 if (ocpn::startswith(path,
"usr/")) {
700 path = path.substr(strlen(
"usr/"));
702 if (ocpn::startswith(path,
"local/")) {
703 path = path.substr(strlen(
"local/"));
705 slashpos = path.find_first_of(
'/');
706 string location = path.substr(0, slashpos);
707 string suffix = path.substr(slashpos + 1);
708 if (installPaths.find(location) == installPaths.end() &&
709 archive_entry_filetype(entry) == AE_IFREG) {
710 wxString msg(_T(
"PluginHandler::Invalid install path on file: "));
711 msg += wxString(path.c_str());
716 if ((location ==
"lib") && ocpn::startswith(suffix,
"opencpn")) {
717 auto parts = split(suffix,
"/");
718 if (parts.size() == 2) suffix = parts[1];
721 if ((location ==
"share") && ocpn::startswith(suffix,
"opencpn")) {
722 auto parts = split(suffix,
"opencpn/");
723 if (parts.size() == 2) suffix = parts[1];
727 string dest = installPaths[location] +
"/" + suffix;
729 archive_entry_set_pathname(entry, dest.c_str());
733 static bool entry_set_install_path(
struct archive_entry* entry,
734 pathmap_t installPaths) {
735 const std::string src = archive_entry_pathname(entry);
737 #ifdef __OCPN__ANDROID__
738 rv = android_entry_set_install_path(entry, installPaths);
740 const auto osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
741 if (g_BasePlatform->isFlatpacked()) {
742 rv = flatpak_entry_set_install_path(entry, installPaths);
743 }
else if (osSystemId & wxOS_UNIX_LINUX) {
744 rv = linux_entry_set_install_path(entry, installPaths);
745 }
else if (osSystemId & wxOS_WINDOWS) {
746 rv = win_entry_set_install_path(entry, installPaths);
747 }
else if (osSystemId & wxOS_MAC) {
748 rv = apple_entry_set_install_path(entry, installPaths);
750 wxLogMessage(
"set_install_path() invoked, unsupported platform %s",
751 wxPlatformInfo::Get().GetOperatingSystemDescription());
755 const std::string dest = archive_entry_pathname(entry);
758 DEBUG_LOG <<
"Installing " << src <<
" into " << dest << std::endl;
764 bool PluginHandler::archive_check(
int r,
const char* msg,
struct archive* a) {
765 if (r < ARCHIVE_OK) {
768 if (archive_error_string(a)) s = s +
": " + archive_error_string(a);
769 wxLogMessage(s.c_str());
772 return r >= ARCHIVE_WARN;
775 bool PluginHandler::explodeTarball(
struct archive* src,
struct archive* dest,
776 std::string& filelist,
777 const std::string& metadata_path,
778 bool only_metadata) {
779 struct archive_entry* entry = 0;
780 pathmap_t pathmap = getInstallPaths();
782 int r = archive_read_next_header(src, &entry);
783 if (r == ARCHIVE_EOF) {
786 if (!archive_check(r,
"archive read header error", src)) {
789 std::string path = archive_entry_pathname(entry);
790 bool is_metadata = std::string::npos != path.find(
"metadata.xml");
792 if (metadata_path ==
"")
continue;
793 archive_entry_set_pathname(entry, metadata_path.c_str());
794 }
else if (!entry_set_install_path(entry, pathmap))
796 if (strlen(archive_entry_pathname(entry)) == 0) {
799 if (!is_metadata && only_metadata) {
803 filelist.append(std::string(archive_entry_pathname(entry)) +
"\n");
805 r = archive_write_header(dest, entry);
806 archive_check(r,
"archive write install header error", dest);
807 if (r >= ARCHIVE_OK && archive_entry_size(entry) > 0) {
808 r = copy_data(src, dest);
809 if (!archive_check(r,
"archive copy data error", dest)) {
813 r = archive_write_finish_entry(dest);
814 if (!archive_check(r,
"archive finish write error", dest)) {
849 bool PluginHandler::extractTarball(
const std::string path,
850 std::string& filelist,
851 const std::string metadata_path,
852 bool only_metadata) {
853 struct archive* src = archive_read_new();
854 archive_read_support_filter_gzip(src);
855 archive_read_support_format_tar(src);
856 int r = archive_read_open_filename(src, path.c_str(), 10240);
857 if (r != ARCHIVE_OK) {
858 std::ostringstream os;
859 os <<
"Cannot read installation tarball: " << path;
860 wxLogWarning(os.str().c_str());
861 last_error_msg = os.str();
864 struct archive* dest = archive_write_disk_new();
865 archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
866 bool ok = explodeTarball(src, dest, filelist, metadata_path, only_metadata);
867 archive_read_free(src);
868 archive_write_free(dest);
884 auto loader = PluginLoader::getInstance();
885 return PlugInIxByName(name, loader->GetPlugInArray()) == -1;
888 static std::string computeMetadataPath(
void) {
891 path +=
"ocpn-plugins.xml";
892 if (ocpn::exists(path)) {
906 path = g_BasePlatform->GetSharedDataDir();
908 path +=
"ocpn-plugins.xml";
909 if (!ocpn::exists(path)) {
910 wxLogWarning(
"Non-existing plugins file: %s", path);
915 static void parseMetadata(
const std::string path,
CatalogCtx& ctx) {
918 wxLogMessage(
"PluginHandler: using metadata path: %s", path);
920 if (!ocpn::exists(path)) {
921 wxLogWarning(
"Non-existing plugins metadata file: %s", path.c_str());
924 ifstream ifpath(path);
925 std::string xml((istreambuf_iterator<char>(ifpath)),
926 istreambuf_iterator<char>());
927 ParseCatalog(xml, &ctx);
930 bool PluginHandler::InstallPlugin(
const std::string& path,
931 std::string& filelist,
932 const std::string metadata_path,
933 bool only_metadata) {
934 if (!extractTarball(path, filelist, metadata_path, only_metadata)) {
935 std::ostringstream os;
936 os <<
"Cannot unpack plugin tarball at : " << path;
937 if (filelist !=
"")
cleanup(filelist,
"unknown_name");
938 last_error_msg = os.str();
945 std::ifstream istream(metadata_path);
946 std::stringstream buff;
947 buff << istream.rdbuf();
949 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
950 ParseCatalog(xml, &ctx);
951 auto name = ctx.plugins[0].name;
952 auto version = ctx.plugins[0].version;
953 saveFilelist(filelist, name);
955 saveVersion(name, version);
961 if (metadataPath.size() > 0) {
964 metadataPath = computeMetadataPath();
965 wxLogDebug(
"Using metadata path: %s", metadataPath.c_str());
972 plugins.insert(plugins.end(), a.begin(), a.end());
973 std::map<std::string, int> count_by_target;
974 for (
const auto& p : plugins) {
975 if (p.target ==
"") {
978 auto key = p.target +
":" + p.target_version;
979 if (count_by_target.find(key) == count_by_target.end()) {
980 count_by_target[key] = 1;
982 count_by_target[key] += 1;
985 return count_by_target;
989 return glob_dir(importsDir(),
"*.xml");
992 void PluginHandler::cleanupFiles(
const std::string& manifestFile,
993 const std::string& plugname) {
994 std::ifstream diskfiles(manifestFile);
995 if (diskfiles.is_open()) {
996 std::stringstream buffer;
997 buffer << diskfiles.rdbuf();
1003 static void PurgeEmptyDirs(
const std::string& root) {
1004 if (!wxFileName::IsDirWritable(root))
return;
1005 if (ocpn::tolower(root).find(
"opencpn") == std::string::npos)
return;
1006 wxDir rootdir(root);
1007 if (!rootdir.IsOpened())
return;
1009 bool cont = rootdir.GetFirst(&dirname,
"", wxDIR_DIRS);
1011 PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1012 cont = rootdir.GetNext(&dirname);
1016 if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1017 wxFileName::Rmdir(rootdir.GetName());
1022 const std::string& plugname) {
1023 wxLogMessage(
"Cleaning up failed install of %s", plugname.c_str());
1025 std::vector<std::string> paths = LoadLinesFromFile(filelist);
1026 for (
const auto& path : paths) {
1027 if (isRegularFile(path.c_str())) {
1028 int r = remove(path.c_str());
1029 if (r != 0) wxLogWarning(
"Cannot remove file %s: %s", path, strerror(r));
1032 for (
const auto& path : paths) PurgeEmptyDirs(path);
1035 if (ocpn::exists(path)) remove(path.c_str());
1038 remove(dirListPath(plugname).c_str());
1048 struct metadata_compare {
1051 return lhs.key() < rhs.key();
1055 std::vector<PluginMetadata> returnArray;
1057 std::set<PluginMetadata, metadata_compare> unique_plugins;
1059 unique_plugins.insert(plugin);
1061 for (
const auto& plugin : unique_plugins) {
1063 returnArray.push_back(plugin);
1070 using namespace std;
1073 auto catalogHandler = CatalogHandler::getInstance();
1075 ctx = catalogHandler->GetActiveCatalogContext();
1076 auto status = catalogHandler->GetCatalogStatus();
1078 if (status == CatalogHandler::ServerStatus::OK) {
1079 catalogData.undef =
false;
1080 catalogData.version = ctx->version;
1081 catalogData.date = ctx->date;
1083 return ctx->plugins;
1087 std::vector<std::string> names;
1089 for (
const auto& entry: fs::directory_iterator(dirpath)) {
1090 const std::string name(entry.path().filename().string());
1091 if (ocpn::endswith(name,
".files"))
1092 names.push_back(ocpn::split(name.c_str(),
".")[0]);
1098 using namespace std;
1099 vector<PluginMetadata> plugins;
1101 auto loader = PluginLoader::getInstance();
1102 for (
unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1108 std::stringstream ss;
1109 ss << p->m_version_major <<
"." << p->m_version_minor;
1110 plugin.version = ss.str();
1113 if (path !=
"" && wxFileName::IsFileReadable(path)) {
1114 std::ifstream stream;
1115 stream.open(path, ifstream::in);
1116 stream >> plugin.version;
1118 plugins.push_back(plugin);
1124 auto loader = PluginLoader::getInstance();
1125 ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1126 if (ix == -1)
return;
1128 auto plugins = *loader->GetPlugInArray();
1129 plugins[ix]->m_managed_metadata = pm;
1133 std::string filelist;
1134 if (!extractTarball(path, filelist)) {
1135 std::ostringstream os;
1136 os <<
"Cannot unpack plugin: " << plugin.name <<
" at " << path;
1137 last_error_msg = os.str();
1141 saveFilelist(filelist, plugin.name);
1142 saveDirlist(plugin.name);
1143 saveVersion(plugin.name, plugin.version);
1152 if (tmpnam(fname) == NULL) {
1153 wxLogWarning(
"Cannot create temporary file");
1157 path = std::string(fname);
1158 std::ofstream stream;
1159 stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1160 DEBUG_LOG <<
"Downloading: " << plugin.name << std::endl;
1161 auto downloader =
Downloader(plugin.tarball_url);
1162 downloader.download(&stream);
1170 MESSAGE_LOG <<
"Cannot extract metadata from tarball";
1178 std::string filelist;
1179 std::string temp_path(tmpnam(0));
1180 if (!extractTarball(path, filelist, temp_path,
true)) {
1181 std::ostringstream os;
1182 os <<
"Cannot unpack plugin tarball at : " << path;
1183 if (filelist !=
"")
cleanup(filelist,
"unknown_name");
1184 last_error_msg = os.str();
1187 if (!isRegularFile(temp_path.c_str()))
1191 std::ifstream istream(temp_path);
1192 std::stringstream buff;
1193 buff << istream.rdbuf();
1194 remove(temp_path.c_str());
1196 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1197 ParseCatalog(xml, &ctx);
1198 metadata = ctx.plugins[0];
1199 return !metadata.name.empty();
1203 auto ix = PlugInIxByName(plugin_name,
1204 PluginLoader::getInstance()->GetPlugInArray());
1206 wxLogWarning(
"Attempt to remove installation data for loaded plugin");
1209 return DoClearInstallData(plugin_name);
1212 bool PluginHandler::DoClearInstallData(
const std::string plugin_name) {
1214 if (!ocpn::exists(path)) {
1215 wxLogWarning(
"Cannot find installation data for %s (%s)",
1216 plugin_name.c_str(), path.c_str());
1219 std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1220 for (
const auto& p : plug_paths) {
1221 if (isRegularFile(p.c_str())) {
1222 int r = remove(p.c_str());
1224 wxLogWarning(
"Cannot remove file %s: %s", p.c_str(), strerror(r));
1228 for (
const auto& p : plug_paths) PurgeEmptyDirs(p);
1229 int r = remove(path.c_str());
1231 wxLogWarning(
"Cannot remove file %s: %s", path.c_str(), strerror(r));
1234 remove(dirListPath(plugin_name).c_str());
1241 using namespace std;
1243 auto loader = PluginLoader::getInstance();
1244 auto ix = PlugInIxByName(plugin_name, loader->GetPlugInArray());
1246 wxLogMessage(
"trying to uninstall non-existing plugin %s",
1247 plugin_name.c_str());
1250 auto pic = loader->GetPlugInArray()->Item(ix);
1253 string libfile = pic->m_plugin_file.ToStdString();
1254 loader->UnLoadPlugIn(ix);
1256 bool ok = DoClearInstallData(plugin_name);
1261 if (isRegularFile(libfile.c_str())) {
1262 remove(libfile.c_str());
1268 using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1275 static std::string FindMatchingDataDir(std::regex name_re) {
1276 using namespace std;
1278 wxStringTokenizer tokens(data_dirs,
";");
1279 while (tokens.HasMoreTokens()) {
1280 auto token = tokens.GetNextToken();
1281 wxFileName path(token);
1282 wxDir dir(path.GetFullPath());
1283 if (dir.IsOpened()) {
1285 bool cont = dir.GetFirst(&filename,
"", wxDIR_DIRS);
1289 if (regex_search(s, sm, name_re)) {
1291 for (
auto c : sm) ss << c;
1294 cont = dir.GetNext(&filename);
1305 static std::string FindMatchingLibFile(std::regex name_re) {
1306 using namespace std;
1310 bool cont = dir.GetFirst(&filename,
"", wxDIR_FILES);
1314 if (regex_search(s, sm, name_re)) {
1316 for (
auto c : sm) ss << c;
1319 cont = dir.GetNext(&filename);
1326 static std::string PluginNameCase(
const std::string& name) {
1327 using namespace std;
1328 const string lc_name = ocpn::tolower(name);
1329 regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1334 for (
const auto& plugin : PluginHandler::getInstance()->
getInstalled()) {
1335 if (ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1337 for (
const auto& plugin : PluginHandler::getInstance()->
getAvailable()) {
1338 if (ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1341 string match = FindMatchingDataDir(name_re);
1342 if (match !=
"")
return match;
1344 match = FindMatchingLibFile(name_re);
1345 return match !=
"" ? match : name;
1349 static void LoadPluginMapFile(PluginMap& map,
const std::string& path) {
1353 wxLogWarning(
"Cannot open %s: %s", path.c_str(), strerror(errno));
1356 std::stringstream buf;
1358 auto filelist = ocpn::split(buf.str().c_str(),
"\n");
1359 for (
auto& file : filelist) {
1360 file = wxFileName(file).GetFullName().ToStdString();
1364 auto key = wxFileName(path).GetFullName().ToStdString();
1365 key = ocpn::split(key.c_str(),
".")[0];
1366 key = PluginNameCase(key);
1367 map[key] = filelist;
1371 static void LoadPluginMap(PluginMap& map) {
1374 if (!root.IsOpened())
return;
1376 bool cont = root.GetFirst(&filename,
"*.files", wxDIR_FILES);
1378 auto path = root.GetNameWithSep() + filename;
1379 LoadPluginMapFile(map, path.ToStdString());
1380 cont = root.GetNext(&filename);
1385 auto basename = wxFileName(filename).GetFullName().ToStdString();
1386 if (files_by_plugin.size() == 0) LoadPluginMap(files_by_plugin);
1387 for (
const auto& it : files_by_plugin) {
1388 auto found = std::find(it.second.begin(), it.second.end(), basename);
1389 if (found != it.second.end())
return it.first;
1396 wxURI uri(wxString(plugin.tarball_url.c_str()));
1397 wxFileName fn(uri.GetPath());
1398 wxString tarballFile = fn.GetFullName();
1405 if (cacheFile ==
"") {
1407 wxFileName fn1(fn.GetFullName());
1408 if (fn1.GetExt().IsSameAs(
"tar")) {
1409 tarballFile = fn.GetFullName();
1415 if (cacheFile !=
"") {
1416 wxLogMessage(
"Installing %s from local cache", tarballFile.c_str());
1419 wxLogWarning(
"Cannot install tarball file %s", cacheFile.c_str());
1420 evt_download_failed.
Notify(cacheFile);
1423 evt_download_ok.
Notify(plugin.name +
" " + plugin.version);
Handle downloading of files from remote urls.
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.
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.
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.