31 #include <wx/bitmap.h>
32 #include <wx/button.h>
38 #include <wx/progdlg.h>
40 #include <wx/statline.h>
43 #include "catalog_mgr.h"
44 #include "download_mgr.h"
45 #include "model/downloader.h"
46 #include "OCPNPlatform.h"
48 #include "model/plugin_handler.h"
49 #include "model/plugin_cache.h"
50 #include "pluginmanager.h"
51 #include "model/semantic_vers.h"
53 #include "svg_utils.h"
65 namespace download_mgr {
70 static 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());
107 static ssize_t PlugInIxByName(
const std::string name,
108 const ArrayOfPlugIns* plugins) {
109 for (
unsigned i = 0; i < plugins->GetCount(); i += 1) {
110 if (name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
119 const ArrayOfPlugIns* plugins) {
120 auto ix = PlugInIxByName(name, plugins);
121 return ix == -1 ? 0 : plugins->Item(ix);
125 static void LoadPNGIcon(
const char* path,
int size, wxBitmap& bitmap) {
126 wxPNGHandler handler;
127 if (!wxImage::FindHandler(handler.GetName())) {
128 wxImage::AddHandler(
new wxPNGHandler());
130 auto img =
new wxImage();
131 bool ok = img->LoadFile(path, wxBITMAP_TYPE_PNG);
136 img->Rescale(size, size);
137 bitmap = wxBitmap(*img);
149 : wxPanel(parent), m_plugin_name(plugin_name) {
150 auto size = GetClientSize();
151 auto minsize = GetTextExtent(
"OpenCPN");
152 SetMinClientSize(wxSize(minsize.GetWidth(), size.GetHeight()));
154 Bind(wxEVT_PAINT, &PluginIconPanel::OnPaint,
this);
157 void OnPaint(wxPaintEvent& event) {
158 auto size = GetClientSize();
159 int minsize = wxMin(size.GetHeight(), size.GetWidth());
160 auto offset = minsize / 10;
162 LoadIcon(m_plugin_name.c_str(), m_bitmap, 2 * minsize / 3);
164 if (!m_bitmap.IsOk()) {
165 wxLogMessage(
"AddPluginPanel: bitmap is not OK!");
168 dc.DrawBitmap(m_bitmap, offset, offset,
true);
173 const std::string m_plugin_name;
175 void LoadIcon(
const char* plugin_name, wxBitmap& bitmap,
int size = 32) {
176 wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
177 path.AppendDir(
"uidata");
178 path.AppendDir(
"plugins");
181 if (path.IsFileReadable()) {
182 bitmap = LoadSVG(path.GetFullPath(), size, size);
187 if (path.IsFileReadable()) {
188 LoadPNGIcon(path.GetFullPath(), size, bitmap);
193 auto style = g_StyleManager->GetCurrentStyle();
194 bitmap = wxBitmap(style->GetIcon(_T(
"default_pi")));
203 : wxPanel(parent), m_metadata(metadata), m_remove(
false) {
205 PlugInByName(metadata.name,
206 PluginLoader::getInstance()->GetPlugInArray());
207 std::string label(_(
"Install"));
209 label = getUpdateLabel(found, metadata);
212 auto button =
new wxButton(
this, wxID_ANY, label);
213 auto pluginHandler = PluginHandler::getInstance();
214 button->Enable(pluginHandler->isPluginWritable(metadata.name));
215 auto box =
new wxBoxSizer(wxHORIZONTAL);
218 Bind(wxEVT_COMMAND_BUTTON_CLICKED, &InstallButton::OnClick,
this);
221 void OnClick(wxCommandEvent& event) {
223 if (m_remove && path !=
"") {
224 wxLogMessage(
"Uninstalling %s", m_metadata.name.c_str());
225 PluginHandler::getInstance()->
uninstall(m_metadata.name);
227 wxLogMessage(
"Installing %s", m_metadata.name.c_str());
229 auto pluginHandler = PluginHandler::getInstance();
230 bool cacheResult = pluginHandler->installPluginFromCache(m_metadata);
234 downloader->run(
this, m_remove);
235 auto loader = PluginLoader::getInstance();
236 auto pic = PlugInByName(m_metadata.name, loader->GetPlugInArray());
238 wxLogMessage(
"Installation of %s failed", m_metadata.name.c_str());
241 auto upwards = GetParent()->GetParent()->GetParent();
243 wxASSERT(main_window != 0);
245 main_window->GetRealParent()->GetPrevSibling());
246 wxASSERT(listPanels != 0);
247 listPanels->ReloadPluginPanels();
248 auto window = GetSizer()->GetItem((
size_t)0)->GetWindow();
249 auto btn =
dynamic_cast<wxButton*
>(window);
251 btn->SetLabel(_(
"Reinstall"));
260 SemanticVersion currentVersion(pic->m_version_major, pic->m_version_minor);
265 if (newVersion > currentVersion) {
267 }
else if (newVersion == currentVersion) {
268 return _(
"Reinstall");
270 return _(
"Downgrade");
280 auto flags = wxSizerFlags().Border();
282 auto vbox =
new wxBoxSizer(wxVERTICAL);
284 flags.DoubleBorder().Top().Right());
285 vbox->Add(1, 1, 1, wxEXPAND);
286 m_info_btn =
new WebsiteButton(
this, plugin->info_url.c_str());
288 vbox->Add(m_info_btn, flags.DoubleBorder().Bottom().Right());
293 void HideDetails(
bool hide) {
294 m_info_btn->Show(!hide);
295 GetParent()->Layout();
307 : wxPanel(parent), m_descr(0), m_buttons(buttons) {
308 auto flags = wxSizerFlags().Border();
310 MORE =
"<span foreground=\'blue\'>";
312 MORE +=
"...</span>";
313 LESS =
"<span foreground=\'blue\'>";
315 LESS +=
"...</span>";
317 auto sum_hbox =
new wxBoxSizer(wxHORIZONTAL);
318 m_summary = staticText(plugin->summary);
319 sum_hbox->Add(m_summary);
320 sum_hbox->AddSpacer(10);
321 m_more = staticText(
"");
322 m_more->SetLabelMarkup(MORE);
323 sum_hbox->Add(m_more, wxSizerFlags());
325 auto vbox =
new wxBoxSizer(wxVERTICAL);
326 wxString nameText(plugin->name +
" " + plugin->version);
327 if (bshowTuple) nameText +=
" " + plugin->target;
328 auto name = staticText(nameText);
329 m_descr = staticText(plugin->description);
331 vbox->Add(name, flags);
332 vbox->Add(sum_hbox, flags);
333 vbox->Add(m_descr, flags.Expand());
336 m_more->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick,
this);
337 m_descr->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick,
this);
340 void OnClick(wxMouseEvent& event) {
341 m_descr->Show(!m_descr->IsShown());
342 m_more->SetLabelMarkup(m_descr->IsShown() ? LESS : MORE);
343 m_buttons->HideDetails(!m_descr->IsShown());
344 GetParent()->SendSizeEvent();
345 GetParent()->GetParent()->GetParent()->Layout();
346 GetParent()->GetParent()->GetParent()->Refresh(
true);
347 GetParent()->GetParent()->GetParent()->Update();
353 wxStaticText* staticText(
const wxString& text) {
354 return new wxStaticText(
this, wxID_ANY, text, wxDefaultPosition,
355 wxDefaultSize, wxALIGN_LEFT);
358 wxStaticText* m_descr;
359 wxStaticText* m_more;
360 wxStaticText* m_summary;
370 : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(200, 32)) {
371 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
372 auto spacing = GetTextExtent(
"m").GetWidth();
373 sizer->Add(1, 1, 1, wxEXPAND);
375 sizer->Add(spacing, 0);
377 sizer->Add(spacing, 0);
378 sizer->Add(
new wxButton(
this, wxID_OK, _(
"Done")), wxSizerFlags());
387 : wxButton(parent, wxID_ANY, _(
"Update plugin catalog")) {
388 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [=](wxCommandEvent&) {
390 wxCommandEvent evt(EVT_PLUGINS_RELOAD);
391 wxPostEvent(GetParent(), evt);
400 : wxButton(parent, wxID_ANY, _(
"Advanced catalog update...")) {
401 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
414 : wxScrolledWindow(parent), m_grid(
new wxFlexGridSizer(3, 0, 0)) {
415 auto box =
new wxBoxSizer(wxVERTICAL);
417 box->Add(m_grid, wxSizerFlags().Proportion(1).Expand());
419 box->Add(button_panel, wxSizerFlags().Right().Border().Expand());
420 Bind(EVT_PLUGINS_RELOAD, [&](wxCommandEvent& ev) { Reload(); });
430 struct metadata_compare {
433 return lhs.key() < rhs.key();
437 auto flags = wxSizerFlags();
439 grid->AddGrowableCol(2);
440 auto available = PluginHandler::getInstance()->
getAvailable();
441 std::set<PluginMetadata, metadata_compare> unique_plugins;
442 for (
auto plugin : PluginHandler::getInstance()->getAvailable()) {
443 unique_plugins.insert(plugin);
445 for (
auto plugin : unique_plugins) {
452 unique_plugins.size() > 1),
453 flags.Proportion(1).Right());
454 grid->Add(buttons, flags.DoubleBorder());
455 grid->Add(
new wxStaticLine(
this), wxSizerFlags(0).Expand());
456 grid->Add(
new wxStaticLine(
this), wxSizerFlags(0).Expand());
457 grid->Add(
new wxStaticLine(
this), wxSizerFlags(0).Expand());
472 wxFlexGridSizer* m_grid;
479 : wxDialog(parent, wxID_ANY, _(
"Plugin Manager"), wxDefaultPosition,
480 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
481 auto vbox =
new wxBoxSizer(wxVERTICAL);
483 vbox->Add(scrwin, wxSizerFlags(1).Expand());
487 int min_height = GetTextExtent(
"abcdefghijklmnopqrst").GetHeight() * 20;
491 int width = GetParent()->GetClientSize().GetWidth();
492 SetMinClientSize(wxSize(width, min_height));
507 std::string GuiDownloader::run(wxWindow* parent,
bool remove_current) {
509 bool downloaded =
false;
513 std::string label(_(
"Downloading "));
516 new wxProgressDialog(_(
"Downloading"), label.c_str(), size, parent,
517 wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
518 #ifdef __OCPN__ANDROID__
519 m_dialog->SetBackgroundColour(wxColour(0x7c, 0xb0, 0xe9));
523 g_Platform->HideBusySpinner();
527 showErrorDialog(
"Download error");
533 showErrorDialog(
"Download aborted");
539 if (!download_mgr::checksum_ok(path, m_plugin)) {
540 showErrorDialog(
"Checksum error");
548 auto pluginHandler = PluginHandler::getInstance();
549 if (remove_current) {
550 wxLogMessage(
"Uninstalling %s", m_plugin.name.c_str());
551 pluginHandler->uninstall(m_plugin.name);
553 ok = pluginHandler->installPlugin(m_plugin, path);
555 showErrorDialog(
"Installation error");
561 wxURI uri(wxString(m_plugin.tarball_url.c_str()));
562 wxFileName fn(uri.GetPath());
563 auto basename = fn.GetFullName().ToStdString();
565 wxLogMessage(
"Copied %s to local cache at %s", path.c_str(),
570 wxMessageDialog* dlg =
new wxMessageDialog(
572 m_plugin.name +
" " + m_plugin.version + _(
" successfully installed"),
573 _(
"Installation complete"), wxOK | wxCENTRE | wxICON_INFORMATION);
580 m_downloaded += bytes;
581 if (m_dialog && !m_dialog->Update(m_downloaded)) {
588 void GuiDownloader::showErrorDialog(
const char* msg) {
589 auto dlg =
new wxMessageDialog(m_parent,
"", _(
"Installation error"),
590 wxOK | wxICON_ERROR);
592 std::string text = msg;
593 if (last_error_msg !=
"") {
594 text = text +
": " + error_msg;
596 text = text +
"\nPlease check system log for more info.";
597 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.
Data for a loaded plugin, including dl-loaded library.
wxString m_version_str
Complete version as of semantic_vers.
PluginDownloadDialog(wxWindow *parent)
Top-level install plugins dialog.
bool uninstall(const std::string plugin)
Uninstall an installed and loaded plugin.
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.
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.
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.
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.