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;13;ubuntu-x86_64;26.04",
275 "debian-x86_64;sid;ubuntu-x86_64;26.04",
277 "debian-arm64;11;ubuntu-gtk3-arm64;20.04",
278 "debian-wx32-arm64;11;ubuntu-wx32-arm64;22.04",
279 "debian-arm64;12;ubuntu-arm64;23.04",
280 "debian-arm64;12;ubuntu-arm64;23.10",
281 "debian-arm64;12;ubuntu-arm64;24.04",
282 "debian-arm64;13;ubuntu-arm64;26.04",
283 "debian-arm64;sid;ubuntu-arm64;26.04",
285 "debian-armhf;10;ubuntu-armhf;18.04",
286 "debian-gtk3-armhf;10;ubuntu-gtk3-armhf;18.04",
287 "debian-armhf;11;ubuntu-gtk3-armhf;20.04",
288 "debian-wx32-armhf;11;ubuntu-wx32-armhf;22.04",
289 "debian-armhf;12;ubuntu-armhf;23.04",
290 "debian-armhf;12;ubuntu-armhf;23.10",
291 "debian-armhf;12;ubuntu-armhf;24.04",
292 "debian-armhf;sid;ubuntu-armhf;24.04"};
295 DEBUG_LOG <<
"Checking for debian plugin on a ubuntu host";
296 const std::string compat_version = plugin.abi() +
";" +
297 plugin.major_version() +
";" + m_abi +
299 for (
auto& cv : compat_versions) {
300 if (compat_version == cv) {
308 const std::string& abi()
const {
return m_abi; }
310 const std::string& abi_version()
const {
return m_abi_version; }
312 const std::string& major_version()
const {
return m_major_version; }
316 std::string m_abi_version;
317 std::string m_major_version;
321 static std::string last_global_os(
"");
324 if (!instance || last_global_os != g_compatOS) {
326 last_global_os = g_compatOS;
331CompatOs::CompatOs() : _name(PKG_TARGET), _version(PKG_TARGET_VERSION) {
337 std::string compatOS(_name);
338 std::string compatOsVersion(_version);
340 if (getenv(
"OPENCPN_COMPAT_TARGET") != 0) {
341 _name = getenv(
"OPENCPN_COMPAT_TARGET");
342 if (_name.find(
':') != std::string::npos) {
345 _version = tokens[1];
347 }
else if (g_compatOS !=
"") {
350 if (g_compatOsVersion !=
"") {
351 _version = g_compatOsVersion;
354 int wxv = wxMAJOR_VERSION * 10 + wxMINOR_VERSION;
357 _name = std::string(tokens[0]) + std::string(
"-wx32");
358 if (tokens.size() > 1) _name = _name + std::string(
"-") + tokens[1];
366PluginHandler::PluginHandler() {}
369 const char* os_version) {
373 if (plugin_api.major == -1) {
374 DEBUG_LOG <<
"Cannot parse API version \"" << metadata.api_version <<
"\"";
377 if (plugin_api < kMinApi || plugin_api > kMaxApi) {
378 DEBUG_LOG <<
"Incompatible API version \"" << metadata.api_version <<
"\"";
382 static const std::vector<std::string> simple_abis = {
383 "msvc",
"msvc-wx32",
"android-armhf",
"android-arm64"};
386 if (plugin.abi() ==
"all") {
387 DEBUG_LOG <<
"Returning true for plugin abi \"all\"";
390 auto compatOS = CompatOs::GetInstance();
393 auto found = std::find(simple_abis.begin(), simple_abis.end(), plugin.abi());
394 if (found != simple_abis.end()) {
395 bool ok = plugin.abi() == host.abi();
396 DEBUG_LOG <<
"Returning " << (ok ?
"ok" :
"fail") <<
" for " << host.abi();
400 if (host.abi() == plugin.abi() && host.is_version_compatible(plugin)) {
402 DEBUG_LOG <<
"Found matching abi version " << plugin.abi_version();
403 }
else if (host.is_debian_plugin_compatible(plugin)) {
405 DEBUG_LOG <<
"Found Debian version matching Ubuntu host";
409 if (host.abi() ==
"darwin-wx32" && plugin.abi() ==
"darwin-wx32") {
411 auto found = metadata.target_arch.find(detail->osd_arch);
412 if (found != std::string::npos) {
416 DEBUG_LOG <<
"Plugin compatibility check Final: "
417 << (rv ?
"ACCEPTED: " :
"REJECTED: ") << metadata.name;
422 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
423 return pluginsConfigDir() + SEP + name +
".files";
427 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
428 return pluginsConfigDir() + SEP + name +
".version";
433 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
434 return importsDir() + SEP + name +
".xml";
437typedef std::unordered_map<std::string, std::string> pathmap_t;
443static pathmap_t getInstallPaths() {
455static void saveFilelist(std::string filelist, std::string name) {
458 ofstream diskfiles(listpath);
459 if (!diskfiles.is_open()) {
460 MESSAGE_LOG <<
"Cannot create installed files list.";
463 diskfiles << filelist;
466static void saveDirlist(std::string name) {
468 string path = dirListPath(name);
470 if (!dirs.is_open()) {
471 MESSAGE_LOG <<
"Cannot create installed files list.";
474 pathmap_t pathmap = getInstallPaths();
475 unordered_map<string, string>::iterator it;
476 for (it = pathmap.begin(); it != pathmap.end(); it++) {
477 dirs << it->first <<
": " << it->second << endl;
481static void saveVersion(
const std::string& name,
const std::string& version) {
484 ofstream stream(path);
485 if (!stream.is_open()) {
486 MESSAGE_LOG <<
"Cannot create version file.";
489 stream << version << endl;
492static int copy_data(
struct archive* ar,
struct archive* aw) {
499 r = archive_read_data_block(ar, &buff, &size, &offset);
500 if (r == ARCHIVE_EOF)
return (ARCHIVE_OK);
501 if (r < ARCHIVE_OK) {
502 std::string s(archive_error_string(ar));
505 r = archive_write_data_block(aw, buff, size, offset);
506 if (r < ARCHIVE_OK) {
507 std::string s(archive_error_string(aw));
508 MESSAGE_LOG <<
"Error copying install data: " << archive_error_string(aw);
514static bool win_entry_set_install_path(
struct archive_entry* entry,
515 pathmap_t installPaths) {
518 string path = archive_entry_pathname(entry);
519 bool is_library =
false;
522 int slashes = count(path.begin(), path.end(),
'/');
524 archive_entry_set_pathname(entry,
"");
528 path = path.substr(1);
532 int slashpos = path.find_first_of(
'/', 1);
534 archive_entry_set_pathname(entry,
"");
538 string prefix = path.substr(0, slashpos);
539 path = path.substr(prefix.size() + 1);
543 slashpos = path.find_first_of(
'/');
544 path = path.substr(slashpos + 1);
545 path = installPaths[
"bin"] +
"\\" + path;
549 wxFileName fn(installPaths[
"share"].c_str(),
552 path = fn.GetFullPath().ToStdString() + path;
554 slashpos = path.find_first_of(
'/');
556 path = path.substr(slashpos + 1);
557 path = installPaths[
"share"] +
"\\" + path;
559 }
else if (archive_entry_filetype(entry) == AE_IFREG) {
560 wxString msg(
"PluginHandler::Invalid install path on file: ");
561 msg += wxString(path.c_str());
570 s.Replace(
"/",
"\\");
571 s.Replace(
"\\\\",
"\\");
572 archive_entry_set_pathname(entry, s.c_str());
576static bool flatpak_entry_set_install_path(
struct archive_entry* entry,
577 pathmap_t installPaths) {
580 string path = archive_entry_pathname(entry);
581 int slashes = count(path.begin(), path.end(),
'/');
583 archive_entry_set_pathname(entry,
"");
587 path = path.substr(2);
589 int slashpos = path.find_first_of(
'/', 1);
590 string prefix = path.substr(0, slashpos);
591 path = path.substr(prefix.size() + 1);
592 slashpos = path.find_first_of(
'/');
593 string location = path.substr(0, slashpos);
594 string suffix = path.substr(slashpos + 1);
595 if (installPaths.find(location) == installPaths.end() &&
596 archive_entry_filetype(entry) == AE_IFREG) {
597 wxString msg(
"PluginHandler::Invalid install path on file: ");
598 msg += wxString(path.c_str());
602 string dest = installPaths[location] +
"/" + suffix;
603 archive_entry_set_pathname(entry, dest.c_str());
606 if (dest.find(paths->
UserLibdir()) != std::string::npos) {
614static bool linux_entry_set_install_path(
struct archive_entry* entry,
615 pathmap_t installPaths) {
618 string path = archive_entry_pathname(entry);
619 int slashes = count(path.begin(), path.end(),
'/');
621 archive_entry_set_pathname(entry,
"");
625 int slashpos = path.find_first_of(
'/', 1);
627 slashpos = path.find_first_of(
'/', 2);
629 string prefix = path.substr(0, slashpos);
630 path = path.substr(prefix.size() + 1);
632 path = path.substr(strlen(
"usr/"));
635 path = path.substr(strlen(
"local/"));
637 slashpos = path.find_first_of(
'/');
638 string location = path.substr(0, slashpos);
639 string suffix = path.substr(slashpos + 1);
640 if (installPaths.find(location) == installPaths.end() &&
641 archive_entry_filetype(entry) == AE_IFREG) {
642 wxString msg(
"PluginHandler::Invalid install path on file: ");
643 msg += wxString(path.c_str());
648 bool is_library =
false;
649 string dest = installPaths[location] +
"/" + suffix;
655 slashpos = suffix.find_first_of(
"opencpn/plugins/");
656 suffix = suffix.substr(16);
663 suffix = suffix.substr(8);
665 "/plugins/lib/" + suffix;
676 wxFileName nm(suffix);
680 archive_entry_set_pathname(entry, dest.c_str());
684static bool apple_entry_set_install_path(
struct archive_entry* entry,
685 pathmap_t installPaths) {
689 "/Library/Application Support/OpenCPN";
691 string path = archive_entry_pathname(entry);
693 bool is_library =
false;
696 size_t slashes = count(path.begin(), path.end(),
'/');
698 archive_entry_set_pathname(entry,
"");
701 auto parts =
split(path,
"Contents/Resources");
702 if (parts.size() >= 2) {
703 dest = base +
"/Contents/Resources" + parts[1];
706 parts =
split(path,
"Contents/SharedSupport");
707 if (parts.size() >= 2) {
708 dest = base +
"/Contents/SharedSupport" + parts[1];
712 parts =
split(path,
"Contents/PlugIns");
713 if (parts.size() >= 2) {
714 dest = base +
"/Contents/PlugIns" + parts[1];
718 if (dest ==
"" && archive_entry_filetype(entry) == AE_IFREG) {
719 wxString msg(
"PluginHandler::Invalid install path on file: ");
720 msg += wxString(path.c_str());
724 archive_entry_set_pathname(entry, dest.c_str());
733static bool android_entry_set_install_path(
struct archive_entry* entry,
734 pathmap_t installPaths) {
737 bool is_library =
false;
738 string path = archive_entry_pathname(entry);
739 int slashes = count(path.begin(), path.end(),
'/');
741 archive_entry_set_pathname(entry,
"");
746 int slashpos = path.find_first_of(
'/', 1);
748 slashpos = path.find_first_of(
'/', 2);
750 string prefix = path.substr(0, slashpos);
751 path = path.substr(prefix.size() + 1);
753 path = path.substr(strlen(
"usr/"));
756 path = path.substr(strlen(
"local/"));
758 slashpos = path.find_first_of(
'/');
759 string location = path.substr(0, slashpos);
760 string suffix = path.substr(slashpos + 1);
761 if (installPaths.find(location) == installPaths.end() &&
762 archive_entry_filetype(entry) == AE_IFREG) {
763 wxString msg(
"PluginHandler::Invalid install path on file: ");
764 msg += wxString(path.c_str());
770 auto parts =
split(suffix,
"/");
771 if (parts.size() == 2) suffix = parts[1];
776 auto parts =
split(suffix,
"opencpn/");
777 if (parts.size() == 2) suffix = parts[1];
781 string dest = installPaths[location] +
"/" + suffix;
783 archive_entry_set_pathname(entry, dest.c_str());
785 wxFileName nm(suffix);
791static bool entry_set_install_path(
struct archive_entry* entry,
792 pathmap_t installPaths) {
793 const std::string src = archive_entry_pathname(entry);
796 rv = android_entry_set_install_path(entry, installPaths);
798 const auto osSystemId = wxPlatformInfo::Get().GetOperatingSystemId();
800 rv = flatpak_entry_set_install_path(entry, installPaths);
801 }
else if (osSystemId & wxOS_UNIX_LINUX) {
802 rv = linux_entry_set_install_path(entry, installPaths);
803 }
else if (osSystemId & wxOS_WINDOWS) {
804 rv = win_entry_set_install_path(entry, installPaths);
805 }
else if (osSystemId & wxOS_MAC) {
806 rv = apple_entry_set_install_path(entry, installPaths);
808 MESSAGE_LOG <<
"set_install_path() invoked, unsupported platform "
809 << wxPlatformInfo::Get().GetOperatingSystemDescription();
813 const std::string dest = archive_entry_pathname(entry);
816 DEBUG_LOG <<
"Installing " << src <<
" into " << dest << std::endl;
822bool PluginHandler::ArchiveCheck(
int r,
const char* msg,
struct archive* a) {
823 if (r < ARCHIVE_OK) {
826 if (archive_error_string(a)) s = s +
": " + archive_error_string(a);
830 return r >= ARCHIVE_WARN;
833bool PluginHandler::ExplodeTarball(
struct archive* src,
struct archive* dest,
834 std::string& filelist,
835 const std::string& metadata_path,
836 bool only_metadata) {
837 struct archive_entry* entry = 0;
838 pathmap_t pathmap = getInstallPaths();
839 bool is_metadata_ok =
false;
841 int r = archive_read_next_header(src, &entry);
842 if (r == ARCHIVE_EOF) {
843 if (!is_metadata_ok) {
844 MESSAGE_LOG <<
"Plugin tarball does not contain metadata.xml";
846 return is_metadata_ok;
848 if (!ArchiveCheck(r,
"archive read header error", src)) {
851 std::string path = archive_entry_pathname(entry);
852 bool is_metadata = std::string::npos != path.find(
"metadata.xml");
854 is_metadata_ok =
true;
855 if (metadata_path ==
"") {
858 archive_entry_set_pathname(entry, metadata_path.c_str());
859 DEBUG_LOG <<
"Extracted metadata.xml to " << metadata_path;
861 }
else if (!entry_set_install_path(entry, pathmap))
863 if (strlen(archive_entry_pathname(entry)) == 0) {
866 if (!is_metadata && only_metadata) {
870 filelist.append(std::string(archive_entry_pathname(entry)) +
"\n");
872 r = archive_write_header(dest, entry);
873 ArchiveCheck(r,
"archive write install header error", dest);
874 if (r >= ARCHIVE_OK && archive_entry_size(entry) > 0) {
875 r = copy_data(src, dest);
876 if (!ArchiveCheck(r,
"archive copy data error", dest)) {
880 r = archive_write_finish_entry(dest);
881 if (!ArchiveCheck(r,
"archive finish write error", dest)) {
917bool PluginHandler::ExtractTarball(
const std::string path,
918 std::string& filelist,
919 const std::string metadata_path,
920 bool only_metadata) {
921 struct archive* src = archive_read_new();
922 archive_read_support_filter_gzip(src);
923 archive_read_support_format_tar(src);
924 int r = archive_read_open_filename(src, path.c_str(), 10240);
925 if (r != ARCHIVE_OK) {
926 std::ostringstream os;
927 os <<
"Cannot read installation tarball: " << path;
928 MESSAGE_LOG << os.str();
929 last_error_msg = os.str();
932 struct archive* dest = archive_write_disk_new();
933 archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
934 bool ok = ExplodeTarball(src, dest, filelist, metadata_path, only_metadata);
935 archive_read_free(src);
936 archive_write_free(dest);
952 auto loader = PluginLoader::GetInstance();
953 return PlugInIxByName(name, loader->GetPlugInArray()) == -1;
956static std::string computeMetadataPath() {
959 path +=
"ocpn-plugins.xml";
976 path +=
"ocpn-plugins.xml";
978 MESSAGE_LOG <<
"Non-existing plugins file: " << path;
983static void parseMetadata(
const std::string path,
CatalogCtx& ctx) {
986 MESSAGE_LOG <<
"PluginHandler: using metadata path: " << path;
989 MESSAGE_LOG <<
"Non-existing plugins metadata file: " << path;
992 ifstream ifpath(path);
993 std::string xml((istreambuf_iterator<char>(ifpath)),
994 istreambuf_iterator<char>());
995 ParseCatalog(xml, &ctx);
999 std::string& filelist,
1000 const std::string metadata_path,
1001 bool only_metadata) {
1002 if (!ExtractTarball(path, filelist, metadata_path, only_metadata)) {
1003 std::ostringstream os;
1004 os <<
"Cannot unpack plugin tarball at : " << path;
1005 MESSAGE_LOG << os.str();
1006 if (filelist !=
"")
Cleanup(filelist,
"unknown_name");
1007 last_error_msg = os.str();
1010 if (only_metadata) {
1014 std::ifstream istream(metadata_path);
1015 std::stringstream buff;
1016 buff << istream.rdbuf();
1018 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1019 ParseCatalog(xml, &ctx);
1020 auto name = ctx.plugins[0].name;
1021 auto version = ctx.plugins[0].version;
1022 saveFilelist(filelist, name);
1024 saveVersion(name, version);
1030 if (metadataPath.size() > 0) {
1031 return metadataPath;
1033 metadataPath = computeMetadataPath();
1034 DEBUG_LOG <<
"Using metadata path: " << metadataPath;
1035 return metadataPath;
1041 return path +
"ocpn-plugins.xml";
1047 plugins.insert(plugins.end(), a.begin(), a.end());
1048 std::map<std::string, int> count_by_target;
1049 for (
const auto& p : plugins) {
1050 if (p.target ==
"") {
1053 auto key = p.target +
":" + p.target_version;
1054 if (count_by_target.find(key) == count_by_target.end()) {
1055 count_by_target[key] = 1;
1057 count_by_target[key] += 1;
1060 return count_by_target;
1064 return glob_dir(importsDir(),
"*.xml");
1067void PluginHandler::CleanupFiles(
const std::string& manifestFile,
1068 const std::string& plugname) {
1069 std::ifstream diskfiles(manifestFile);
1070 if (diskfiles.is_open()) {
1071 std::stringstream buffer;
1072 buffer << diskfiles.rdbuf();
1078static void PurgeEmptyDirs(
const std::string& root) {
1079 if (!wxFileName::IsDirWritable(root))
return;
1080 if (
ocpn::tolower(root).find(
"opencpn") == std::string::npos)
return;
1081 wxDir rootdir(root);
1082 if (!rootdir.IsOpened())
return;
1084 bool cont = rootdir.GetFirst(&dirname,
"", wxDIR_DIRS);
1086 PurgeEmptyDirs((rootdir.GetNameWithSep() + dirname).ToStdString());
1087 cont = rootdir.GetNext(&dirname);
1091 if (!(rootdir.HasFiles() || rootdir.HasSubDirs())) {
1092 wxFileName::Rmdir(rootdir.GetName());
1097 const std::string& plugname) {
1098 MESSAGE_LOG <<
"Cleaning up failed install of " << plugname;
1100 std::vector<std::string> paths = LoadLinesFromFile(filelist);
1101 for (
const auto& path : paths) {
1102 if (isRegularFile(path.c_str())) {
1103 int r = remove(path.c_str());
1105 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1109 for (
const auto& path : paths) PurgeEmptyDirs(path);
1115 remove(dirListPath(plugname).c_str());
1125 struct metadata_compare {
1128 return lhs.key() < rhs.key();
1132 std::vector<PluginMetadata> returnArray;
1134 std::set<PluginMetadata, metadata_compare> unique_plugins;
1136 unique_plugins.insert(plugin);
1138 for (
const auto& plugin : unique_plugins) {
1140 returnArray.push_back(plugin);
1147 using namespace std;
1150 auto catalogHandler = CatalogHandler::GetInstance();
1152 ctx = catalogHandler->GetActiveCatalogContext();
1153 auto status = catalogHandler->GetCatalogStatus();
1155 if (status == CatalogHandler::ServerStatus::OK) {
1156 catalogData.undef =
false;
1157 catalogData.version = ctx->version;
1158 catalogData.date = ctx->date;
1160 return ctx->plugins;
1164 std::vector<std::string> names;
1166 for (
const auto& entry : fs::directory_iterator(dirpath)) {
1167 const std::string name(entry.path().filename().string());
1169 names.push_back(
ocpn::split(name.c_str(),
".")[0]);
1175 using namespace std;
1176 vector<PluginMetadata> plugins;
1178 auto loader = PluginLoader::GetInstance();
1179 for (
unsigned int i = 0; i < loader->GetPlugInArray()->GetCount(); i += 1) {
1185 std::stringstream ss;
1186 ss << p->m_version_major <<
"." << p->m_version_minor;
1187 plugin.version = ss.str();
1190 if (path !=
"" && wxFileName::IsFileReadable(path)) {
1191 std::ifstream stream;
1192 stream.open(path, ifstream::in);
1193 stream >> plugin.version;
1195 plugins.push_back(plugin);
1201 auto loader = PluginLoader::GetInstance();
1202 ssize_t ix = PlugInIxByName(pm.name, loader->GetPlugInArray());
1203 if (ix == -1)
return;
1205 auto plugins = *loader->GetPlugInArray();
1206 plugins[ix]->m_managed_metadata = pm;
1210 std::string filelist;
1211 if (!ExtractTarball(path, filelist)) {
1212 std::ostringstream os;
1213 os <<
"Cannot unpack plugin: " << plugin.name <<
" at " << path;
1214 MESSAGE_LOG << os.str();
1215 last_error_msg = os.str();
1219 saveFilelist(filelist, plugin.name);
1220 saveDirlist(plugin.name);
1221 saveVersion(plugin.name, plugin.version);
1226 std::string path = tmpfile_path();
1228 MESSAGE_LOG <<
"Cannot create temporary file";
1232 std::ofstream stream;
1233 stream.open(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
1234 DEBUG_LOG <<
"Downloading: " << plugin.name << std::endl;
1235 auto downloader =
Downloader(plugin.tarball_url);
1236 downloader.download(&stream);
1244 MESSAGE_LOG <<
"Cannot extract metadata from tarball";
1252 std::string filelist;
1253 std::string temp_path = tmpfile_path();
1254 if (!ExtractTarball(path, filelist, temp_path,
true)) {
1255 std::ostringstream os;
1256 os <<
"Cannot unpack plugin " << metadata.name <<
" tarball at: " << path;
1257 MESSAGE_LOG << os.str();
1258 if (filelist !=
"")
Cleanup(filelist,
"unknown_name");
1259 last_error_msg = os.str();
1262 if (!isRegularFile(temp_path.c_str())) {
1269 std::ifstream istream(temp_path);
1270 std::stringstream buff;
1271 buff << istream.rdbuf();
1272 int r = remove(temp_path.c_str());
1274 MESSAGE_LOG <<
"Cannot remove file " << temp_path <<
":" << strerror(r);
1276 auto xml = std::string(
"<plugins>") + buff.str() +
"</plugins>";
1277 ParseCatalog(xml, &ctx);
1278 metadata = ctx.plugins[0];
1279 if (metadata.name.empty()) {
1280 MESSAGE_LOG <<
"Plugin metadata is empty";
1282 return !metadata.name.empty();
1286 auto ix = PlugInIxByName(plugin_name,
1287 PluginLoader::GetInstance()->GetPlugInArray());
1289 MESSAGE_LOG <<
"Attempt to remove installation data for loaded plugin";
1292 return DoClearInstallData(plugin_name);
1295bool PluginHandler::DoClearInstallData(
const std::string plugin_name) {
1298 MESSAGE_LOG <<
"Cannot find installation data for " << plugin_name <<
" ("
1302 std::vector<std::string> plug_paths = LoadLinesFromFile(path);
1303 for (
const auto& p : plug_paths) {
1304 if (isRegularFile(p.c_str())) {
1305 int r = remove(p.c_str());
1307 MESSAGE_LOG <<
"Cannot remove file " << p <<
": " << strerror(r);
1311 for (
const auto& p : plug_paths) PurgeEmptyDirs(p);
1312 int r = remove(path.c_str());
1314 MESSAGE_LOG <<
"Cannot remove file " << path <<
": " << strerror(r);
1317 remove(dirListPath(plugin_name).c_str());
1324 using namespace std;
1326 auto loader = PluginLoader::GetInstance();
1327 auto ix = PlugInIxByName(plugin, loader->GetPlugInArray());
1329 MESSAGE_LOG <<
"trying to Uninstall non-existing plugin " << plugin;
1332 auto pic = loader->GetPlugInArray()->Item(ix);
1335 string libfile = pic->m_plugin_file.ToStdString();
1336 loader->UnLoadPlugIn(ix);
1338 bool ok = DoClearInstallData(plugin);
1343 if (isRegularFile(libfile.c_str())) {
1344 remove(libfile.c_str());
1346 loader->MarkAsLoadable(libfile);
1351using PluginMap = std::unordered_map<std::string, std::vector<std::string>>;
1358static std::string FindMatchingDataDir(std::regex name_re) {
1359 using namespace std;
1361 wxStringTokenizer tokens(data_dirs,
";");
1362 while (tokens.HasMoreTokens()) {
1363 auto token = tokens.GetNextToken();
1364 wxFileName path(token);
1365 wxDir dir(path.GetFullPath());
1366 if (dir.IsOpened()) {
1368 bool cont = dir.GetFirst(&filename,
"", wxDIR_DIRS);
1372 if (regex_search(s, sm, name_re)) {
1374 for (
auto c : sm) ss << c;
1377 cont = dir.GetNext(&filename);
1388static std::string FindMatchingLibFile(std::regex name_re) {
1389 using namespace std;
1390 for (
const auto& lib :
PluginPaths::GetInstance()->Libdirs()) {
1393 bool cont = dir.GetFirst(&filename,
"", wxDIR_FILES);
1397 if (regex_search(s, sm, name_re)) {
1399 for (
auto c : sm) ss << c;
1402 cont = dir.GetNext(&filename);
1409static std::string PluginNameCase(
const std::string& name) {
1410 using namespace std;
1412 regex name_re(lc_name, regex_constants::icase | regex_constants::ECMAScript);
1418 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1420 for (
const auto& plugin :
PluginHandler::GetInstance()->GetAvailable()) {
1421 if (
ocpn::tolower(plugin.name) == lc_name)
return plugin.name;
1424 string match = FindMatchingDataDir(name_re);
1425 if (match !=
"")
return match;
1427 match = FindMatchingLibFile(name_re);
1428 return match !=
"" ? match : name;
1432static void LoadPluginMapFile(PluginMap& map,
const std::string& path) {
1436 MESSAGE_LOG <<
"Cannot open " << path <<
": " << strerror(errno);
1439 std::stringstream buf;
1441 auto filelist =
ocpn::split(buf.str().c_str(),
"\n");
1442 for (
auto& file : filelist) {
1443 file = wxFileName(file).GetFullName().ToStdString();
1447 auto key = wxFileName(path).GetFullName().ToStdString();
1449 key = PluginNameCase(key);
1450 map[key] = filelist;
1454static void LoadPluginMap(PluginMap& map) {
1457 if (!root.IsOpened())
return;
1459 bool cont = root.GetFirst(&filename,
"*.files", wxDIR_FILES);
1461 auto path = root.GetNameWithSep() + filename;
1462 LoadPluginMapFile(map, path.ToStdString());
1463 cont = root.GetNext(&filename);
1468 auto basename = wxFileName(filename).GetFullName().ToStdString();
1469 if (FilesByPlugin.size() == 0) LoadPluginMap(FilesByPlugin);
1470 for (
const auto& it : FilesByPlugin) {
1471 auto found = std::find(it.second.begin(), it.second.end(), basename);
1472 if (found != it.second.end())
return it.first;
1479 wxURI uri(wxString(plugin.tarball_url.c_str()));
1480 wxFileName fn(uri.GetPath());
1481 wxString tarballFile = fn.GetFullName();
1488 if (cacheFile ==
"") {
1490 wxFileName fn1(fn.GetFullName());
1491 if (fn1.GetExt().IsSameAs(
"tar")) {
1492 tarballFile = fn.GetFullName();
1498 if (cacheFile !=
"") {
1499 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.