36#include <unordered_map>
45#include <wx/filename.h>
47#include <wx/tokenzr.h>
52#include <archive_entry.h>
54typedef __LA_INT64_T la_int64_t;
56#if defined(__MINGW32__) && defined(Yield)
74#include "std_filesystem.h"
77static std::string SEP(
"\\");
79static std::string SEP(
"/");
87static std::vector<std::string>
split(
const std::string& s,
88 const std::string& delim) {
89 std::vector<std::string> result;
90 size_t pos = s.find(delim);
91 if (pos == std::string::npos) {
95 result.push_back(s.substr(0, pos));
96 result.push_back(s.substr(pos + delim.length()));
100inline std::string basename(
const std::string path) {
101 wxFileName wxFile(path);
102 return wxFile.GetFullName().ToStdString();
105bool isRegularFile(
const char* path) {
106 wxFileName wxFile(path);
107 return wxFile.FileExists() && !wxFile.IsDir();
110static void mkdir(
const std::string path) {
111#if defined(_WIN32) && !defined(__MINGW32__)
112 _mkdir(path.c_str());
113#elif defined(__MINGW32__)
116 mkdir(path.c_str(), 0755);
120static std::vector<std::string> glob_dir(
const std::string& dir_path,
121 const std::string& pattern) {
122 std::vector<std::string> found;
125 auto match = dir.GetFirst(&s, pattern);
127 static const std::string SEP =
128 wxString(wxFileName::GetPathSeparator()).ToStdString();
129 found.push_back(dir_path + SEP + s.ToStdString());
130 match = dir.GetNext(&s);
139static ssize_t PlugInIxByName(
const std::string& name,
140 const ArrayOfPlugIns* plugins) {
142 for (
unsigned i = 0; i < plugins->GetCount(); i += 1) {
143 if (lc_name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
150static std::string pluginsConfigDir() {
152 pluginDataDir += SEP +
"plugins";
154 mkdir(pluginDataDir);
156 pluginDataDir += SEP +
"install_data";
158 mkdir(pluginDataDir);
160 return pluginDataDir;
163static std::string importsDir() {
164 auto path = pluginsConfigDir();
165 path = path + SEP +
"imports";
172static std::string dirListPath(std::string name) {
173 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
174 return pluginsConfigDir() + SEP + name +
".dirs";
178 return pluginsConfigDir();
181static std::vector<std::string> LoadLinesFromFile(
const std::string& path) {
182 std::vector<std::string> lines;
183 std::ifstream src(path);
186 src.getline(line,
sizeof(line));
187 lines.push_back(line);
193static std::string tmpfile_path() {
196 if (tmpnam(fname) == NULL) {
197 MESSAGE_LOG <<
"Cannot create temporary file";
200 return std::string(fname);
204static std::string tmpfile_path() {
206 fs::path tmp_path = fs::temp_directory_path() /
"ocpn-tmpXXXXXX";
208 strncpy(buff, tmp_path.c_str(), PATH_MAX - 1);
209 int fd = mkstemp(buff);
211 MESSAGE_LOG <<
"Cannot create temporary file: " << strerror(errno);
214 assert(close(fd) == 0 &&
"Cannot close file?!");
215 return std::string(buff);
223 m_abi = metadata.target;
224 m_abi_version = metadata.target_version;
225 m_major_version =
ocpn::split(m_abi_version.c_str(),
".")[0];
226 m_name = metadata.name;
227 DEBUG_LOG <<
"Plugin: setting up, name: " << m_name;
228 DEBUG_LOG <<
"Plugin: init: abi: " << m_abi
229 <<
", abi_version: " << m_abi_version
230 <<
", major ver: " << m_major_version;
232 const std::string& abi()
const {
return m_abi; }
233 const std::string& abi_version()
const {
return m_abi_version; }
234 const std::string& major_version()
const {
return m_major_version; }
235 const std::string& name()
const {
return m_name; }
239 std::string m_abi_version;
240 std::string m_major_version;
248 m_abi = compatOs->name();
249 m_abi_version = compatOs->version();
250 m_major_version =
ocpn::split(m_abi_version.c_str(),
".")[0];
251 DEBUG_LOG <<
"Host: init: abi: " << m_abi
252 <<
", abi_version: " << m_abi_version
253 <<
", major ver: " << m_major_version;
256 bool is_version_compatible(
const Plugin& plugin)
const {
258 return plugin.abi_version() == m_abi_version;
260 return plugin.major_version() == m_major_version;
265 bool is_debian_plugin_compatible(
const Plugin& plugin)
const {
267 static const std::vector<std::string> compat_versions = {
269 "debian-x86_64;11;ubuntu-gtk3-x86_64;20.04",
270 "debian-wx32-x86_64;11;ubuntu-wx32-x86_64;22.04",
271 "debian-x86_64;12;ubuntu-x86_64;23.04",
272 "debian-x86_64;12;ubuntu-x86_64;23.10",
273 "debian-x86_64;12;ubuntu-x86_64;24.04",
274 "debian-x86_64;sid;ubuntu-x86_64;24.04",
276 "debian-arm64;11;ubuntu-gtk3-arm64;20.04",
277 "debian-wx32-arm64;11;ubuntu-wx32-arm64;22.04",
278 "debian-arm64;12;ubuntu-arm64;23.04",
279 "debian-arm64;12;ubuntu-arm64;23.10",
280 "debian-arm64;12;ubuntu-arm64;24.04",
281 "debian-arm64;sid;ubuntu-arm64;24.04",
283 "debian-armhf;10;ubuntu-armhf;18.04",
284 "debian-gtk3-armhf;10;ubuntu-gtk3-armhf;18.04",
285 "debian-armhf;11;ubuntu-gtk3-armhf;20.04",
286 "debian-wx32-armhf;11;ubuntu-wx32-armhf;22.04",
287 "debian-armhf;12;ubuntu-armhf;23.04",
288 "debian-armhf;12;ubuntu-armhf;23.10",
289 "debian-armhf;12;ubuntu-armhf;24.04",
290 "debian-armhf;sid;ubuntu-armhf;24.04"};
293 DEBUG_LOG <<
"Checking for debian plugin on a ubuntu host";
294 const std::string compat_version = plugin.abi() +
";" +
295 plugin.major_version() +
";" + m_abi +
297 for (
auto& cv : compat_versions) {
298 if (compat_version == cv) {
306 const std::string& abi()
const {
return m_abi; }
308 const std::string& abi_version()
const {
return m_abi_version; }
310 const std::string& major_version()
const {
return m_major_version; }
314 std::string m_abi_version;
315 std::string m_major_version;
319 static std::string last_global_os(
"");
322 if (!instance || last_global_os != g_compatOS) {
324 last_global_os = g_compatOS;
329CompatOs::CompatOs() : _name(PKG_TARGET), _version(PKG_TARGET_VERSION) {
335 std::string compatOS(_name);
336 std::string compatOsVersion(_version);
338 if (getenv(
"OPENCPN_COMPAT_TARGET") != 0) {
339 _name = getenv(
"OPENCPN_COMPAT_TARGET");
340 if (_name.find(
':') != std::string::npos) {
343 _version = tokens[1];
345 }
else if (g_compatOS !=
"") {
348 if (g_compatOsVersion !=
"") {
349 _version = g_compatOsVersion;
352 int wxv = wxMAJOR_VERSION * 10 + wxMINOR_VERSION;
355 _name = std::string(tokens[0]) + std::string(
"-wx32");
356 if (tokens.size() > 1) _name = _name + std::string(
"-") + tokens[1];
364PluginHandler::PluginHandler() {}
367 const char* os_version) {
371 if (plugin_api.major == -1) {
372 DEBUG_LOG <<
"Cannot parse API version \"" << metadata.api_version <<
"\"";
375 if (plugin_api < kMinApi || plugin_api > kMaxApi) {
376 DEBUG_LOG <<
"Incompatible API version \"" << metadata.api_version <<
"\"";
380 static const std::vector<std::string> simple_abis = {
381 "msvc",
"msvc-wx32",
"android-armhf",
"android-arm64"};
384 if (plugin.abi() ==
"all") {
385 DEBUG_LOG <<
"Returning true for plugin abi \"all\"";
388 auto compatOS = CompatOs::GetInstance();
391 auto found = std::find(simple_abis.begin(), simple_abis.end(), plugin.abi());
392 if (found != simple_abis.end()) {
393 bool ok = plugin.abi() == host.abi();
394 DEBUG_LOG <<
"Returning " << (ok ?
"ok" :
"fail") <<
" for " << host.abi();
398 if (host.abi() == plugin.abi() && host.is_version_compatible(plugin)) {
400 DEBUG_LOG <<
"Found matching abi version " << plugin.abi_version();
401 }
else if (host.is_debian_plugin_compatible(plugin)) {
403 DEBUG_LOG <<
"Found Debian version matching Ubuntu host";
407 if (host.abi() ==
"darwin-wx32" && plugin.abi() ==
"darwin-wx32") {
409 auto found = metadata.target_arch.find(detail->osd_arch);
410 if (found != std::string::npos) {
414 DEBUG_LOG <<
"Plugin compatibility check Final: "
415 << (rv ?
"ACCEPTED: " :
"REJECTED: ") << metadata.name;
420 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
421 return pluginsConfigDir() + SEP + name +
".files";
425 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
426 return pluginsConfigDir() + SEP + name +
".version";
431 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
432 return importsDir() + SEP + name +
".xml";
435typedef std::unordered_map<std::string, std::string> pathmap_t;
441static pathmap_t getInstallPaths() {
453static void saveFilelist(std::string filelist, std::string name) {
456 ofstream diskfiles(listpath);
457 if (!diskfiles.is_open()) {
458 MESSAGE_LOG <<
"Cannot create installed files list.";
461 diskfiles << filelist;
464static void saveDirlist(std::string name) {
466 string path = dirListPath(name);
468 if (!dirs.is_open()) {
469 MESSAGE_LOG <<
"Cannot create installed files list.";
472 pathmap_t pathmap = getInstallPaths();
473 unordered_map<string, string>::iterator it;
474 for (it = pathmap.begin(); it != pathmap.end(); it++) {
475 dirs << it->first <<
": " << it->second << endl;
479static void saveVersion(
const std::string& name,
const std::string& version) {
482 ofstream stream(path);
483 if (!stream.is_open()) {
484 MESSAGE_LOG <<
"Cannot create version file.";
487 stream << version << endl;
490static int copy_data(
struct archive* ar,
struct archive* aw) {
497 r = archive_read_data_block(ar, &buff, &size, &offset);
498 if (r == ARCHIVE_EOF)
return (ARCHIVE_OK);
499 if (r < ARCHIVE_OK) {
500 std::string s(archive_error_string(ar));
503 r = archive_write_data_block(aw, buff, size, offset);
504 if (r < ARCHIVE_OK) {
505 std::string s(archive_error_string(aw));
506 MESSAGE_LOG <<
"Error copying install data: " << archive_error_string(aw);
512static bool win_entry_set_install_path(
struct archive_entry* entry,
513 pathmap_t installPaths) {
516 string path = archive_entry_pathname(entry);
517 bool is_library =
false;
520 int slashes = count(path.begin(), path.end(),
'/');
522 archive_entry_set_pathname(entry,
"");
526 path = path.substr(1);
530 int slashpos = path.find_first_of(
'/', 1);
532 archive_entry_set_pathname(entry,
"");
536 string prefix = path.substr(0, slashpos);
537 path = path.substr(prefix.size() + 1);
541 slashpos = path.find_first_of(
'/');
542 path = path.substr(slashpos + 1);
543 path = installPaths[
"bin"] +
"\\" + path;
547 wxFileName fn(installPaths[
"share"].c_str(),
550 path = fn.GetFullPath().ToStdString() + path;
552 slashpos = path.find_first_of(
'/');
554 path = path.substr(slashpos + 1);
555 path = installPaths[
"share"] +
"\\" + path;
557 }
else if (archive_entry_filetype(entry) == AE_IFREG) {
558 wxString msg(
"PluginHandler::Invalid install path on file: ");
559 msg += wxString(path.c_str());
568 s.Replace(
"/",
"\\");
569 s.Replace(
"\\\\",
"\\");
570 archive_entry_set_pathname(entry, s.c_str());
574static bool flatpak_entry_set_install_path(
struct archive_entry* entry,
575 pathmap_t installPaths) {
578 string path = archive_entry_pathname(entry);
579 int slashes = count(path.begin(), path.end(),
'/');
581 archive_entry_set_pathname(entry,
"");
585 path = path.substr(2);
587 int slashpos = path.find_first_of(
'/', 1);
588 string prefix = path.substr(0, slashpos);
589 path = path.substr(prefix.size() + 1);
590 slashpos = path.find_first_of(
'/');
591 string location = path.substr(0, slashpos);
592 string suffix = path.substr(slashpos + 1);
593 if (installPaths.find(location) == installPaths.end() &&
594 archive_entry_filetype(entry) == AE_IFREG) {
595 wxString msg(
"PluginHandler::Invalid install path on file: ");
596 msg += wxString(path.c_str());
600 string dest = installPaths[location] +
"/" + suffix;
601 archive_entry_set_pathname(entry, dest.c_str());
604 if (dest.find(paths->
UserLibdir()) != std::string::npos) {
612static bool linux_entry_set_install_path(
struct archive_entry* entry,
613 pathmap_t installPaths) {
616 string path = archive_entry_pathname(entry);
617 int slashes = count(path.begin(), path.end(),
'/');
619 archive_entry_set_pathname(entry,
"");
623 int slashpos = path.find_first_of(
'/', 1);
625 slashpos = path.find_first_of(
'/', 2);
627 string prefix = path.substr(0, slashpos);
628 path = path.substr(prefix.size() + 1);
630 path = path.substr(strlen(
"usr/"));
633 path = path.substr(strlen(
"local/"));
635 slashpos = path.find_first_of(
'/');
636 string location = path.substr(0, slashpos);
637 string suffix = path.substr(slashpos + 1);
638 if (installPaths.find(location) == installPaths.end() &&
639 archive_entry_filetype(entry) == AE_IFREG) {
640 wxString msg(
"PluginHandler::Invalid install path on file: ");
641 msg += wxString(path.c_str());
646 bool is_library =
false;
647 string dest = installPaths[location] +
"/" + suffix;
653 slashpos = suffix.find_first_of(
"opencpn/plugins/");
654 suffix = suffix.substr(16);
661 suffix = suffix.substr(8);
663 "/plugins/lib/" + suffix;
674 wxFileName nm(suffix);
678 archive_entry_set_pathname(entry, dest.c_str());
682static bool apple_entry_set_install_path(
struct archive_entry* entry,
683 pathmap_t installPaths) {
687 "/Library/Application Support/OpenCPN";
689 string path = archive_entry_pathname(entry);
691 bool is_library =
false;
694 size_t slashes = count(path.begin(), path.end(),
'/');
696 archive_entry_set_pathname(entry,
"");
699 auto parts =
split(path,
"Contents/Resources");
700 if (parts.size() >= 2) {
701 dest = base +
"/Contents/Resources" + parts[1];
704 parts =
split(path,
"Contents/SharedSupport");
705 if (parts.size() >= 2) {
706 dest = base +
"/Contents/SharedSupport" + parts[1];
710 parts =
split(path,
"Contents/PlugIns");
711 if (parts.size() >= 2) {
712 dest = base +
"/Contents/PlugIns" + parts[1];
716 if (dest ==
"" && archive_entry_filetype(entry) == AE_IFREG) {
717 wxString msg(
"PluginHandler::Invalid install path on file: ");
718 msg += wxString(path.c_str());
722 archive_entry_set_pathname(entry, dest.c_str());
731static bool android_entry_set_install_path(
struct archive_entry* entry,
732 pathmap_t installPaths) {
735 bool is_library =
false;
736 string path = archive_entry_pathname(entry);
737 int slashes = count(path.begin(), path.end(),
'/');
739 archive_entry_set_pathname(entry,
"");
744 int slashpos = path.find_first_of(
'/', 1);
746 slashpos = path.find_first_of(
'/', 2);
748 string prefix = path.substr(0, slashpos);
749 path = path.substr(prefix.size() + 1);
751 path = path.substr(strlen(
"usr/"));
754 path = path.substr(strlen(
"local/"));
756 slashpos = path.find_first_of(
'/');
757 string location = path.substr(0, slashpos);
758 string suffix = path.substr(slashpos + 1);
759 if (installPaths.find(location) == installPaths.end() &&
760 archive_entry_filetype(entry) == AE_IFREG) {
761 wxString msg(
"PluginHandler::Invalid install path on file: ");
762 msg += wxString(path.c_str());
768 auto parts =
split(suffix,
"/");
769 if (parts.size() == 2) suffix = parts[1];
774 auto parts =
split(suffix,
"opencpn/");
775 if (parts.size() == 2) suffix = parts[1];
779 string dest = installPaths[location] +
"/" + suffix;
781 archive_entry_set_pathname(entry, dest.c_str());
783 wxFileName nm(suffix);
789static bool entry_set_install_path(
struct archive_entry* entry,
790 pathmap_t installPaths) {
791 const std::string src = archive_entry_pathname(entry);
794 rv = android_entry_set_install_path(entry, installPaths);
796 const auto osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
798 rv = flatpak_entry_set_install_path(entry, installPaths);
799 }
else if (osSystemId & wxOS_UNIX_LINUX) {
800 rv = linux_entry_set_install_path(entry, installPaths);
801 }
else if (osSystemId & wxOS_WINDOWS) {
802 rv = win_entry_set_install_path(entry, installPaths);
803 }
else if (osSystemId & wxOS_MAC) {
804 rv = apple_entry_set_install_path(entry, installPaths);
806 MESSAGE_LOG <<
"set_install_path() invoked, unsupported platform "
807 << wxPlatformInfo::Get().GetOperatingSystemDescription();
811 const std::string dest = archive_entry_pathname(entry);
814 DEBUG_LOG <<
"Installing " << src <<
" into " << dest << std::endl;
820bool PluginHandler::ArchiveCheck(
int r,
const char* msg,
struct archive* a) {
821 if (r < ARCHIVE_OK) {
824 if (archive_error_string(a)) s = s +
": " + archive_error_string(a);
828 return r >= ARCHIVE_WARN;
831bool PluginHandler::ExplodeTarball(
struct archive* src,
struct archive* dest,
832 std::string& filelist,
833 const std::string& metadata_path,
834 bool only_metadata) {
835 struct archive_entry* entry = 0;
836 pathmap_t pathmap = getInstallPaths();
837 bool is_metadata_ok =
false;
839 int r = archive_read_next_header(src, &entry);
840 if (r == ARCHIVE_EOF) {
841 if (!is_metadata_ok) {
842 MESSAGE_LOG <<
"Plugin tarball does not contain metadata.xml";
844 return is_metadata_ok;
846 if (!ArchiveCheck(r,
"archive read header error", src)) {
849 std::string path = archive_entry_pathname(entry);
850 bool is_metadata = std::string::npos != path.find(
"metadata.xml");
852 is_metadata_ok =
true;
853 if (metadata_path ==
"") {
856 archive_entry_set_pathname(entry, metadata_path.c_str());
857 DEBUG_LOG <<
"Extracted metadata.xml to " << metadata_path;
859 }
else if (!entry_set_install_path(entry, pathmap))
861 if (strlen(archive_entry_pathname(entry)) == 0) {
864 if (!is_metadata && only_metadata) {
868 filelist.append(std::string(archive_entry_pathname(entry)) +
"\n");
870 r = archive_write_header(dest, entry);
871 ArchiveCheck(r,
"archive write install header error", dest);
872 if (r >= ARCHIVE_OK && archive_entry_size(entry) > 0) {
873 r = copy_data(src, dest);
874 if (!ArchiveCheck(r,
"archive copy data error", dest)) {
878 r = archive_write_finish_entry(dest);
879 if (!ArchiveCheck(r,
"archive finish write error", dest)) {
915bool PluginHandler::ExtractTarball(
const std::string path,
916 std::string& filelist,
917 const std::string metadata_path,
918 bool only_metadata) {
919 struct archive* src = archive_read_new();
920 archive_read_support_filter_gzip(src);
921 archive_read_support_format_tar(src);
922 int r = archive_read_open_filename(src, path.c_str(), 10240);
923 if (r != ARCHIVE_OK) {
924 std::ostringstream os;
925 os <<
"Cannot read installation tarball: " << path;
926 MESSAGE_LOG << os.str();
927 last_error_msg = os.str();
930 struct archive* dest = archive_write_disk_new();
931 archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
932 bool ok = ExplodeTarball(src, dest, filelist, metadata_path, only_metadata);
933 archive_read_free(src);
934 archive_write_free(dest);
950 auto loader = PluginLoader::GetInstance();
951 return PlugInIxByName(name, loader->GetPlugInArray()) == -1;
954static std::string computeMetadataPath() {
957 path +=
"ocpn-plugins.xml";
974 path +=
"ocpn-plugins.xml";
976 MESSAGE_LOG <<
"Non-existing plugins file: " << path;
981static void parseMetadata(
const std::string path,
CatalogCtx& ctx) {
984 MESSAGE_LOG <<
"PluginHandler: using metadata path: " << path;
987 MESSAGE_LOG <<
"Non-existing plugins metadata file: " << path;
990 ifstream ifpath(path);
991 std::string xml((istreambuf_iterator<char>(ifpath)),
992 istreambuf_iterator<char>());
993 ParseCatalog(xml, &ctx);
997 std::string& filelist,
998 const std::string metadata_path,
999 bool only_metadata) {
1000 if (!ExtractTarball(path, filelist, metadata_path, only_metadata)) {
1001 std::ostringstream os;
1002 os <<
"Cannot unpack plugin tarball at : " << path;
1003 MESSAGE_LOG << os.str();
1004 if (filelist !=
"")
Cleanup(filelist,
"unknown_name");
1005 last_error_msg = os.str();
1008 if (only_metadata) {
1012 std::ifstream istream(metadata_path);
1013 std::stringstream buff;
1014 buff << istream.rdbuf();
1016 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1017 ParseCatalog(xml, &ctx);
1018 auto name = ctx.plugins[0].name;
1019 auto version = ctx.plugins[0].version;
1020 saveFilelist(filelist, name);
1022 saveVersion(name, version);
1028 if (metadataPath.size() > 0) {
1029 return metadataPath;
1031 metadataPath = computeMetadataPath();
1032 DEBUG_LOG <<
"Using metadata path: " << metadataPath;
1033 return metadataPath;
1039 return path +
"ocpn-plugins.xml";
1045 plugins.insert(plugins.end(), a.begin(), a.end());
1046 std::map<std::string, int> count_by_target;
1047 for (
const auto& p : plugins) {
1048 if (p.target ==
"") {
1051 auto key = p.target +
":" + p.target_version;
1052 if (count_by_target.find(key) == count_by_target.end()) {
1053 count_by_target[key] = 1;
1055 count_by_target[key] += 1;
1058 return count_by_target;
1062 return glob_dir(importsDir(),
"*.xml");
1065void PluginHandler::CleanupFiles(
const std::string& manifestFile,
1066 const std::string& plugname) {
1067 std::ifstream diskfiles(manifestFile);
1068 if (diskfiles.is_open()) {
1069 std::stringstream buffer;
1070 buffer << diskfiles.rdbuf();
1076static void PurgeEmptyDirs(
const std::string& root) {
1077 if (!wxFileName::IsDirWritable(root))
return;
1078 if (
ocpn::tolower(root).find(
"opencpn") == std::string::npos)
return;
1079 wxDir rootdir(root);
1080 if (!rootdir.IsOpened())
return;
1082 bool cont = rootdir.GetFirst(&dirname,
"", wxDIR_DIRS);
1084 PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1085 cont = rootdir.GetNext(&dirname);
1089 if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1090 wxFileName::Rmdir(rootdir.GetName());
1095 const std::string& plugname) {
1096 MESSAGE_LOG <<
"Cleaning up failed install of " << plugname;
1098 std::vector<std::string> paths = LoadLinesFromFile(filelist);
1099 for (
const auto& path : paths) {
1100 if (isRegularFile(path.c_str())) {
1101 int r = remove(path.c_str());
1103 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1107 for (
const auto& path : paths) PurgeEmptyDirs(path);
1113 remove(dirListPath(plugname).c_str());
1123 struct metadata_compare {
1126 return lhs.key() < rhs.key();
1130 std::vector<PluginMetadata> returnArray;
1132 std::set<PluginMetadata, metadata_compare> unique_plugins;
1134 unique_plugins.insert(plugin);
1136 for (
const auto& plugin : unique_plugins) {
1138 returnArray.push_back(plugin);
1145 using namespace std;
1148 auto catalogHandler = CatalogHandler::GetInstance();
1150 ctx = catalogHandler->GetActiveCatalogContext();
1151 auto status = catalogHandler->GetCatalogStatus();
1153 if (status == CatalogHandler::ServerStatus::OK) {
1154 catalogData.undef =
false;
1155 catalogData.version = ctx->version;
1156 catalogData.date = ctx->date;
1158 return ctx->plugins;
1162 std::vector<std::string> names;
1164 for (
const auto& entry : fs::directory_iterator(dirpath)) {
1165 const std::string name(entry.path().filename().string());
1167 names.push_back(
ocpn::split(name.c_str(),
".")[0]);
1173 using namespace std;
1174 vector<PluginMetadata> plugins;
1176 auto loader = PluginLoader::GetInstance();
1177 for (
unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1183 std::stringstream ss;
1184 ss << p->m_version_major <<
"." << p->m_version_minor;
1185 plugin.version = ss.str();
1188 if (path !=
"" && wxFileName::IsFileReadable(path)) {
1189 std::ifstream stream;
1190 stream.open(path, ifstream::in);
1191 stream >> plugin.version;
1193 plugins.push_back(plugin);
1199 auto loader = PluginLoader::GetInstance();
1200 ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1201 if (ix == -1)
return;
1203 auto plugins = *loader->GetPlugInArray();
1204 plugins[ix]->m_managed_metadata = pm;
1208 std::string filelist;
1209 if (!ExtractTarball(path, filelist)) {
1210 std::ostringstream os;
1211 os <<
"Cannot unpack plugin: " << plugin.name <<
" at " << path;
1212 MESSAGE_LOG << os.str();
1213 last_error_msg = os.str();
1217 saveFilelist(filelist, plugin.name);
1218 saveDirlist(plugin.name);
1219 saveVersion(plugin.name, plugin.version);
1224 std::string path = tmpfile_path();
1226 MESSAGE_LOG <<
"Cannot create temporary file";
1230 std::ofstream stream;
1231 stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1232 DEBUG_LOG <<
"Downloading: " << plugin.name << std::endl;
1233 auto downloader =
Downloader(plugin.tarball_url);
1234 downloader.download(&stream);
1242 MESSAGE_LOG <<
"Cannot extract metadata from tarball";
1250 std::string filelist;
1251 std::string temp_path = tmpfile_path();
1252 if (!ExtractTarball(path, filelist, temp_path,
true)) {
1253 std::ostringstream os;
1254 os <<
"Cannot unpack plugin " << metadata.name <<
" tarball at: " << path;
1255 MESSAGE_LOG << os.str();
1256 if (filelist !=
"")
Cleanup(filelist,
"unknown_name");
1257 last_error_msg = os.str();
1260 if (!isRegularFile(temp_path.c_str())) {
1267 std::ifstream istream(temp_path);
1268 std::stringstream buff;
1269 buff << istream.rdbuf();
1270 int r = remove(temp_path.c_str());
1272 MESSAGE_LOG <<
"Cannot remove file " << temp_path <<
":" << strerror(r);
1274 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1275 ParseCatalog(xml, &ctx);
1276 metadata = ctx.plugins[0];
1277 if (metadata.name.empty()) {
1278 MESSAGE_LOG <<
"Plugin metadata is empty";
1280 return !metadata.name.empty();
1284 auto ix = PlugInIxByName(plugin_name,
1285 PluginLoader::GetInstance()->GetPlugInArray());
1287 MESSAGE_LOG <<
"Attempt to remove installation data for loaded plugin";
1290 return DoClearInstallData(plugin_name);
1293bool PluginHandler::DoClearInstallData(
const std::string plugin_name) {
1296 MESSAGE_LOG <<
"Cannot find installation data for " << plugin_name <<
" ("
1300 std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1301 for (
const auto& p : plug_paths) {
1302 if (isRegularFile(p.c_str())) {
1303 int r = remove(p.c_str());
1305 MESSAGE_LOG <<
"Cannot remove file " << p <<
": " << strerror(r);
1309 for (
const auto& p : plug_paths) PurgeEmptyDirs(p);
1310 int r = remove(path.c_str());
1312 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1315 remove(dirListPath(plugin_name).c_str());
1322 using namespace std;
1324 auto loader = PluginLoader::GetInstance();
1325 auto ix = PlugInIxByName(plugin, loader->GetPlugInArray());
1327 MESSAGE_LOG <<
"trying to Uninstall non-existing plugin " << plugin;
1330 auto pic = loader->GetPlugInArray()->Item(ix);
1333 string libfile = pic->m_plugin_file.ToStdString();
1334 loader->UnLoadPlugIn(ix);
1336 bool ok = DoClearInstallData(plugin);
1341 if (isRegularFile(libfile.c_str())) {
1342 remove(libfile.c_str());
1344 loader->MarkAsLoadable(libfile);
1349using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1356static std::string FindMatchingDataDir(std::regex name_re) {
1357 using namespace std;
1359 wxStringTokenizer tokens(data_dirs,
";");
1360 while (tokens.HasMoreTokens()) {
1361 auto token = tokens.GetNextToken();
1362 wxFileName path(token);
1363 wxDir dir(path.GetFullPath());
1364 if (dir.IsOpened()) {
1366 bool cont = dir.GetFirst(&filename,
"", wxDIR_DIRS);
1370 if (regex_search(s, sm, name_re)) {
1372 for (
auto c : sm) ss << c;
1375 cont = dir.GetNext(&filename);
1386static std::string FindMatchingLibFile(std::regex name_re) {
1387 using namespace std;
1388 for (
const auto& lib :
PluginPaths::GetInstance()->Libdirs()) {
1391 bool cont = dir.GetFirst(&filename,
"", wxDIR_FILES);
1395 if (regex_search(s, sm, name_re)) {
1397 for (
auto c : sm) ss << c;
1400 cont = dir.GetNext(&filename);
1407static std::string PluginNameCase(
const std::string& name) {
1408 using namespace std;
1410 regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1416 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1418 for (
const auto& plugin :
PluginHandler::GetInstance()->GetAvailable()) {
1419 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1422 string match = FindMatchingDataDir(name_re);
1423 if (match !=
"")
return match;
1425 match = FindMatchingLibFile(name_re);
1426 return match !=
"" ? match : name;
1430static void LoadPluginMapFile(PluginMap& map,
const std::string& path) {
1434 MESSAGE_LOG <<
"Cannot open " << path <<
": " << strerror(errno);
1437 std::stringstream buf;
1439 auto filelist =
ocpn::split(buf.str().c_str(),
"\n");
1440 for (
auto& file : filelist) {
1441 file = wxFileName(file).GetFullName().ToStdString();
1445 auto key = wxFileName(path).GetFullName().ToStdString();
1447 key = PluginNameCase(key);
1448 map[key] = filelist;
1452static void LoadPluginMap(PluginMap& map) {
1455 if (!root.IsOpened())
return;
1457 bool cont = root.GetFirst(&filename,
"*.files", wxDIR_FILES);
1459 auto path = root.GetNameWithSep() + filename;
1460 LoadPluginMapFile(map, path.ToStdString());
1461 cont = root.GetNext(&filename);
1466 auto basename = wxFileName(filename).GetFullName().ToStdString();
1467 if (FilesByPlugin.size() == 0) LoadPluginMap(FilesByPlugin);
1468 for (
const auto& it : FilesByPlugin) {
1469 auto found = std::find(it.second.begin(), it.second.end(), basename);
1470 if (found != it.second.end())
return it.first;
1477 wxURI uri(wxString(plugin.tarball_url.c_str()));
1478 wxFileName fn(uri.GetPath());
1479 wxString tarballFile = fn.GetFullName();
1486 if (cacheFile ==
"") {
1488 wxFileName fn1(fn.GetFullName());
1489 if (fn1.GetExt().IsSameAs(
"tar")) {
1490 tarballFile = fn.GetFullName();
1496 if (cacheFile !=
"") {
1497 MESSAGE_LOG <<
"Installing " << tarballFile <<
" from local cache";
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.
Internal helper wrapping host OS and version.
Default downloader, usable in a CLI context.
void Notify() override
Notify all listeners, no data supplied.
Host ABI encapsulation and plugin compatibility checks.
Data for a loaded plugin, including dl-loaded library.
wxString m_common_name
A common name string for the plugin.
Handle plugin install from remote repositories and local operations to Uninstall and list plugins.
static std::vector< std::string > GetImportPaths()
List of paths for imported plugins metadata.
bool Uninstall(const std::string plugin)
Uninstall an installed and loaded plugin.
const std::vector< PluginMetadata > GetInstalled()
Return list of all installed and loaded plugins.
const std::map< std::string, int > GetCountByTarget()
Map of available plugin targets -> number of occurences.
static void Cleanup(const std::string &filelist, const std::string &plugname)
Cleanup failed installation attempt using filelist for plugin.
bool IsPluginWritable(std::string name)
Check if given plugin can be installed/updated.
std::vector< PluginMetadata > getCompatiblePlugins()
Return list of available, unique and compatible plugins from configured XML catalog.
static std::string ImportedMetadataPath(std::string name)
Return path to imported metadata for given plugin.
static std::string VersionPath(std::string name)
Return path to file containing version for given plugin.
std::string GetMetadataPath()
Return path to metadata XML file.
std::vector< std::string > GetInstalldataPlugins()
Return list of installed plugins lower case names, not necessarily loaded.
bool InstallPlugin(PluginMetadata plugin)
Download and install a new, not installed plugin.
const std::vector< PluginMetadata > GetAvailable()
Update catalog and return list of available, not installed plugins.
bool ClearInstallData(const std::string plugin_name)
Remove installation data for not loaded plugin.
static bool IsCompatible(const PluginMetadata &metadata, const char *os=PKG_TARGET, const char *os_version=PKG_TARGET_VERSION)
Return true if given plugin is loadable on given os/version.
std::string GetUserMetadataPath()
Return path to user, writable metadata XML file.
static std::string FileListPath(std::string name)
Return path to installation manifest for given plugin.
EventVar evt_download_failed
Notified with plugin name after failed download attempt.
void SetInstalledMetadata(const PluginMetadata &pm)
Set metadata for an installed plugin.
bool ExtractMetadata(const std::string &path, PluginMetadata &metadata)
Extract metadata in given tarball path.
bool InstallPluginFromCache(PluginMetadata plugin)
Install plugin tarball from local cache.
std::string GetPluginByLibrary(const std::string &filename)
Return plugin containing given filename or "" if not found.
static PluginHandler * GetInstance()
Singleton factory.
static std::string PluginsInstallDataPath()
Return base directory for installation data.
EventVar evt_download_ok
Notified with plugin name + version string after successful download from repository.
static void MarkAsLoadable(const std::string &library_path)
Mark a library file (complete path) as loadable i.
Accessors for various paths to install plugins and their data.
std::string UserLibdir()
The single, user-writable directory for installing .dll files.
std::string Homedir() const
home directory, convenience stuff.
std::string UserDatadir()
The single, user-writable common parent for plugin data directories, typically ending in 'plugins'.
static PluginPaths * GetInstance()
Return the singleton instance.
std::string UserBindir()
The single, user-writable directory for installing helper binaries.
Plugin ABI encapsulation.
Global variables reflecting command line options and arguments.
Global variables stored in configuration file.
Handle downloading of files from remote urls.
Enhanced logging interface on top of wx/log.h.
std::string lookup_tarball(const char *uri)
Get path to tarball in cache for given filename.
std::string lookup_metadata(const char *name)
Get metadata path for a given name defaulting to ocpn-plugins.xml)
bool startswith(const std::string &str, const std::string &prefix)
Return true if s starts with given prefix.
std::string tolower(const std::string &input)
Return copy of s with all characters converted to lower case.
std::vector< std::string > split(const char *token_string, const std::string &delimiter)
Return vector of items in s separated by delimiter.
bool endswith(const std::string &str, const std::string &suffix)
Return true if s ends with given suffix.
bool exists(const std::string &name)
void mkdir(const std::string path)
Miscellaneous utilities, many of which string related.
Downloaded plugins cache.
Plugin remote repositories installation and Uninstall/list operations.
Low level code to load plugins from disk, notably the PluginLoader class.
Plugin installation and data paths support.
std::vector< const PlugInData * > GetInstalled()
Return sorted list of all installed plugins.
The result from parsing the xml catalog i.
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.