38#include <wx/progdlg.h>
40#include <wx/statline.h>
43#include "catalog_mgr.h"
44#include "download_mgr.h"
45#include "downloader.h"
46#include "OCPNPlatform.h"
48#include "plugin_handler.h"
49#include "plugin_cache.h"
50#include "pluginmanager.h"
51#include "semantic_vers.h"
63wxDEFINE_EVENT(EVT_PLUGINS_RELOAD, wxCommandEvent);
65namespace download_mgr {
70static bool checksum_ok(
const std::string& path,
72 wxLogDebug(
"Checksum test on %s", metadata.name.c_str());
73 if (metadata.checksum ==
"") {
74 wxLogDebug(
"No metadata checksum, aborting check,");
77 const size_t pos = metadata.checksum.find(
':');
78 std::string checksum(metadata.checksum);
79 if (pos == std::string::npos) {
80 checksum = std::string(
"sha256:") + checksum;
82 std::ifstream f(path, std::ios::binary);
83 picosha2::hash256_one_by_one hasher;
86 f.read(buff,
sizeof(buff));
87 const std::string
block(buff, f.gcount());
91 std::string tarball_hash =
92 std::string(
"sha256:") + picosha2::get_hash_hex_string(hasher);
94 if (tarball_hash == checksum) {
95 wxLogDebug(
"Checksum ok: %s", tarball_hash.c_str());
98 wxLogMessage(
"Checksum fail on %s, tarball: %s, metadata: %s",
99 metadata.name.c_str(), tarball_hash.c_str(), checksum.c_str());
107static ssize_t PlugInIxByName(
const std::string name, ArrayOfPlugIns* plugins) {
108 for (
unsigned i = 0; i < plugins->GetCount(); i += 1) {
109 if (name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
118 ArrayOfPlugIns* plugins) {
119 auto ix = PlugInIxByName(name, plugins);
120 return ix == -1 ? 0 : plugins->Item(ix);
124static void LoadPNGIcon(
const char* path,
int size, wxBitmap& bitmap) {
125 wxPNGHandler handler;
126 if (!wxImage::FindHandler(handler.GetName())) {
127 wxImage::AddHandler(
new wxPNGHandler());
129 auto img =
new wxImage();
130 bool ok = img->LoadFile(path, wxBITMAP_TYPE_PNG);
135 img->Rescale(size, size);
136 bitmap = wxBitmap(*img);
148 : wxPanel(parent), m_plugin_name(plugin_name) {
149 auto size = GetClientSize();
150 auto minsize = GetTextExtent(
"OpenCPN");
151 SetMinClientSize(wxSize(minsize.GetWidth(), size.GetHeight()));
153 Bind(wxEVT_PAINT, &PluginIconPanel::OnPaint,
this);
156 void OnPaint(wxPaintEvent& event) {
157 auto size = GetClientSize();
158 int minsize = wxMin(size.GetHeight(), size.GetWidth());
159 auto offset = minsize / 10;
161 LoadIcon(m_plugin_name.c_str(), m_bitmap, 2 * minsize / 3);
163 if (!m_bitmap.IsOk()) {
164 wxLogMessage(
"AddPluginPanel: bitmap is not OK!");
167 dc.DrawBitmap(m_bitmap, offset, offset,
true);
172 const std::string m_plugin_name;
174 void LoadIcon(
const char* plugin_name, wxBitmap& bitmap,
int size = 32) {
175 wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
176 path.AppendDir(
"uidata");
177 path.AppendDir(
"plugins");
180 if (path.IsFileReadable()) {
181 bitmap = LoadSVG(path.GetFullPath(), size, size);
186 if (path.IsFileReadable()) {
187 LoadPNGIcon(path.GetFullPath(), size, bitmap);
192 auto style = g_StyleManager->GetCurrentStyle();
193 bitmap = wxBitmap(style->GetIcon(_T(
"default_pi")));
202 : wxPanel(parent), m_metadata(metadata), m_remove(
false) {
204 PlugInByName(metadata.name,
205 PluginLoader::getInstance()->GetPlugInArray());
206 std::string label(_(
"Install"));
208 label = getUpdateLabel(found, metadata);
211 auto button =
new wxButton(
this, wxID_ANY, label);
212 auto pluginHandler = PluginHandler::getInstance();
213 button->Enable(pluginHandler->isPluginWritable(metadata.name));
214 auto box =
new wxBoxSizer(wxHORIZONTAL);
217 Bind(wxEVT_COMMAND_BUTTON_CLICKED, &InstallButton::OnClick,
this);
220 void OnClick(wxCommandEvent& event) {
222 if (m_remove && path !=
"") {
223 wxLogMessage(
"Uninstalling %s", m_metadata.name.c_str());
224 PluginHandler::getInstance()->
uninstall(m_metadata.name);
226 wxLogMessage(
"Installing %s", m_metadata.name.c_str());
228 auto pluginHandler = PluginHandler::getInstance();
229 bool cacheResult = pluginHandler->installPluginFromCache(m_metadata);
233 downloader->run(
this, m_remove);
234 auto loader = PluginLoader::getInstance();
235 auto pic = PlugInByName(m_metadata.name, loader->GetPlugInArray());
237 wxLogMessage(
"Installation of %s failed", m_metadata.name.c_str());
240 auto upwards = GetParent()->GetParent()->GetParent();
242 wxASSERT(main_window != 0);
244 main_window->GetRealParent()->GetPrevSibling());
245 wxASSERT(listPanels != 0);
246 listPanels->ReloadPluginPanels();
247 auto window = GetSizer()->GetItem((
size_t)0)->GetWindow();
248 auto btn =
dynamic_cast<wxButton*
>(window);
250 btn->SetLabel(_(
"Reinstall"));
259 SemanticVersion currentVersion(pic->m_version_major, pic->m_version_minor);
260 if (pic->m_version_str !=
"") {
264 if (newVersion > currentVersion) {
266 }
else if (newVersion == currentVersion) {
267 return _(
"Reinstall");
269 return _(
"Downgrade");
279 auto flags = wxSizerFlags().Border();
281 auto vbox =
new wxBoxSizer(wxVERTICAL);
283 flags.DoubleBorder().Top().Right());
284 vbox->Add(1, 1, 1, wxEXPAND);
285 m_info_btn =
new WebsiteButton(
this, plugin->info_url.c_str());
287 vbox->Add(m_info_btn, flags.DoubleBorder().Bottom().Right());
292 void HideDetails(
bool hide) {
293 m_info_btn->Show(!hide);
294 GetParent()->Layout();
306 : wxPanel(parent), m_descr(0), m_buttons(buttons) {
307 auto flags = wxSizerFlags().Border();
309 MORE =
"<span foreground=\'blue\'>";
311 MORE +=
"...</span>";
312 LESS =
"<span foreground=\'blue\'>";
314 LESS +=
"...</span>";
316 auto sum_hbox =
new wxBoxSizer(wxHORIZONTAL);
317 m_summary = staticText(plugin->summary);
318 sum_hbox->Add(m_summary);
319 sum_hbox->AddSpacer(10);
320 m_more = staticText(
"");
321 m_more->SetLabelMarkup(MORE);
322 sum_hbox->Add(m_more, wxSizerFlags());
324 auto vbox =
new wxBoxSizer(wxVERTICAL);
325 wxString nameText(plugin->name +
" " + plugin->version);
326 if (bshowTuple) nameText +=
" " + plugin->target;
327 auto name = staticText(nameText);
328 m_descr = staticText(plugin->description);
330 vbox->Add(name, flags);
331 vbox->Add(sum_hbox, flags);
332 vbox->Add(m_descr, flags.Expand());
335 m_more->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick,
this);
336 m_descr->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick,
this);
339 void OnClick(wxMouseEvent& event) {
340 m_descr->Show(!m_descr->IsShown());
341 m_more->SetLabelMarkup(m_descr->IsShown() ? LESS : MORE);
342 m_buttons->HideDetails(!m_descr->IsShown());
343 GetParent()->SendSizeEvent();
344 GetParent()->GetParent()->GetParent()->Layout();
345 GetParent()->GetParent()->GetParent()->Refresh(
true);
346 GetParent()->GetParent()->GetParent()->Update();
352 wxStaticText* staticText(
const wxString& text) {
353 return new wxStaticText(
this, wxID_ANY, text, wxDefaultPosition,
354 wxDefaultSize, wxALIGN_LEFT);
357 wxStaticText* m_descr;
358 wxStaticText* m_more;
359 wxStaticText* m_summary;
369 : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(200, 32)) {
370 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
371 auto spacing = GetTextExtent(
"m").GetWidth();
372 sizer->Add(1, 1, 1, wxEXPAND);
374 sizer->Add(spacing, 0);
376 sizer->Add(spacing, 0);
377 sizer->Add(
new wxButton(
this, wxID_OK, _(
"Done")), wxSizerFlags());
386 : wxButton(parent, wxID_ANY, _(
"Update plugin catalog")) {
387 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [=](wxCommandEvent&) {
389 wxCommandEvent evt(EVT_PLUGINS_RELOAD);
390 wxPostEvent(GetParent(), evt);
399 : wxButton(parent, wxID_ANY, _(
"Advanced catalog update...")) {
400 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
413 : wxScrolledWindow(parent), m_grid(
new wxFlexGridSizer(3, 0, 0)) {
414 auto box =
new wxBoxSizer(wxVERTICAL);
416 box->Add(m_grid, wxSizerFlags().Proportion(1).Expand());
418 box->Add(button_panel, wxSizerFlags().Right().Border().Expand());
419 Bind(EVT_PLUGINS_RELOAD, [&](wxCommandEvent& ev) { Reload(); });
429 struct metadata_compare {
432 return lhs.key() < rhs.key();
436 auto flags = wxSizerFlags();
438 grid->AddGrowableCol(2);
439 auto available = PluginHandler::getInstance()->
getAvailable();
440 std::set<PluginMetadata, metadata_compare> unique_plugins;
441 for (
auto plugin : PluginHandler::getInstance()->getAvailable()) {
442 unique_plugins.insert(plugin);
444 for (
auto plugin : unique_plugins) {
451 unique_plugins.size() > 1),
452 flags.Proportion(1).Right());
453 grid->Add(buttons, flags.DoubleBorder());
454 grid->Add(
new wxStaticLine(
this), wxSizerFlags(0).Expand());
455 grid->Add(
new wxStaticLine(
this), wxSizerFlags(0).Expand());
456 grid->Add(
new wxStaticLine(
this), wxSizerFlags(0).Expand());
471 wxFlexGridSizer* m_grid;
478 : wxDialog(parent, wxID_ANY, _(
"Plugin Manager"), wxDefaultPosition,
479 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
480 auto vbox =
new wxBoxSizer(wxVERTICAL);
482 vbox->Add(scrwin, wxSizerFlags(1).Expand());
486 int min_height = GetTextExtent(
"abcdefghijklmnopqrst").GetHeight() * 20;
490 int width = GetParent()->GetClientSize().GetWidth();
491 SetMinClientSize(wxSize(width, min_height));
506std::string GuiDownloader::run(wxWindow* parent,
bool remove_current) {
508 bool downloaded =
false;
512 std::string label(_(
"Downloading "));
515 new wxProgressDialog(_(
"Downloading"), label.c_str(), size, parent,
516 wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
517#ifdef __OCPN__ANDROID__
518 m_dialog->SetBackgroundColour(wxColour(0x7c, 0xb0, 0xe9));
522 g_Platform->HideBusySpinner();
526 showErrorDialog(
"Download error");
532 showErrorDialog(
"Download aborted");
538 if (!download_mgr::checksum_ok(path, m_plugin)) {
539 showErrorDialog(
"Checksum error");
547 auto pluginHandler = PluginHandler::getInstance();
548 if (remove_current) {
549 wxLogMessage(
"Uninstalling %s", m_plugin.name.c_str());
550 pluginHandler->uninstall(m_plugin.name);
552 ok = pluginHandler->installPlugin(m_plugin, path);
554 showErrorDialog(
"Installation error");
560 wxURI uri(wxString(m_plugin.tarball_url.c_str()));
561 wxFileName fn(uri.GetPath());
562 auto basename = fn.GetFullName().ToStdString();
564 wxLogMessage(
"Copied %s to local cache at %s", path.c_str(),
569 wxMessageDialog* dlg =
new wxMessageDialog(
571 m_plugin.name +
" " + m_plugin.version + _(
" successfully installed"),
572 _(
"Installation complete"), wxOK | wxCENTRE | wxICON_INFORMATION);
579 m_downloaded += bytes;
580 if (m_dialog && !m_dialog->Update(m_downloaded)) {
587void GuiDownloader::showErrorDialog(
const char* msg) {
588 auto dlg =
new wxMessageDialog(m_parent,
"", _(
"Installation error"),
589 wxOK | wxICON_ERROR);
591 std::string text = msg;
592 if (last_error_msg !=
"") {
593 text = text +
": " + error_msg;
595 text = text +
"\nPlease check system log for more info.";
596 dlg->SetMessage(text);
Handle downloading of files from remote urls.
bool download(std::ostream *stream)
Download url into stream, return false on errors.
long get_filesize()
Try to get remote filesize, return 0 on failure.
std::string last_error()
Last Curl error message.
virtual void on_chunk(const char *buff, unsigned bytes)
Called when given bytes has been transferred from remote.
Add progress and final message dialogs to the basic Downloader.
void on_chunk(const char *buff, unsigned bytes) override
Called when given bytes has been transferred from remote.
GuiDownloader(wxWindow *parent, PluginMetadata plugin)
Add progress and final message dialogs to the basic Downloader.
PluginDownloadDialog(wxWindow *parent)
Top-level install plugins dialog.
bool uninstall(const std::string plugin)
Uninstall an installed plugin.
const std::vector< PluginMetadata > getAvailable()
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.
Button invoking the advanced catalog dialog.
Invokes the simple update catalog procedure.
Three buttons bottom-right for plugin catalog maintenance.
A plugin icon, scaled to about 2/3 of available space.
Plugin name, version, summary + an optionally shown description.
std::string lookup_tarball(const char *uri)
Get path to tarball in cache for given filename.
bool store_tarball(const char *path, const char *basename)
Store a tarball in tarball cache, return success/fail.
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.
Runtime representation of a plugin block.