OpenCPN Partial API docs
Loading...
Searching...
No Matches
chartdldr_pi.cpp
1/******************************************************************************
2 * $Id: chartdldr_pi.cpp,v 1.0 2011/02/26 01:54:37 nohal Exp $
3 *
4 * Project: OpenCPN
5 * Purpose: Chart downloader Plugin
6 * Author: Pavel Kalian
7 *
8 ***************************************************************************
9 * Copyright (C) 2011 by Pavel Kalian *
10 * $EMAIL$ *
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 * This program is distributed in the hope that it will be useful, *
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
20 * GNU General Public License for more details. *
21 * *
22 * You should have received a copy of the GNU General Public License *
23 * along with this program; if not, write to the *
24 * Free Software Foundation, Inc., *
25 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
26 ***************************************************************************
27 */
28
29#include "wx/wxprec.h"
30
31#ifndef WX_PRECOMP
32#include "wx/wx.h"
33#endif // precompiled headers
34
35#include "chartdldr_pi.h"
36#include "wxWTranslateCatalog.h"
37#include <wx/stdpaths.h>
38#include <wx/url.h>
39#include <wx/progdlg.h>
40#include <wx/sstream.h>
41#include <wx/wfstream.h>
42#include <wx/filename.h>
43#include <wx/listctrl.h>
44#include <wx/dir.h>
45#include <wx/filesys.h>
46#include <wx/zipstrm.h>
47#include <wx/wfstream.h>
48#include <memory>
49#include <wx/regex.h>
50#include <wx/debug.h>
51
52#ifdef DLDR_USE_LIBARCHIVE
53#include <archive.h>
54#include <archive_entry.h>
55#ifdef CHARTDLDR_RAR_UNARR
56#include "unarr.h"
57#endif
58#else
59#include "unarr.h"
60#endif
61
62#ifdef __ANDROID__
63#define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
64#endif
65
66#include <fstream>
67
68#ifdef __ANDROID__
69#include "androidSupport.h"
70#include "android_jvm.h"
71#include <jni.h>
72#endif
73
74#ifdef __WXMAC__
75#define CATALOGS_NAME_WIDTH 300
76#define CATALOGS_DATE_WIDTH 120
77#define CATALOGS_PATH_WIDTH 100
78#define CHARTS_NAME_WIDTH 300
79#define CHARTS_STATUS_WIDTH 100
80#define CHARTS_DATE_WIDTH 120
81#else
82#ifdef __ANDROID__
83
84#define CATALOGS_NAME_WIDTH 350
85#define CATALOGS_DATE_WIDTH 500
86#define CATALOGS_PATH_WIDTH 1000
87#define CHARTS_NAME_WIDTH 520
88#define CHARTS_STATUS_WIDTH 150
89#define CHARTS_DATE_WIDTH 200
90
91#else
92
93#define CATALOGS_NAME_WIDTH 200
94#define CATALOGS_DATE_WIDTH 130
95#define CATALOGS_PATH_WIDTH 250
96#define CHARTS_NAME_WIDTH 320
97#define CHARTS_STATUS_WIDTH 150
98#define CHARTS_DATE_WIDTH 130
99
100#endif
101#endif // __WXMAC__
102
103#ifdef __ANDROID__
104#include <QtAndroidExtras/QAndroidJniObject>
105#include "qdebug.h"
106
107#endif
108
109bool getDisplayMetrics();
110
111#define CHART_DIR "Charts"
112
113static wxString FormatBytes(double bytes) {
114 if (bytes <= 0) return "?";
115 return wxString::Format(_T("%.1fMB"), bytes / 1024 / 1024);
116}
117
118static wxString FormatBytes(long bytes) {
119 return FormatBytes(static_cast<double>(bytes));
120}
121
122int g_Android_SDK_Version;
123
124bool IsDLDirWritable(wxFileName fn) {
125#ifndef __ANDROID__
126 return fn.IsDirWritable();
127#else
128 if (g_Android_SDK_Version >= 30) { // scoped storage?
129 // Use a simple test here
130 return (fn.GetFullPath().Contains("org.opencpn.opencpn")); // fast test
131 } else
132 return fn.IsDirWritable();
133
134#endif
135}
136
137// the class factories, used to create and destroy instances of the PlugIn
138
139extern "C" DECL_EXP opencpn_plugin *create_pi(void *ppimgr) {
140 return new chartdldr_pi(ppimgr);
141}
142
143extern "C" DECL_EXP void destroy_pi(opencpn_plugin *p) { delete p; }
144
145double g_androidDPmm;
146chartdldr_pi *g_pi;
147
148//---------------------------------------------------------------------------------------------------------
149//
150// ChartDldr PlugIn Implementation
151//
152//---------------------------------------------------------------------------------------------------------
153
154#include "icons.h"
155
156//---------------------------------------------------------------------------------------------------------
157//
158// PlugIn initialization and de-init
159//
160//---------------------------------------------------------------------------------------------------------
161
162chartdldr_pi::chartdldr_pi(void *ppimgr) : opencpn_plugin_113(ppimgr) {
163 // Create the PlugIn icons
164 initialize_images();
165
166 m_parent_window = NULL;
167 m_pChartSource = NULL;
168 m_pconfig = NULL;
169 m_preselect_new = false;
170 m_preselect_updated = false;
171 m_allow_bulk_update = false;
172 m_pOptionsPage = NULL;
173 m_selected_source = -1;
174 m_dldrpanel = NULL;
175 m_schartdldr_sources = wxEmptyString;
176
177 g_pi = this;
178}
179
181 AddLocaleCatalog(PLUGIN_CATALOG_NAME);
182
183 // Get a pointer to the opencpn display canvas, to use as a parent for the
184 // POI Manager dialog
185 m_parent_window = GetOCPNCanvasWindow();
186
187 // Get a pointer to the opencpn configuration object
188 m_pconfig = GetOCPNConfigObject();
189 m_pOptionsPage = NULL;
190
191 m_pChartSource = NULL;
192
193#ifdef __ANDROID__
194 androidGetSDKVersion();
195#endif
196
197 // And load the configuration items
198 LoadConfig();
199
200 getDisplayMetrics();
201
202 wxStringTokenizer st(m_schartdldr_sources, _T("|"), wxTOKEN_DEFAULT);
203 while (st.HasMoreTokens()) {
204 wxString s1 = st.GetNextToken();
205 wxString s2 = st.GetNextToken();
206 wxString s3 = st.GetNextToken();
207 if (!s2.IsEmpty()) // scrub empty sources.
208 m_ChartSources.push_back(std::make_unique<ChartSource>(s1, s2, s3));
209 }
211}
212
214 wxLogMessage(_T("chartdldr_pi: DeInit"));
215
216 m_ChartSources.clear();
217 // wxDELETE(m_pChartSource);
218 /* TODO: Seth */
219 // dialog->Close();
220 // dialog->Destroy();
221 // wxDELETE(dialog);
222 /* We must delete remaining page if the plugin is disabled while in Options
223 * dialog */
224 if (m_pOptionsPage) {
225 if (DeleteOptionsPage(m_pOptionsPage)) m_pOptionsPage = NULL;
226 // TODO: any other memory leak?
227 }
228 return true;
229}
230
231int chartdldr_pi::GetAPIVersionMajor() { return MY_API_VERSION_MAJOR; }
232
233int chartdldr_pi::GetAPIVersionMinor() { return MY_API_VERSION_MINOR; }
234
235int chartdldr_pi::GetPlugInVersionMajor() { return PLUGIN_VERSION_MAJOR; }
236
237int chartdldr_pi::GetPlugInVersionMinor() { return PLUGIN_VERSION_MINOR; }
238
239wxBitmap *chartdldr_pi::GetPlugInBitmap() { return _img_chartdldr_pi; }
240
241wxString chartdldr_pi::GetCommonName() { return _("ChartDownloader"); }
242
244 return _("Chart Downloader PlugIn for OpenCPN");
245}
246
248 return _(
249 "Chart Downloader PlugIn for OpenCPN\n\
250Manages chart downloads and updates from sources supporting\n\
251NOAA Chart Catalog format");
252}
253
255 m_pOptionsPage =
256 AddOptionsPage(PI_OPTIONS_PARENT_CHARTS, _("Chart Downloader"));
257 if (!m_pOptionsPage) {
258 wxLogMessage(
259 _T("Error: chartdldr_pi::OnSetupOptions AddOptionsPage failed!"));
260 return;
261 }
262 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
263 m_pOptionsPage->SetSizer(sizer);
264
265 m_dldrpanel =
266 new ChartDldrPanelImpl(this, m_pOptionsPage, wxID_ANY, wxDefaultPosition,
267 wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
268
269 m_pOptionsPage->InvalidateBestSize();
270 sizer->Add(m_dldrpanel, 1, wxALL | wxEXPAND);
271 m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
272 m_dldrpanel->FitInside();
273}
274
275void chartdldr_pi::OnCloseToolboxPanel(int page_sel, int ok_apply_cancel) {
276 /* TODO: Seth */
277 m_dldrpanel->CancelDownload();
278#ifndef __ANDROID__
279 OCPN_cancelDownloadFileBackground(
280 0); // Stop the thread, is something like this needed on Android as well?
281#endif
282 m_selected_source = m_dldrpanel->GetSelectedCatalog();
283 SaveConfig();
284}
285
286bool chartdldr_pi::LoadConfig(void) {
287 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
288
289 if (pConf) {
290 pConf->SetPath(_T ( "/Settings/ChartDnldr" ));
291 pConf->Read(_T ( "ChartSources" ), &m_schartdldr_sources, wxEmptyString);
292 pConf->Read(_T ( "Source" ), &m_selected_source, -1);
293
294 wxFileName fn(GetWritableDocumentsDir(), wxEmptyString);
295 fn.AppendDir(_T(CHART_DIR));
296
297 pConf->Read(_T ( "BaseChartDir" ), &m_base_chart_dir, fn.GetPath());
298 wxLogMessage(_T ( "chartdldr_pi:m_base_chart_dir: " ) + m_base_chart_dir);
299
300 // Check to see if the directory is writeable, esp. on App updates.
301 wxFileName testFN(m_base_chart_dir);
302 if (!IsDLDirWritable(testFN)) {
303 wxLogMessage(
304 "Cannot write to m_base_chart_dir, override to "
305 "GetWritableDocumentsDir()");
306 m_base_chart_dir = fn.GetPath();
307 wxLogMessage(_T ( "chartdldr_pi: Corrected: " ) + m_base_chart_dir);
308 }
309
310 pConf->Read(_T ( "PreselectNew" ), &m_preselect_new, true);
311 pConf->Read(_T ( "PreselectUpdated" ), &m_preselect_updated, true);
312 pConf->Read(_T ( "AllowBulkUpdate" ), &m_allow_bulk_update, false);
313 return true;
314 } else
315 return false;
316}
317
318bool chartdldr_pi::SaveConfig(void) {
319 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
320
321 m_schartdldr_sources.Clear();
322
323 for (size_t i = 0; i < m_ChartSources.size(); i++) {
324 std::unique_ptr<ChartSource> &cs = m_ChartSources.at(i);
325 m_schartdldr_sources.Append(
326 wxString::Format(_T("%s|%s|%s|"), cs->GetName().c_str(),
327 cs->GetUrl().c_str(), cs->GetDir().c_str()));
328 }
329
330 if (pConf) {
331 pConf->SetPath(_T ( "/Settings/ChartDnldr" ));
332 pConf->Write(_T ( "ChartSources" ), m_schartdldr_sources);
333 pConf->Write(_T ( "Source" ), m_selected_source);
334 pConf->Write(_T ( "BaseChartDir" ), m_base_chart_dir);
335 pConf->Write(_T ( "PreselectNew" ), m_preselect_new);
336 pConf->Write(_T ( "PreselectUpdated" ), m_preselect_updated);
337 pConf->Write(_T ( "AllowBulkUpdate" ), m_allow_bulk_update);
338
339 return true;
340 } else
341 return false;
342}
343
344void SetBackColor(wxWindow *ctrl, wxColour col) {
345 static int depth = 0; // recursion count
346 if (depth == 0) { // only for the window root, not for every child
347
348 ctrl->SetBackgroundColour(col);
349 }
350
351 wxWindowList kids = ctrl->GetChildren();
352 for (unsigned int i = 0; i < kids.GetCount(); i++) {
353 wxWindowListNode *node = kids.Item(i);
354 wxWindow *win = node->GetData();
355
356 if (dynamic_cast<wxListBox *>(win))
357 dynamic_cast<wxListBox *>(win)->SetBackgroundColour(col);
358
359 else if (dynamic_cast<wxTextCtrl *>(win))
360 dynamic_cast<wxTextCtrl *>(win)->SetBackgroundColour(col);
361
362 // else if( win->IsKindOf( CLASSINFO(wxStaticText) ) )
363 // ( (wxStaticText*) win )->SetForegroundColour( uitext );
364
365 else if (dynamic_cast<wxChoice *>(win))
366 dynamic_cast<wxChoice *>(win)->SetBackgroundColour(col);
367
368 else if (dynamic_cast<wxComboBox *>(win))
369 dynamic_cast<wxComboBox *>(win)->SetBackgroundColour(col);
370
371 else if (dynamic_cast<wxRadioButton *>(win))
372 dynamic_cast<wxRadioButton *>(win)->SetBackgroundColour(col);
373
374 else if (dynamic_cast<wxScrolledWindow *>(win)) {
375 dynamic_cast<wxScrolledWindow *>(win)->SetBackgroundColour(col);
376 }
377
378 else if (dynamic_cast<wxButton *>(win)) {
379 dynamic_cast<wxButton *>(win)->SetBackgroundColour(col);
380 }
381
382 else {
383 ;
384 }
385
386 if (win->GetChildren().GetCount() > 0) {
387 depth++;
388 wxWindow *w = win;
389 SetBackColor(w, col);
390 depth--;
391 }
392 }
393}
394
396 ChartDldrPrefsDlgImpl *dialog = new ChartDldrPrefsDlgImpl(parent);
397
398 wxFont fo = GetOCPNGUIScaledFont_PlugIn(_("Dialog"));
399 dialog->SetFont(fo);
400
401#ifdef __ANDROID__
402 if (m_parent_window) {
403 int xmax = m_parent_window->GetSize().GetWidth();
404 int ymax = m_parent_window->GetParent()
405 ->GetSize()
406 .GetHeight(); // This would be the Options dialog itself
407 dialog->SetSize(xmax, ymax);
408 dialog->Layout();
409
410 dialog->Move(0, 0);
411 }
412
413 wxColour cl = wxColour(214, 218, 222);
414 SetBackColor(dialog, cl);
415#endif
416
417 dialog->SetPath(m_base_chart_dir);
418 dialog->SetPreferences(m_preselect_new, m_preselect_updated,
419 m_allow_bulk_update);
420
421 dialog->ShowModal();
422 dialog->Destroy();
423}
424
425void chartdldr_pi::UpdatePrefs(ChartDldrPrefsDlgImpl *dialog) {
426 m_base_chart_dir = dialog->GetPath();
427 dialog->GetPreferences(m_preselect_new, m_preselect_updated,
428 m_allow_bulk_update);
429 SaveConfig();
430 if (m_dldrpanel) m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
431}
432
433bool getDisplayMetrics() {
434#ifdef __ANDROID__
435
436 g_androidDPmm = 4.0; // nominal default
437
438 // Get a reference to the running native activity
439 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
440 "org/qtproject/qt5/android/QtNative", "activity",
441 "()Landroid/app/Activity;");
442
443 if (!activity.isValid()) {
444 return false;
445 }
446
447 // Call the desired method
448 QAndroidJniObject data =
449 activity.callObjectMethod("getDisplayMetrics", "()Ljava/lang/String;");
450
451 wxString return_string;
452 jstring s = data.object<jstring>();
453
454 // Need a Java environment to decode the resulting string
455 JNIEnv *jenv;
456 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
457 // qDebug() << "GetEnv failed.";
458 } else {
459 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
460 return_string = wxString(ret_string, wxConvUTF8);
461 }
462
463 // Return string may have commas instead of periods, if using Euro locale
464 // We just fix it here...
465 return_string.Replace(_T(","), _T("."));
466
467 // wxLogMessage(_T("Metrics:") + return_string);
468 // wxSize screen_size = ::wxGetDisplaySize();
469 // wxString msg;
470 // msg.Printf(_T("wxGetDisplaySize(): %d %d"), screen_size.x,
471 // screen_size.y); wxLogMessage(msg);
472
473 double density = 1.0;
474 wxStringTokenizer tk(return_string, _T(";"));
475 if (tk.HasMoreTokens()) {
476 wxString token = tk.GetNextToken(); // xdpi
477 token = tk.GetNextToken(); // density
478
479 long b = ::wxGetDisplaySize().y;
480 token.ToDouble(&density);
481
482 token = tk.GetNextToken(); // ldpi
483
484 token = tk.GetNextToken(); // width
485 token = tk.GetNextToken(); // height - statusBarHeight
486 token = tk.GetNextToken(); // width
487 token = tk.GetNextToken(); // height
488 token = tk.GetNextToken(); // dm.widthPixels
489 token = tk.GetNextToken(); // dm.heightPixels
490
491 token = tk.GetNextToken(); // actionBarHeight
492 long abh;
493 token.ToLong(&abh);
494 // g_ActionBarHeight = wxMax(abh, 50);
495
496 // qDebug() << "g_ActionBarHeight" << abh << g_ActionBarHeight;
497 }
498
499 double ldpi = 160. * density;
500
501 // double maxDim = wxMax(::wxGetDisplaySize().x, ::wxGetDisplaySize().y);
502 // ret = (maxDim / ldpi) * 25.4;
503
504 // msg.Printf(_T("Android Auto Display Size (mm, est.): %g"), ret);
505 // wxLogMessage(msg);
506
507 // Save some items as global statics for convenience
508 g_androidDPmm = ldpi / 25.4;
509 // g_androidDensity = density;
510
511 // qDebug() << "PI Metrics" << g_androidDPmm << density;
512 return true;
513#else
514
515 return true;
516#endif
517}
518
519ChartSource::ChartSource(wxString name, wxString url, wxString localdir) {
520 m_name = name;
521 m_url = url;
522 m_dir = localdir;
523 m_update_data.clear();
524}
525
526ChartSource::~ChartSource() { m_update_data.clear(); }
527
528#define ID_MNU_SELALL 2001
529#define ID_MNU_DELALL 2002
530#define ID_MNU_INVSEL 2003
531#define ID_MNU_SELUPD 2004
532#define ID_MNU_SELNEW 2005
533
534enum { ThreadId = wxID_HIGHEST + 1 };
535
536BEGIN_EVENT_TABLE(ChartDldrPanelImpl, ChartDldrPanel)
537END_EVENT_TABLE()
538
539void ChartDldrPanelImpl::OnPopupClick(wxCommandEvent &evt) {
540 switch (evt.GetId()) {
541 case ID_MNU_SELALL:
542 CheckAllCharts(true);
543 break;
544 case ID_MNU_DELALL:
545 CheckAllCharts(false);
546 break;
547 case ID_MNU_INVSEL:
548 InvertCheckAllCharts();
549 break;
550 case ID_MNU_SELUPD:
551 CheckUpdatedCharts(true);
552 break;
553 case ID_MNU_SELNEW:
554 CheckNewCharts(true);
555 break;
556 }
557}
558
559void ChartDldrPanelImpl::OnContextMenu(wxMouseEvent &event) {
560 wxMenu menu;
561
562 wxPoint mouseScreen = wxGetMousePosition();
563 wxPoint mouseClient = ScreenToClient(mouseScreen);
564
565#ifdef __ANDROID__
566 wxFont *pf = OCPNGetFont(_("Menu"));
567
568 // add stuff
569 wxMenuItem *item1 = new wxMenuItem(&menu, ID_MNU_SELALL, _("Select all"));
570 item1->SetFont(*pf);
571 menu.Append(item1);
572
573 wxMenuItem *item2 = new wxMenuItem(&menu, ID_MNU_DELALL, _("Deselect all"));
574 item2->SetFont(*pf);
575 menu.Append(item2);
576
577 wxMenuItem *item3 =
578 new wxMenuItem(&menu, ID_MNU_INVSEL, _("Invert selection"));
579 item3->SetFont(*pf);
580 menu.Append(item3);
581
582 wxMenuItem *item4 = new wxMenuItem(&menu, ID_MNU_SELUPD, _("Select updated"));
583 item4->SetFont(*pf);
584 menu.Append(item4);
585
586 wxMenuItem *item5 = new wxMenuItem(&menu, ID_MNU_SELNEW, _("Select new"));
587 item5->SetFont(*pf);
588 menu.Append(item5);
589
590#else
591
592 menu.Append(ID_MNU_SELALL, _("Select all"), wxT(""));
593 menu.Append(ID_MNU_DELALL, _("Deselect all"), wxT(""));
594 menu.Append(ID_MNU_INVSEL, _("Invert selection"), wxT(""));
595 menu.Append(ID_MNU_SELUPD, _("Select updated"), wxT(""));
596 menu.Append(ID_MNU_SELNEW, _("Select new"), wxT(""));
597
598#endif
599
600 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
601 (wxObjectEventFunction)&ChartDldrPanelImpl::OnPopupClick, NULL,
602 this);
603 // and then display
604 PopupMenu(&menu, mouseClient.x, mouseClient.y);
605}
606
607void ChartDldrPanelImpl::OnShowLocalDir(wxCommandEvent &event) {
608 if (pPlugIn->m_pChartSource == NULL) return;
609#ifdef __WXGTK__
610 wxExecute(wxString::Format(_T("xdg-open %s"),
611 pPlugIn->m_pChartSource->GetDir().c_str()));
612#endif
613#ifdef __WXMAC__
614 wxExecute(wxString::Format(_T("open %s"),
615 pPlugIn->m_pChartSource->GetDir().c_str()));
616#endif
617#ifdef __WXMSW__
618 wxExecute(wxString::Format(_T("explorer %s"),
619 pPlugIn->m_pChartSource->GetDir().c_str()));
620#endif
621}
622
623void ChartDldrPanelImpl::SetSource(int id) {
624 pPlugIn->SetSourceId(id);
625
626 m_bDeleteSource->Enable(id >= 0);
627 m_bUpdateChartList->Enable(id >= 0);
628 m_bEditSource->Enable(id >= 0);
629
630 // TODO: DAN - Need to optimze to only update the chart list if needed.
631 // Right now it updates multiple times unnecessarily.
632 CleanForm();
633 if (id >= 0 && id < (int)pPlugIn->m_ChartSources.size()) {
634 ::wxBeginBusyCursor(); // wxSetCursor(wxCURSOR_WAIT);
635 // wxYield();
636 std::unique_ptr<ChartSource> &cs = pPlugIn->m_ChartSources.at(id);
637 cs->LoadUpdateData();
638 cs->UpdateLocalFiles();
639 pPlugIn->m_pChartSource = cs.get();
640 FillFromFile(cs->GetUrl(), cs->GetDir(), pPlugIn->m_preselect_new,
641 pPlugIn->m_preselect_updated);
642 wxURI url(cs->GetUrl());
643 m_chartsLabel->SetLabel(wxString::Format(
644 _("Charts: %s"), (cs->GetName() + _(" from ") + url.BuildURI() +
645 _T(" -> ") + cs->GetDir())
646 .c_str()));
647 if (::wxIsBusy()) ::wxEndBusyCursor();
648 } else {
649 pPlugIn->m_pChartSource = NULL;
650 m_chartsLabel->SetLabel(_("Charts"));
651 }
652}
653
654void ChartDldrPanelImpl::SelectSource(wxListEvent &event) {
655 int i = GetSelectedCatalog();
656 if (i >= 0) SetSource(i);
657 event.Skip();
658}
659
660void ChartDldrPanelImpl::SetBulkUpdate(bool bulk_update) {
661 m_bUpdateAllCharts->Enable(bulk_update);
662 m_bUpdateAllCharts->Show(bulk_update);
663 Layout();
664 m_parent->Layout();
665}
666
667void ChartDldrPanelImpl::CleanForm() {
668#if defined(CHART_LIST)
669 clearChartList();
670#else
671 m_scrollWinChartList->ClearBackground();
672#endif /* CHART_LIST */
673 // m_stCatalogInfo->Show( false );
674}
675
676void ChartDldrPanelImpl::FillFromFile(wxString url, wxString dir, bool selnew,
677 bool selupd) {
678 // load if exists
679 wxStringTokenizer tk(url, _T("/"));
680 wxString file;
681 do {
682 file = tk.GetNextToken();
683 } while (tk.HasMoreTokens());
684 wxFileName fn;
685 fn.SetFullName(file);
686 fn.SetPath(dir);
687 wxString path = fn.GetFullPath();
688 if (wxFileExists(path)) {
689 pPlugIn->m_pChartCatalog.LoadFromFile(path);
690 // m_tChartSourceInfo->SetValue(pPlugIn->m_pChartCatalog.GetDescription());
691 // fill in the rest of the form
692
693 m_updatedCharts = 0;
694 m_newCharts = 0;
695
696#if !defined(CHART_LIST)
697 // Clear any existing panels
698 m_panelArray.clear();
699 m_scrollWinChartList->ClearBackground();
700#endif /* CHART_LIST */
701
702 for (size_t i = 0; i < pPlugIn->m_pChartCatalog.charts.size(); i++) {
703 wxString status;
704 wxString latest;
705 bool bcheck = false;
706 wxString file_ =
707 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartFilename(true);
708 if (!pPlugIn->m_pChartSource->ExistsLocaly(
709 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_)) {
710 m_newCharts++;
711 status = _("New");
712 if (selnew) bcheck = true;
713 } else {
714 if (pPlugIn->m_pChartSource->IsNewerThanLocal(
715 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_,
716 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime())) {
717 m_updatedCharts++;
718 status = _("Out of date");
719 if (selupd) bcheck = true;
720 } else {
721 status = _("Up to date");
722 }
723 }
724 latest =
725 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime().Format(
726 _T("%Y-%m-%d"));
727
728#if defined(CHART_LIST)
729 wxVector<wxVariant> data;
730 data.push_back(wxVariant(bcheck));
731 data.push_back(wxVariant(status));
732 data.push_back(wxVariant(latest));
733 data.push_back(
734 wxVariant(pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle()));
735 getChartList()->AppendItem(data);
736#else
737 auto pC = std::make_unique<ChartPanel>(
738 m_scrollWinChartList, wxID_ANY, wxDefaultPosition, wxSize(-1, -1),
739 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle(), status,
740 latest, this, bcheck);
741 pC->Connect(wxEVT_RIGHT_DOWN,
742 wxMouseEventHandler(ChartDldrPanel::OnContextMenu), NULL,
743 this);
744
745 m_boxSizerCharts->Add(pC.get(), 0, wxEXPAND | wxLEFT | wxRIGHT, 2);
746 m_panelArray.push_back(std::move(pC));
747#endif /* CHART_LIST */
748 }
749
750#if !defined(CHART_LIST) // wxDataViewListCtrl handles all of this AFAIK: Dan
751 m_scrollWinChartList->ClearBackground();
752 m_scrollWinChartList->FitInside();
753 m_scrollWinChartList->GetSizer()->Layout();
754 Layout();
755 m_scrollWinChartList->ClearBackground();
756 SetChartInfo(wxString::Format(_("%lu charts total, %lu updated, %lu new"),
757 pPlugIn->m_pChartCatalog.charts.size(),
758 m_updatedCharts, m_newCharts));
759#else
760 SetChartInfo(wxString::Format(
761 _("%lu charts total, %lu updated, %lu new, %lu selected"),
762 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
763 GetCheckedChartCount()));
764#endif /* CHART_LIST */
765 }
766}
767
768bool ChartSource::ExistsLocaly(wxString chart_number, wxString filename) {
769 wxASSERT(this);
770
771 wxStringTokenizer tk(filename, _T("."));
772 wxString file = tk.GetNextToken().MakeLower();
773
774 if (!m_update_data.empty()) {
775 return m_update_data.find(std::string(chart_number.Lower().mb_str())) !=
776 m_update_data.end() ||
777 m_update_data.find(std::string(file.mb_str())) !=
778 m_update_data.end();
779 }
780 for (size_t i = 0; i < m_localfiles.Count(); i++) {
781 if (m_localfiles.Item(i) == file) return true;
782 }
783 return false;
784}
785
786bool ChartSource::IsNewerThanLocal(wxString chart_number, wxString filename,
787 wxDateTime validDate) {
788 wxStringTokenizer tk(filename, _T("."));
789 wxString file = tk.GetNextToken().MakeLower();
790 if (!m_update_data.empty()) {
791 if (m_update_data[std::string(chart_number.Lower().mbc_str())] <
792 validDate.GetTicks() &&
793 m_update_data[std::string(file.mbc_str())] < validDate.GetTicks())
794 return true;
795 else
796 return false;
797 }
798 bool update_candidate = false;
799
800 for (size_t i = 0; i < m_localfiles.Count(); i++) {
801 if (m_localfiles.Item(i) == file) {
802 if (validDate.IsLaterThan(m_localdt.at(i))) {
803 update_candidate = true;
804 } else
805 return false;
806 }
807 }
808 return update_candidate;
809}
810
811int ChartDldrPanelImpl::GetSelectedCatalog() {
812 long item =
813 m_lbChartSources->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
814 return item;
815}
816
817void ChartDldrPanelImpl::SelectCatalog(int item) {
818 if (item >= 0) {
819 m_bDeleteSource->Enable();
820 m_bEditSource->Enable();
821 m_bUpdateChartList->Enable();
822 } else {
823 m_bDeleteSource->Disable();
824 m_bEditSource->Disable();
825 m_bUpdateChartList->Disable();
826 }
827 m_lbChartSources->SetItemState(item, wxLIST_STATE_SELECTED,
828 wxLIST_STATE_SELECTED);
829}
830
831void ChartDldrPanelImpl::AppendCatalog(std::unique_ptr<ChartSource> &cs) {
832 long id = m_lbChartSources->GetItemCount();
833 m_lbChartSources->InsertItem(id, cs->GetName());
834 m_lbChartSources->SetItem(id, 1, _("(Please update first)"));
835 m_lbChartSources->SetItem(id, 2, cs->GetDir());
836 wxURI url(cs->GetUrl());
837 if (url.IsReference()) {
838 OCPNMessageBox_PlugIn(
839 this, _("Error, the URL to the chart source data seems wrong."),
840 _("Error"));
841 return;
842 }
843 wxFileName fn(url.GetPath());
844 fn.SetPath(cs->GetDir());
845 wxString path = fn.GetFullPath();
846 if (wxFileExists(path)) {
847 if (pPlugIn->m_pChartCatalog.LoadFromFile(path, true)) {
848 m_lbChartSources->SetItem(id, 0, pPlugIn->m_pChartCatalog.title);
849 m_lbChartSources->SetItem(
850 id, 1,
851 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
852 _T("%Y-%m-%d %H:%M")));
853 m_lbChartSources->SetItem(id, 2, path);
854#ifdef __ANDROID__
855 m_lbChartSources->GetHandle()->resizeColumnToContents(0);
856 m_lbChartSources->GetHandle()->resizeColumnToContents(1);
857 m_lbChartSources->GetHandle()->resizeColumnToContents(2);
858#endif
859 }
860 }
861}
862
863void ChartDldrPanelImpl::UpdateAllCharts(wxCommandEvent &event) {
864 int failed_to_update = 0;
865 int attempted_to_update = 0;
866 if ((pPlugIn->m_preselect_new) && (pPlugIn->m_preselect_updated)) {
867 wxMessageDialog mess(
868 this,
869 _("You have chosen to update all chart catalogs.\nThen download all "
870 "new and updated charts.\nThis may take a long time."),
871 _("Chart Downloader"), wxOK | wxCANCEL);
872 if (mess.ShowModal() == wxID_CANCEL) return;
873 } else if (pPlugIn->m_preselect_new) {
874 wxMessageDialog mess(
875 this,
876 _("You have chosen to update all chart catalogs.\nThen download only "
877 "new (but not updated) charts.\nThis may take a long time."),
878 _("Chart Downloader"), wxOK | wxCANCEL);
879 if (mess.ShowModal() == wxID_CANCEL) return;
880 } else if (pPlugIn->m_preselect_updated) {
881 wxMessageDialog mess(
882 this,
883 _("You have chosen to update all chart catalogs.\nThen download only "
884 "updated (but not new) charts.\nThis may take a long time."),
885 _("Chart Downloader"), wxOK | wxCANCEL);
886 if (mess.ShowModal() == wxID_CANCEL) return;
887 }
888 updatingAll = true;
889 cancelled = false;
890 // Flip to the list of charts so user can observe the download progress
891 int oldPage = m_DLoadNB->SetSelection(1);
892 for (long chartIndex = 0; chartIndex < m_lbChartSources->GetItemCount();
893 chartIndex++) {
894 m_lbChartSources->SetItemState(chartIndex, wxLIST_STATE_SELECTED,
895 wxLIST_STATE_SELECTED);
896 if (cancelled) break;
897 UpdateChartList(event);
899 attempted_to_update += m_downloading;
900 failed_to_update += m_failed_downloads;
901 }
902 wxLogMessage(wxString::Format(
903 _T("chartdldr_pi::UpdateAllCharts() downloaded %d out of %d charts."),
904 attempted_to_update - failed_to_update, attempted_to_update));
905 if (failed_to_update > 0)
906 OCPNMessageBox_PlugIn(
907 this,
908 wxString::Format(_("%d out of %d charts failed to download.\nCheck the "
909 "list, verify there is a working Internet "
910 "connection and repeat the operation if needed."),
911 failed_to_update, attempted_to_update),
912 _("Chart Downloader"), wxOK | wxICON_ERROR);
913 if (attempted_to_update > failed_to_update) ForceChartDBUpdate();
914 updatingAll = false;
915 cancelled = false;
916 // Flip back to the original page
917 m_DLoadNB->SetSelection(oldPage);
918}
919
920void ChartDldrPanelImpl::UpdateChartList(wxCommandEvent &event) {
921 // TODO: check if everything exists and we can write to the output dir etc.
922 if (!m_lbChartSources->GetSelectedItemCount()) return;
923 std::unique_ptr<ChartSource> &cs =
924 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
925 wxURI url(cs->GetUrl());
926 if (url.IsReference()) {
927 OCPNMessageBox_PlugIn(
928 this, _("Error, the URL to the chart source data seems wrong."),
929 _("Error"));
930 return;
931 }
932
933 wxStringTokenizer tk(url.GetPath(), _T("/"));
934 wxString file;
935 do {
936 file = tk.GetNextToken();
937 } while (tk.HasMoreTokens());
938 wxFileName fn;
939 fn.SetFullName(file);
940 fn.SetPath(cs->GetDir());
941 if (!wxDirExists(cs->GetDir())) {
942 if (!wxFileName::Mkdir(cs->GetDir(), 0755, wxPATH_MKDIR_FULL)) {
943 OCPNMessageBox_PlugIn(
944 this,
945 wxString::Format(_("Directory %s can't be created."),
946 cs->GetDir().c_str()),
947 _("Chart Downloader"));
948 return;
949 }
950 }
951
952 bool bok = false;
953
954#ifdef __ANDROID__
955 wxString file_URI = _T("file://") + fn.GetFullPath();
956
957 // wxFile testFile(tfn.GetFullPath().c_str(), wxFile::write);
958 // if(!testFile.IsOpened()){
959 // wxMessageBox(this, wxString::Format(_("File %s can't be written.
960 // \nChoose a writable folder for Chart Downloader file storage."),
961 // tfn.GetFullPath().c_str()), _("Chart Downloader")); return;
962 // }
963 // testFile.Close();
964 // ::wxRemoveFile(tfn.GetFullPath());
965
966 _OCPN_DLStatus ret = OCPN_downloadFile(
967 cs->GetUrl(), file_URI, _("Downloading file"),
968 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
973 10);
974 bok = true;
975
976#else
977 wxFileName tfn = wxFileName::CreateTempFileName(fn.GetFullPath());
978 wxString file_URI = tfn.GetFullPath();
979
980 _OCPN_DLStatus ret = OCPN_downloadFile(
981 cs->GetUrl(), file_URI, _("Downloading file"),
982 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
987 10);
988
989 bok = wxCopyFile(tfn.GetFullPath(), fn.GetFullPath());
990 wxRemoveFile(tfn.GetFullPath());
991
992#endif
993
994 // wxLogMessage(_T("chartdldr_pi: OCPN_downloadFile done:"));
995
996 switch (ret) {
997 case OCPN_DL_NO_ERROR: {
998 if (bok) {
999 long id = GetSelectedCatalog();
1000 SetSource(id);
1001
1002 m_lbChartSources->SetItem(id, 0, pPlugIn->m_pChartCatalog.title);
1003 m_lbChartSources->SetItem(
1004 id, 1,
1005 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1006 _T("%Y-%m-%d %H:%M")));
1007 m_lbChartSources->SetItem(id, 2, cs->GetDir());
1008
1009 } else
1010 OCPNMessageBox_PlugIn(
1011 this,
1012 wxString::Format(_("Failed to Find New Catalog: %s "),
1013 url.BuildURI().c_str()),
1014 _("Chart Downloader"), wxOK | wxICON_ERROR);
1015 break;
1016 }
1017 case OCPN_DL_FAILED: {
1018 OCPNMessageBox_PlugIn(
1019 this,
1020 wxString::Format(_("Failed to Download Catalog: %s \nVerify there is "
1021 "a working Internet connection."),
1022 url.BuildURI().c_str()),
1023 _("Chart Downloader"), wxOK | wxICON_ERROR);
1024 break;
1025 }
1026
1028 case OCPN_DL_ABORTED: {
1029 cancelled = true;
1030 break;
1031 }
1032
1033 case OCPN_DL_UNKNOWN:
1034 case OCPN_DL_STARTED: {
1035 break;
1036 }
1037
1038 default:
1039 wxASSERT(false); // This should never happen because we handle all
1040 // possible cases of ret
1041 }
1042
1043 if ((ret == OCPN_DL_NO_ERROR) && bok) m_DLoadNB->SetSelection(1);
1044}
1045
1046void ChartSource::GetLocalFiles() {
1047 if (!UpdateDataExists() || m_update_data.empty()) {
1048 wxArrayString *allFiles = new wxArrayString;
1049 if (wxDirExists(GetDir())) wxDir::GetAllFiles(GetDir(), allFiles);
1050 m_localdt.clear();
1051 m_localfiles.Clear();
1052 wxDateTime ct, mt, at;
1053 wxString name;
1054 for (size_t i = 0; i < allFiles->Count(); i++) {
1055 wxFileName fn(allFiles->Item(i));
1056 name = fn.GetFullName().Lower();
1057 // Only add unique files names to the local list.
1058 // This is safe because all chart names within a catalog
1059 // are necessarily unique.
1060 if (!ExistsLocaly(wxEmptyString, name)) {
1061 fn.GetTimes(&at, &mt, &ct);
1062 m_localdt.push_back(mt);
1063 m_localfiles.Add(fn.GetName().Lower());
1064
1065 wxStringTokenizer tk(name, _T("."));
1066 wxString file = tk.GetNextToken().MakeLower();
1067 m_update_data[std::string(file.mbc_str())] = mt.GetTicks();
1068 }
1069 }
1070 allFiles->Clear();
1071 wxDELETE(allFiles);
1072 SaveUpdateData();
1073 } else {
1074 LoadUpdateData();
1075 }
1076}
1077
1078bool ChartSource::UpdateDataExists() {
1079 return wxFileExists(GetDir() + wxFileName::GetPathSeparator() +
1080 _T(UPDATE_DATA_FILENAME));
1081}
1082
1083void ChartSource::LoadUpdateData() {
1084 m_update_data.clear();
1085 wxString fn =
1086 GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1087
1088 if (!wxFileExists(fn)) return;
1089
1090 std::ifstream infile(fn.mb_str());
1091
1092 std::string key;
1093 long value;
1094
1095 while (infile >> key >> value) m_update_data[key] = value;
1096
1097 infile.close();
1098}
1099
1100void ChartSource::SaveUpdateData() {
1101 wxString fn;
1102 fn = GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1103
1104#ifdef __ANDROID__
1105 fn = AndroidGetCacheDir() + wxFileName::GetPathSeparator() +
1106 _T(UPDATE_DATA_FILENAME);
1107#endif
1108
1109 std::ofstream outfile(fn.mb_str());
1110 if (!outfile.is_open()) return;
1111
1112 std::map<std::string, time_t>::iterator iter;
1113 for (iter = m_update_data.begin(); iter != m_update_data.end(); ++iter) {
1114 if (iter->first.find(" ") == std::string::npos)
1115 if (!iter->first.empty())
1116 outfile << iter->first << " " << iter->second << "\n";
1117 }
1118
1119 outfile.close();
1120
1121#ifdef __ANDROID__
1122 AndroidSecureCopyFile(
1123 fn, GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME));
1124#endif
1125}
1126
1127void ChartSource::ChartUpdated(wxString chart_number, time_t timestamp) {
1128 m_update_data[std::string(chart_number.Lower().mb_str())] = timestamp;
1129 SaveUpdateData();
1130}
1131
1132bool ChartDldrPanelImpl::DownloadChart(wxString url, wxString file,
1133 wxString title) {
1134 return false;
1135}
1136
1137void ChartDldrPanelImpl::DisableForDownload(bool enabled) {
1138 m_bAddSource->Enable(enabled);
1139 m_bDeleteSource->Enable(enabled);
1140 m_bEditSource->Enable(enabled);
1141 m_bUpdateAllCharts->Enable(enabled);
1142 m_bUpdateChartList->Enable(enabled);
1143 m_lbChartSources->Enable(enabled);
1144#if defined(CHART_LIST)
1145 m_bSelectNew->Enable(enabled);
1146 m_bSelectUpdated->Enable(enabled);
1147 m_bSelectAll->Enable(enabled);
1148#endif /* CHART_LIST */
1149}
1150
1151void ChartDldrPanelImpl::OnDownloadCharts(wxCommandEvent &event) {
1152 if (DownloadIsCancel) {
1153 cancelled = true;
1154 return;
1155 }
1157}
1158#if defined(CHART_LIST)
1159void ChartDldrPanelImpl::OnSelectChartItem(wxCommandEvent &event) {
1160 if (!m_bInfoHold)
1161 SetChartInfo(wxString::Format(
1162 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1163 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1164 GetCheckedChartCount()));
1165 else
1166 event.Skip();
1167}
1168#endif /* CHART_LIST */
1169#if defined(CHART_LIST)
1170void ChartDldrPanelImpl::OnSelectNewCharts(wxCommandEvent &event) {
1171 CheckNewCharts(true);
1172}
1173#endif /* CHART_LIST */
1174
1175#if defined(CHART_LIST)
1176void ChartDldrPanelImpl::OnSelectUpdatedCharts(wxCommandEvent &event) {
1177 CheckUpdatedCharts(true);
1178}
1179#endif /* CHART_LIST */
1180
1181#if defined(CHART_LIST)
1182void ChartDldrPanelImpl::OnSelectAllCharts(wxCommandEvent &event) {
1183 if (m_bSelectAll->GetLabel() == _("Select All")) {
1184 CheckAllCharts(true);
1185 m_bSelectAll->SetLabel(_("Select None"));
1186 m_bSelectAll->SetToolTip(_("De-select all charts in the list."));
1187 } else {
1188 CheckAllCharts(false);
1189 m_bSelectAll->SetLabel(_("Select All"));
1190 m_bSelectAll->SetToolTip(_("Select all charts in the list."));
1191 }
1192}
1193#endif /* CHART_LIST */
1194
1195int ChartDldrPanelImpl::GetChartCount() {
1196#if defined(CHART_LIST)
1197 return getChartList()->GetItemCount();
1198#else
1199 return m_panelArray.size();
1200#endif /* CHART_LIST*/
1201}
1202
1203int ChartDldrPanelImpl::GetCheckedChartCount() {
1204#if defined(CHART_LIST)
1205 int cnt = 0;
1206 int chartCnt = GetChartCount();
1207 for (int i = 0; i < chartCnt; i++)
1208 if (isChartChecked(i)) cnt++;
1209#else
1210 int cnt = 0;
1211 for (int i = 0; i < GetChartCount(); i++) {
1212 if (m_panelArray.at(i)->GetCB()->IsChecked()) cnt++;
1213 }
1214#endif /* CHART_LIST*/
1215 return cnt;
1216}
1217
1218bool ChartDldrPanelImpl::isChartChecked(int i) {
1219 wxASSERT_MSG(i >= 0,
1220 wxT("This function should be called with non-negative index."));
1221 if (i <= GetChartCount())
1222#if defined(CHART_LIST)
1223 return getChartList()->GetToggleValue(i, 0);
1224#else
1225 return m_panelArray.at(i)->GetCB()->IsChecked();
1226#endif /* CHART_LIST*/
1227 else
1228 return false;
1229}
1230
1231void ChartDldrPanelImpl::CheckAllCharts(bool value) {
1232#if defined(CHART_LIST)
1233 m_bInfoHold = true;
1234#endif /* CHART_LIST */
1235
1236 for (int i = 0; i < GetChartCount(); i++) {
1237#if defined(CHART_LIST)
1238 getChartList()->SetToggleValue(value, i, 0);
1239#else
1240 m_panelArray.at(i)->GetCB()->SetValue(value);
1241#endif /* CHART_LIST*/
1242 }
1243#if defined(CHART_LIST)
1244 SetChartInfo(wxString::Format(
1245 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1246 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1247 GetCheckedChartCount()));
1248 m_bInfoHold = false;
1249#endif /* CHART_LIST */
1250}
1251
1252void ChartDldrPanelImpl::CheckNewCharts(bool value) {
1253 for (int i = 0; i < GetChartCount(); i++) {
1254#if defined(CHART_LIST)
1255 if (isNew(i)) getChartList()->SetToggleValue(true, i, 0);
1256#else
1257 if (m_panelArray.at(i)->isNew())
1258 m_panelArray.at(i)->GetCB()->SetValue(value);
1259#endif /* CHART_LIST*/
1260 }
1261#if defined(CHART_LIST)
1262 SetChartInfo(wxString::Format(
1263 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1264 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1265 GetCheckedChartCount()));
1266#endif /* CHART_LIST */
1267}
1268
1269void ChartDldrPanelImpl::CheckUpdatedCharts(bool value) {
1270 for (int i = 0; i < GetChartCount(); i++) {
1271#if defined(CHART_LIST)
1272 if (isUpdated(i)) getChartList()->SetToggleValue(value, i, 0);
1273#else
1274 if (m_panelArray.at(i)->isUpdated())
1275 m_panelArray.at(i)->GetCB()->SetValue(value);
1276#endif /* CHART_LIST */
1277 }
1278#if defined(CHART_LIST)
1279 SetChartInfo(wxString::Format(
1280 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1281 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1282 GetCheckedChartCount()));
1283#endif /* CHART_LIST */
1284}
1285
1286void ChartDldrPanelImpl::InvertCheckAllCharts() {
1287#if defined(CHART_LIST)
1288 m_bInfoHold = true;
1289#endif /* CHART_LIST */
1290 for (int i = 0; i < GetChartCount(); i++)
1291#if defined(CHART_LIST)
1292 getChartList()->SetToggleValue(!isChartChecked(i), i, 0);
1293#else
1294 m_panelArray.at(i)->GetCB()->SetValue(!isChartChecked(i));
1295#endif /* CHART_LIST */
1296#if defined(CHART_LIST)
1297 m_bInfoHold = false;
1298 SetChartInfo(wxString::Format(
1299 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1300 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1301 GetCheckedChartCount()));
1302#endif /* CHART_LIST */
1303}
1304
1306 if (!m_bconnected) {
1307 Connect(
1308 wxEVT_DOWNLOAD_EVENT,
1309 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1310 m_bconnected = true;
1311 }
1312
1313 if (!GetCheckedChartCount() && !updatingAll) {
1314 OCPNMessageBox_PlugIn(this, _("No charts selected for download."));
1315 return;
1316 }
1317 std::unique_ptr<ChartSource> &cs =
1318 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
1319
1320 cancelled = false;
1321 to_download = GetCheckedChartCount();
1322 m_downloading = 0;
1323 m_failed_downloads = 0;
1324 DisableForDownload(false);
1325 // wxString old_label = m_bDnldCharts->GetLabel(); // Broken on Android??
1326 m_bDnldCharts->SetLabel(_("Abort download"));
1327 DownloadIsCancel = true;
1328
1329 wxFileName downloaded_p;
1330 int idx = -1;
1331
1332 for (int i = 0; i < GetChartCount() && to_download; i++) {
1333 int index = i;
1334 if (cancelled) break;
1335 // Prepare download queues
1336 if (!isChartChecked(i)) continue;
1337 m_bTransferComplete = false;
1338 m_bTransferSuccess = true;
1339 m_totalsize = -1;
1340 m_transferredsize = 0;
1341 m_downloading++;
1342 if (pPlugIn->m_pChartCatalog.charts.at(index)->NeedsManualDownload()) {
1343 if (wxID_YES ==
1344 OCPNMessageBox_PlugIn(
1345 this,
1346 wxString::Format(
1347 _("The selected chart '%s' can't be downloaded automatically, do you want me to open a browser window and download them manually?\n\n \
1348After downloading the charts, please extract them to %s"),
1349 pPlugIn->m_pChartCatalog.charts.at(index)->title.c_str(),
1350 pPlugIn->m_pChartSource->GetDir().c_str()),
1351 _("Chart Downloader"), wxYES_NO | wxCENTRE | wxICON_QUESTION)) {
1352 wxLaunchDefaultBrowser(
1353 pPlugIn->m_pChartCatalog.charts.at(index)->GetManualDownloadUrl());
1354 }
1355 continue;
1356 }
1357
1358 // download queue
1359 wxURI url(pPlugIn->m_pChartCatalog.charts.at(index)->GetDownloadLocation());
1360 if (url.IsReference()) {
1361 OCPNMessageBox_PlugIn(
1362 this,
1363 wxString::Format(
1364 _("Error, the URL to the chart (%s) data seems wrong."),
1365 url.BuildURI().c_str()),
1366 _("Error"));
1367 this->Enable();
1369 return;
1370 }
1371 // construct local file path
1372 wxString file =
1373 pPlugIn->m_pChartCatalog.charts.at(index)->GetChartFilename();
1374 wxFileName fn;
1375 fn.SetFullName(file);
1376 fn.SetPath(cs->GetDir());
1377 wxString path = fn.GetFullPath();
1378 if (wxFileExists(path)) wxRemoveFile(path);
1379 wxString title = pPlugIn->m_pChartCatalog.charts.at(index)->GetChartTitle();
1380
1381 // Ready to start download
1382#ifdef __ANDROID__
1383 wxString file_path = _T("file://") + fn.GetFullPath();
1384#else
1385 wxString file_path = fn.GetFullPath();
1386#endif
1387
1388 long handle;
1389 OCPN_downloadFileBackground(url.BuildURI(), file_path, this, &handle);
1390
1391 if (idx >= 0) {
1392 if (pPlugIn->ProcessFile(
1393 downloaded_p.GetFullPath(), downloaded_p.GetPath(), true,
1394 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1395 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1396 pPlugIn->m_pChartCatalog.charts.at(idx)
1397 ->GetUpdateDatetime()
1398 .GetTicks());
1399 } else {
1400 m_failed_downloads++;
1401 }
1402 idx = -1;
1403 }
1404
1405 while (!m_bTransferComplete && m_bTransferSuccess && !cancelled) {
1406 if (m_failed_downloads)
1407 SetChartInfo(wxString::Format(
1408 _("Downloading chart %u of %u, %u downloads failed (%s / %s)"),
1409 m_downloading, to_download, m_failed_downloads,
1410 FormatBytes(m_transferredsize), FormatBytes(m_totalsize)));
1411 else
1412 SetChartInfo(wxString::Format(_("Downloading chart %u of %u (%s / %s)"),
1413 m_downloading, to_download,
1414 FormatBytes(m_transferredsize),
1415 FormatBytes(m_totalsize)));
1416
1417 Update();
1418 Refresh();
1419
1420 wxTheApp->ProcessPendingEvents();
1421 wxYield();
1422 wxMilliSleep(20);
1423 }
1424
1425 if (cancelled) {
1426 idx = -1;
1427 OCPN_cancelDownloadFileBackground(handle);
1428 }
1429
1430 if (m_bTransferSuccess && !cancelled) {
1431 idx = index;
1432 downloaded_p = path;
1433 } else {
1434 idx = -1;
1435 if (wxFileExists(path)) wxRemoveFile(path);
1436 m_failed_downloads++;
1437 }
1438 }
1439 if (idx >= 0) {
1440 if (pPlugIn->ProcessFile(
1441 downloaded_p.GetFullPath(), downloaded_p.GetPath(), true,
1442 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1443 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1444 pPlugIn->m_pChartCatalog.charts.at(idx)
1445 ->GetUpdateDatetime()
1446 .GetTicks());
1447 } else {
1448 m_failed_downloads++;
1449 }
1450 }
1451 DisableForDownload(true);
1452 m_bDnldCharts->SetLabel(_("Download selected charts"));
1453 DownloadIsCancel = false;
1454 SetSource(GetSelectedCatalog());
1455 if (m_failed_downloads > 0 && !updatingAll && !cancelled)
1456 OCPNMessageBox_PlugIn(
1457 this,
1458 wxString::Format(_("%d out of %d charts failed to download.\nCheck the "
1459 "list, verify there is a working Internet "
1460 "connection and repeat the operation if needed."),
1461 m_failed_downloads, m_downloading),
1462 _("Chart Downloader"), wxOK | wxICON_ERROR);
1463
1464 if (cancelled)
1465 OCPNMessageBox_PlugIn(this, _("Chart download cancelled."),
1466 _("Chart Downloader"), wxOK | wxICON_INFORMATION);
1467
1468 if ((m_downloading - m_failed_downloads > 0) && !updatingAll)
1470}
1471
1472ChartDldrPanelImpl::~ChartDldrPanelImpl() {
1473 Disconnect(
1474 wxEVT_DOWNLOAD_EVENT,
1475 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1476 m_bconnected = false;
1477
1478#ifndef __ANDROID__
1479 OCPN_cancelDownloadFileBackground(
1480 0); // Stop the thread, is something like this needed on Android as well?
1481#endif
1482#if defined(CHART_LIST)
1483 clearChartList();
1484#endif /* CHART_LIST */
1485}
1486
1487ChartDldrPanelImpl::ChartDldrPanelImpl(chartdldr_pi *plugin, wxWindow *parent,
1488 wxWindowID id, const wxPoint &pos,
1489 const wxSize &size, long style)
1490 : ChartDldrPanel(parent, id, pos, size, style) {
1491 m_bDeleteSource->Disable();
1492 m_bUpdateChartList->Disable();
1493 m_bEditSource->Disable();
1494 m_lbChartSources->InsertColumn(0, _("Catalog"), wxLIST_FORMAT_LEFT,
1495 CATALOGS_NAME_WIDTH);
1496 m_lbChartSources->InsertColumn(1, _("Released"), wxLIST_FORMAT_LEFT,
1497 CATALOGS_DATE_WIDTH);
1498 m_lbChartSources->InsertColumn(2, _("Local path"), wxLIST_FORMAT_LEFT,
1499 CATALOGS_PATH_WIDTH);
1500 m_lbChartSources->Enable();
1501 m_bInfoHold = false;
1502 cancelled = true;
1503 to_download = -1;
1504 m_downloading = -1;
1505 updatingAll = false;
1506 pPlugIn = plugin;
1507 m_populated = false;
1508 DownloadIsCancel = false;
1509 m_failed_downloads = 0;
1510 SetChartInfo(wxEmptyString);
1511 m_bTransferComplete = true;
1512 m_bTransferSuccess = true;
1513
1514 Connect(
1515 wxEVT_DOWNLOAD_EVENT,
1516 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1517 m_bconnected = true;
1518
1519 for (size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1520 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1521 }
1522 m_populated = true;
1523}
1524
1525void ChartDldrPanelImpl::OnPaint(wxPaintEvent &event) {
1526 if (!m_populated) {
1527 m_populated = true;
1528 for (size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1529 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1530 }
1531 }
1532#ifdef __WXMAC__
1533 // Mojave does not paint the controls correctly without this.
1534 m_lbChartSources->Refresh(true);
1535#endif
1536 event.Skip();
1537}
1538
1539void ChartDldrPanelImpl::DeleteSource(wxCommandEvent &event) {
1540 if (!m_lbChartSources->GetSelectedItemCount()) return;
1541 if (wxID_YES != OCPNMessageBox_PlugIn(
1542 this,
1543 _("Do you really want to remove the chart source?\nThe "
1544 "local chart files will not be removed,\nbut you will "
1545 "not be able to update the charts anymore."),
1546 _("Chart Downloader"), wxYES_NO | wxCENTRE))
1547 return;
1548 int ToBeRemoved = GetSelectedCatalog();
1549 m_lbChartSources->SetItemState(ToBeRemoved, 0,
1550 wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1551 pPlugIn->m_ChartSources.erase(pPlugIn->m_ChartSources.begin() + ToBeRemoved);
1552 m_lbChartSources->DeleteItem(ToBeRemoved);
1553 CleanForm();
1554 pPlugIn->SetSourceId(-1);
1555 SelectCatalog(-1);
1556 pPlugIn->SaveConfig();
1557 event.Skip();
1558}
1559
1560void ChartDldrPanelImpl::AddSource(wxCommandEvent &event) {
1562 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1563
1564 wxSize sz = GetParent()
1565 ->GetGrandParent()
1566 ->GetSize(); // This is the options panel true size
1567 dialog->SetSize(sz.GetWidth(), sz.GetHeight());
1568 dialog->Center();
1569
1570#ifdef __ANDROID__
1571 androidDisableRotation();
1572#endif
1573
1574 if (dialog->ShowModal() == wxID_OK) {
1575 std::unique_ptr<ChartSource> cs =
1576 std::make_unique<ChartSource>(dialog->m_tSourceName->GetValue(),
1577 dialog->m_tChartSourceUrl->GetValue(),
1578 dialog->m_tcChartDirectory->GetValue());
1579 dialog->Destroy();
1580 AppendCatalog(cs);
1581 bool covered = false;
1582 for (size_t i = 0; i < GetChartDBDirArrayString().GetCount(); i++) {
1583 if (cs->GetDir().StartsWith((GetChartDBDirArrayString().Item(i)))) {
1584 covered = true;
1585 break;
1586 }
1587 }
1588 if (!covered) {
1589 wxString dir = cs->GetDir();
1590 AddChartDirectory(dir);
1591 }
1592
1593 long itemSelectedNow = GetSelectedCatalog();
1594 m_lbChartSources->SetItemState(itemSelectedNow, 0, wxLIST_STATE_SELECTED);
1595
1596 SelectCatalog(m_lbChartSources->GetItemCount() - 1);
1597 pPlugIn->m_ChartSources.push_back(std::move(cs));
1598 pPlugIn->SaveConfig();
1599 }
1600#ifdef __ANDROID__
1601 androidEnableRotation();
1602#endif
1603
1604 event.Skip();
1605}
1606
1607void ChartDldrPanelImpl::DoEditSource() {
1608 if (!m_lbChartSources->GetSelectedItemCount()) return;
1609 int cat = GetSelectedCatalog();
1611 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1612 dialog->SetSourceEdit(pPlugIn->m_ChartSources.at(cat));
1613 dialog->SetTitle(_("Edit Chart Source"));
1614
1615 dialog->ShowModal();
1616 int retcode = dialog->GetReturnCode();
1617 {
1618 if (retcode == wxID_OK) {
1619 pPlugIn->m_ChartSources.at(cat)->SetName(
1620 dialog->m_tSourceName->GetValue());
1621 pPlugIn->m_ChartSources.at(cat)->SetUrl(
1622 dialog->m_tChartSourceUrl->GetValue());
1623 pPlugIn->m_ChartSources.at(cat)->SetDir(
1624 dialog->m_tcChartDirectory->GetValue());
1625
1626 m_lbChartSources->SetItem(cat, 0,
1627 pPlugIn->m_ChartSources.at(cat)->GetName());
1628 m_lbChartSources->SetItem(cat, 1, _("(Please update first)"));
1629 m_lbChartSources->SetItem(cat, 2,
1630 pPlugIn->m_ChartSources.at(cat)->GetDir());
1631 wxURI url(pPlugIn->m_ChartSources.at(cat)->GetUrl());
1632 wxFileName fn(url.GetPath());
1633 fn.SetPath(pPlugIn->m_ChartSources.at(cat)->GetDir());
1634 wxString path = fn.GetFullPath();
1635 if (wxFileExists(path)) {
1636 if (pPlugIn->m_pChartCatalog.LoadFromFile(path, true)) {
1637 m_lbChartSources->SetItem(cat, 0, pPlugIn->m_pChartCatalog.title);
1638 m_lbChartSources->SetItem(
1639 cat, 1,
1640 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1641 _T("%Y-%m-%d %H:%M")));
1642 m_lbChartSources->SetItem(cat, 2, path);
1643 }
1644 }
1645 bool covered = false;
1646 for (size_t i = 0; i < GetChartDBDirArrayString().GetCount(); i++) {
1647 if (pPlugIn->m_ChartSources.at(cat)->GetDir().StartsWith(
1648 (GetChartDBDirArrayString().Item(i)))) {
1649 covered = true;
1650 break;
1651 }
1652 }
1653 if (!covered)
1654 OCPNMessageBox_PlugIn(
1655 this,
1656 wxString::Format(
1657 _("Path %s seems not to be covered by your configured Chart "
1658 "Directories.\nTo see the charts you have to adjust the "
1659 "configuration on the 'Chart Files' tab."),
1660 pPlugIn->m_ChartSources.at(cat)->GetDir().c_str()),
1661 _("Chart Downloader"));
1662
1663 pPlugIn->SaveConfig();
1664 SetSource(cat);
1665 }
1666 }
1667}
1668
1669void ChartDldrPanelImpl::EditSource(wxCommandEvent &event) {
1670 DoEditSource();
1671 event.Skip();
1672}
1673
1674void ChartDldrPanelImpl::OnLeftDClick(wxMouseEvent &event) {
1675 DoEditSource();
1676 event.Skip();
1677}
1678
1679bool chartdldr_pi::ProcessFile(const wxString &aFile,
1680 const wxString &aTargetDir, bool aStripPath,
1681 wxDateTime aMTime) {
1682 if (aFile.Lower().EndsWith(_T("zip"))) // Zip compressed
1683 {
1684 bool ret = ExtractZipFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1685 if (ret)
1686 wxRemoveFile(aFile);
1687 else
1688 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1689 return ret;
1690 }
1691#ifdef DLDR_USE_LIBARCHIVE
1692 else if (aFile.Lower().EndsWith(_T("rar"))) {
1693#ifdef CHARTDLDR_RAR_UNARR
1694 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1695#else
1696 bool ret =
1697 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1698#endif
1699 if (ret)
1700 wxRemoveFile(aFile);
1701 else
1702 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1703 return ret;
1704 } else if (aFile.Lower().EndsWith(_T("tar")) ||
1705 aFile.Lower().EndsWith(_T("gz")) ||
1706 aFile.Lower().EndsWith(_T("bz2")) ||
1707 aFile.Lower().EndsWith(_T("lzma")) ||
1708 aFile.Lower().EndsWith(_T("7z")) ||
1709 aFile.Lower().EndsWith(_T("xz"))) {
1710 bool ret =
1711 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1712 if (ret)
1713 wxRemoveFile(aFile);
1714 else
1715 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1716 return ret;
1717 }
1718#else
1719 else if (aFile.Lower().EndsWith(_T("rar")) ||
1720 aFile.Lower().EndsWith(_T("tar"))
1721#ifdef HAVE_BZIP2
1722 || aFile.Lower().EndsWith(_T("bz2"))
1723#endif
1724#ifdef HAVE_ZLIB
1725 || aFile.Lower().EndsWith(_T("gz"))
1726#endif
1727#ifdef HAVE_7Z
1728 || aFile.Lower().EndsWith(
1729 _T("7z")) // TODO: Could it actually extract more formats the
1730 // LZMA SDK supports?
1731#endif
1732 ) {
1733 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1734 if (ret)
1735 wxRemoveFile(aFile);
1736 else
1737 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1738 return ret;
1739 }
1740#endif
1741
1742#ifdef __ANDROID__
1743 else if (aFile.Lower().EndsWith(_T("tar")) ||
1744 aFile.Lower().EndsWith(_T("gz")) ||
1745 aFile.Lower().EndsWith(_T("bz2")) ||
1746 aFile.Lower().EndsWith(_T("lzma")) ||
1747 aFile.Lower().EndsWith(_T("7z")) ||
1748 aFile.Lower().EndsWith(_T("xz"))) {
1749 int nStrip = 0;
1750 if (aStripPath) nStrip = 1;
1751
1752 if (m_dldrpanel) m_dldrpanel->SetChartInfo(_("Installing charts."));
1753
1754 androidShowBusyIcon();
1755 bool ret = AndroidUnzip(aFile, aTargetDir, nStrip, true);
1756 androidHideBusyIcon();
1757
1758 return ret;
1759 }
1760#endif
1761
1762 else // Uncompressed
1763 {
1764 wxFileName fn(aFile);
1765 if (fn.GetPath() != aTargetDir) // We have to move the file somewhere
1766 {
1767 if (!wxDirExists(aTargetDir)) {
1768 if (wxFileName::Mkdir(aTargetDir, 0755, wxPATH_MKDIR_FULL)) {
1769 if (!wxRenameFile(aFile, aTargetDir)) return false;
1770 } else
1771 return false;
1772 }
1773 }
1774 wxString name = fn.GetFullName();
1775 fn.Clear();
1776 fn.Assign(aTargetDir, name);
1777 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1778 }
1779 return true;
1780}
1781
1782#ifdef DLDR_USE_LIBARCHIVE
1783#ifndef __ANDROID__
1784static int copy_data(struct archive *ar, struct archive *aw) {
1785 int r;
1786 const void *buff;
1787 size_t size;
1788 __LA_INT64_T offset;
1789
1790 for (;;) {
1791 r = archive_read_data_block(ar, &buff, &size, &offset);
1792 if (r == ARCHIVE_EOF) return (ARCHIVE_OK);
1793 if (r < ARCHIVE_OK) return (r);
1794 r = archive_write_data_block(aw, buff, size, offset);
1795 if (r < ARCHIVE_OK) {
1796 // fprintf(stderr, "%s\n", archive_error_string(aw));
1797 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1798 archive_error_string(aw)));
1799 return (r);
1800 }
1801 }
1802}
1803#endif
1804
1805bool chartdldr_pi::ExtractLibArchiveFiles(const wxString &aArchiveFile,
1806 const wxString &aTargetDir,
1807 bool aStripPath, wxDateTime aMTime,
1808 bool aRemoveArchive) {
1809#ifndef __ANDROID__
1810 struct archive *a;
1811 struct archive *ext;
1812 struct archive_entry *entry;
1813 int flags;
1814 int r;
1815
1816 /* Select which attributes we want to restore. */
1817 flags = ARCHIVE_EXTRACT_TIME;
1818 /*
1819 flags |= ARCHIVE_EXTRACT_PERM;
1820 flags |= ARCHIVE_EXTRACT_ACL;
1821 flags |= ARCHIVE_EXTRACT_FFLAGS;
1822 */
1823
1824 a = archive_read_new();
1825 archive_read_support_format_all(a);
1826 archive_read_support_filter_all(a);
1827 archive_read_support_compression_all(a);
1828 ext = archive_write_disk_new();
1829 archive_write_disk_set_options(ext, flags);
1830 archive_write_disk_set_standard_lookup(ext);
1831 if ((r = archive_read_open_filename(a, aArchiveFile.c_str(), 10240))) {
1832 return false;
1833 }
1834 for (;;) {
1835 r = archive_read_next_header(a, &entry);
1836 if (r == ARCHIVE_EOF) break;
1837 if (r < ARCHIVE_OK)
1838 // fprintf(stderr, "%s\n", archive_error_string(a));
1839 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1840 archive_error_string(a)));
1841 if (r < ARCHIVE_WARN) return false;
1842 if (aStripPath) {
1843 const char *currentFile = archive_entry_pathname(entry);
1844 std::string fullOutputPath = currentFile;
1845 size_t sep = fullOutputPath.find_last_of("\\/");
1846 if (sep != std::string::npos)
1847 fullOutputPath =
1848 fullOutputPath.substr(sep + 1, fullOutputPath.size() - sep - 1);
1849 archive_entry_set_pathname(entry, fullOutputPath.c_str());
1850 }
1851 if (aTargetDir != wxEmptyString) {
1852 const char *currentFile = archive_entry_pathname(entry);
1853 const std::string fullOutputPath =
1854 aTargetDir.ToStdString() +
1855 wxString(wxFileName::GetPathSeparator()).ToStdString() + currentFile;
1856 archive_entry_set_pathname(entry, fullOutputPath.c_str());
1857 }
1858 r = archive_write_header(ext, entry);
1859 if (r < ARCHIVE_OK)
1860 // fprintf(stderr, "%s\n", archive_error_string(ext));
1861 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1862 archive_error_string(ext)));
1863 else if (archive_entry_size(entry) > 0) {
1864 r = copy_data(a, ext);
1865 if (r < ARCHIVE_OK)
1866 // fprintf(stderr, "%s\n", archive_error_string(ext));
1867 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1868 archive_error_string(ext)));
1869 if (r < ARCHIVE_WARN) return false;
1870 }
1871 r = archive_write_finish_entry(ext);
1872
1873 if (r < ARCHIVE_OK)
1874 // fprintf(stderr, "%s\n", archive_error_string(ext));
1875 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1876 archive_error_string(ext)));
1877 if (r < ARCHIVE_WARN) return false;
1878 }
1879 archive_read_close(a);
1880 // archive_read_free(a);
1881 archive_write_close(ext);
1882 // archive_write_free(ext);
1883
1884 if (aRemoveArchive) wxRemoveFile(aArchiveFile);
1885 return true;
1886
1887#else
1888
1889 return rv;
1890
1891#endif // Android
1892}
1893#endif
1894
1895#if defined(CHARTDLDR_RAR_UNARR) || !defined(DLDR_USE_LIBARCHIVE)
1896ar_archive *ar_open_any_archive(ar_stream *stream, const char *fileext) {
1897 ar_archive *ar = ar_open_rar_archive(stream);
1898 if (!ar)
1899 ar =
1900 ar_open_zip_archive(stream, fileext && (strcmp(fileext, ".xps") == 0 ||
1901 strcmp(fileext, ".epub") == 0));
1902 if (!ar) ar = ar_open_7z_archive(stream);
1903 if (!ar) ar = ar_open_tar_archive(stream);
1904 return ar;
1905}
1906
1907bool chartdldr_pi::ExtractUnarrFiles(const wxString &aRarFile,
1908 const wxString &aTargetDir,
1909 bool aStripPath, wxDateTime aMTime,
1910 bool aRemoveRar) {
1911 ar_stream *stream = NULL;
1912 ar_archive *ar = NULL;
1913 int entry_count = 1;
1914 int entry_skips = 0;
1915 int error_step = 1;
1916 bool ret = true;
1917
1918 stream = ar_open_file(aRarFile.c_str());
1919 if (!stream) {
1920 wxLogError(_T("Can not open file '") + aRarFile + _T("'."));
1921 ar_close_archive(ar);
1922 ar_close(stream);
1923 return false;
1924 }
1925 ar = ar_open_any_archive(stream, strrchr(aRarFile.c_str(), '.'));
1926 if (!ar) {
1927 wxLogError(_T("Can not open archive '") + aRarFile + _T("'."));
1928 ar_close_archive(ar);
1929 ar_close(stream);
1930 return false;
1931 }
1932 while (ar_parse_entry(ar)) {
1933 size_t size = ar_entry_get_size(ar);
1934 wxString name = ar_entry_get_name(ar);
1935 if (aStripPath) {
1936 wxFileName fn(name);
1937 /* We can completly replace the entry path */
1938 // fn.SetPath(aTargetDir);
1939 // name = fn.GetFullPath();
1940 /* Or only remove the first dir (eg. ENC_ROOT) */
1941 if (fn.GetDirCount() > 0) {
1942 fn.RemoveDir(0);
1943 name = aTargetDir + wxFileName::GetPathSeparator() + fn.GetFullPath();
1944 } else {
1945 name = aTargetDir + wxFileName::GetPathSeparator() + name;
1946 }
1947 }
1948 wxFileName fn(name);
1949 if (!fn.DirExists()) {
1950 if (!wxFileName::Mkdir(fn.GetPath())) {
1951 wxLogError(_T("Can not create directory '") + fn.GetPath() + _T("'."));
1952 ret = false;
1953 break;
1954 }
1955 }
1956 wxFileOutputStream file(name);
1957 if (!file) {
1958 wxLogError(_T("Can not create file '") + name + _T("'."));
1959 ret = false;
1960 break;
1961 }
1962 while (size > 0) {
1963 unsigned char buffer[1024];
1964 size_t count = size < sizeof(buffer) ? size : sizeof(buffer);
1965 if (!ar_entry_uncompress(ar, buffer, count)) break;
1966 file.Write(buffer, count);
1967 size -= count;
1968 }
1969 file.Close();
1970 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1971 if (size > 0) {
1972 wxLogError("Warning: Failed to uncompress... skipping");
1973 entry_skips++;
1974 ret = false;
1975 }
1976 }
1977 if (!ar_at_eof(ar)) {
1978 wxLogError("Error: Failed to parse entry %d!", entry_count);
1979 ret = false;
1980 }
1981 ar_close_archive(ar);
1982 ar_close(stream);
1983
1984 if (aRemoveRar) wxRemoveFile(aRarFile);
1985
1986#ifdef _UNIX
1987 // reset LC_NUMERIC locale, some locales use a comma for decimal point
1988 // and it corrupts navobj.xml file
1989 setlocale(LC_NUMERIC, "C");
1990#endif
1991
1992 return ret;
1993}
1994#endif
1995
1996bool chartdldr_pi::ExtractZipFiles(const wxString &aZipFile,
1997 const wxString &aTargetDir, bool aStripPath,
1998 wxDateTime aMTime, bool aRemoveZip) {
1999 bool ret = true;
2000
2001#ifdef __ANDROID__
2002 int nStrip = 0;
2003 if (aStripPath) nStrip = 1;
2004
2005 ret = AndroidUnzip(aZipFile, aTargetDir, nStrip, true);
2006#else
2007 std::unique_ptr<wxZipEntry> entry(new wxZipEntry());
2008
2009 do {
2010 wxLogMessage(_T("chartdldr_pi: Going to extract '") + aZipFile + _T("'."));
2011 wxFileInputStream in(aZipFile);
2012
2013 if (!in) {
2014 wxLogMessage(_T("Can not open file '") + aZipFile + _T("'."));
2015 ret = false;
2016 break;
2017 }
2018 wxZipInputStream zip(in);
2019 ret = false;
2020
2021 while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
2022 // access meta-data
2023 wxString name = entry->GetName();
2024 if (aStripPath) {
2025 wxFileName fn(name);
2026 /* We can completly replace the entry path */
2027 // fn.SetPath(aTargetDir);
2028 // name = fn.GetFullPath();
2029 /* Or only remove the first dir (eg. ENC_ROOT) */
2030 if (fn.GetDirCount() > 0) fn.RemoveDir(0);
2031 name = aTargetDir + wxFileName::GetPathSeparator() + fn.GetFullPath();
2032 } else {
2033 name = aTargetDir + wxFileName::GetPathSeparator() + name;
2034 }
2035
2036 // read 'zip' to access the entry's data
2037 if (entry->IsDir()) {
2038 int perm = entry->GetMode();
2039 if (!wxFileName::Mkdir(name, perm, wxPATH_MKDIR_FULL)) {
2040 wxLogMessage(_T("Can not create directory '") + name + _T("'."));
2041 ret = false;
2042 break;
2043 }
2044 } else {
2045 if (!zip.OpenEntry(*entry.get())) {
2046 wxLogMessage(_T("Can not open zip entry '") + entry->GetName() +
2047 _T("'."));
2048 ret = false;
2049 break;
2050 }
2051 if (!zip.CanRead()) {
2052 wxLogMessage(_T("Can not read zip entry '") + entry->GetName() +
2053 _T("'."));
2054 ret = false;
2055 break;
2056 }
2057
2058 wxFileName fn(name);
2059 if (!fn.DirExists()) {
2060 if (!wxFileName::Mkdir(fn.GetPath())) {
2061 wxLogMessage(_T("Can not create directory '") + fn.GetPath() +
2062 _T("'."));
2063 ret = false;
2064 break;
2065 }
2066 }
2067
2068 wxFileOutputStream file(name);
2069
2070 if (!file) {
2071 wxLogMessage(_T("Can not create file '") + name + _T("'."));
2072 ret = false;
2073 break;
2074 }
2075 zip.Read(file);
2076 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2077 ret = true;
2078 }
2079 }
2080
2081 } while (false);
2082
2083 if (aRemoveZip) wxRemoveFile(aZipFile);
2084#endif // Android
2085
2086 return ret;
2087}
2088
2089ChartDldrGuiAddSourceDlg::ChartDldrGuiAddSourceDlg(wxWindow *parent)
2090 : AddSourceDlg(parent) {
2091 wxFileName fn;
2092 fn.SetPath(*GetpSharedDataLocation());
2093 fn.AppendDir(_T("plugins"));
2094 fn.AppendDir(_T("chartdldr_pi"));
2095 fn.AppendDir(_T("data"));
2096
2097 int w = 16; // default for desktop
2098 int h = 16;
2099
2100#ifdef __ANDROID__
2101 w = 6 * g_androidDPmm; // mm nominal size
2102 h = w;
2103
2104 p_buttonIconList = new wxImageList(w, h);
2105
2106 fn.SetFullName(_T("button_right.png"));
2107 wxImage im1(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2108 im1.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2109 p_buttonIconList->Add(im1);
2110
2111 fn.SetFullName(_T("button_right.png"));
2112 wxImage im2(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2113 im2.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2114 p_buttonIconList->Add(im2);
2115
2116 fn.SetFullName(_T("button_down.png"));
2117 wxImage im3(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2118 im3.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2119 p_buttonIconList->Add(im3);
2120
2121 fn.SetFullName(_T("button_down.png"));
2122 wxImage im4(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2123 im4.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2124 p_buttonIconList->Add(im4);
2125
2126 m_treeCtrlPredefSrcs->AssignButtonsImageList(p_buttonIconList);
2127#else
2128 p_iconList = new wxImageList(w, h);
2129
2130 fn.SetFullName(_T("folder.png"));
2131 wxImage ima(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2132 ima.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2133 p_iconList->Add(ima);
2134
2135 fn.SetFullName(_T("file.png"));
2136 wxImage imb(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2137 imb.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2138 p_iconList->Add(imb);
2139
2140 m_treeCtrlPredefSrcs->AssignImageList(p_iconList);
2141#endif /* __ANDROID__ */
2142
2143 m_treeCtrlPredefSrcs->SetIndent(w);
2144
2145 m_base_path = wxEmptyString;
2146 m_last_path = wxEmptyString;
2147 LoadSources();
2148 m_nbChoice->SetSelection(0);
2149 // m_treeCtrlPredefSrcs->ExpandAll();
2150
2151 Fit();
2152
2153 applyStyle();
2154}
2155
2156bool ChartDldrGuiAddSourceDlg::LoadSources() {
2157 wxTreeItemId tree = m_treeCtrlPredefSrcs->AddRoot(_T("root"));
2158
2159 wxFileName fn;
2161 fn.SetFullName(_T("chartdldr_pi-chart_sources.xml"));
2162 if (!fn.FileExists()) {
2163 fn.SetPath(*GetpSharedDataLocation());
2164 fn.AppendDir(_T("plugins"));
2165 fn.AppendDir(_T("chartdldr_pi"));
2166 fn.AppendDir(_T("data"));
2167 fn.SetFullName(_T("chart_sources.xml"));
2168 if (!fn.FileExists()) {
2169 wxLogMessage(wxString::Format(
2170 _T("Error: chartdldr_pi::LoadSources() %s not found!"),
2171 fn.GetFullPath().c_str()));
2172 return false;
2173 }
2174 }
2175 wxString path = fn.GetFullPath();
2176
2178 bool ret = doc->load_file(path.mb_str());
2179 if (ret) {
2180 pugi::xml_node root = doc->first_child();
2181
2182 for (pugi::xml_node element = root.first_child(); element;
2183 element = element.next_sibling()) {
2184 if (!strcmp(element.name(), "sections")) {
2185 LoadSections(tree, element);
2186 }
2187 }
2188 }
2189 wxDELETE(doc);
2190 return true;
2191}
2192
2193bool ChartDldrGuiAddSourceDlg::LoadSections(const wxTreeItemId &root,
2194 pugi::xml_node &node) {
2195 for (pugi::xml_node element = node.first_child(); element;
2196 element = element.next_sibling()) {
2197 if (!strcmp(element.name(), "section")) {
2198 LoadSection(root, element);
2199 }
2200 }
2201 return true;
2202}
2203
2204bool ChartDldrGuiAddSourceDlg::LoadSection(const wxTreeItemId &root,
2205 pugi::xml_node &node) {
2206 wxTreeItemId item;
2207 for (pugi::xml_node element = node.first_child(); element;
2208 element = element.next_sibling()) {
2209 if (!strcmp(element.name(), "name")) {
2210 item = m_treeCtrlPredefSrcs->AppendItem(
2211 root, wxString::FromUTF8(element.first_child().value()), 0, 0);
2212
2213 wxFont *pFont = OCPNGetFont(_("Dialog"));
2214 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(item, *pFont);
2215 }
2216 if (!strcmp(element.name(), "sections")) LoadSections(item, element);
2217 if (!strcmp(element.name(), "catalogs")) LoadCatalogs(item, element);
2218 }
2219
2220 return true;
2221}
2222
2223bool ChartDldrGuiAddSourceDlg::LoadCatalogs(const wxTreeItemId &root,
2224 pugi::xml_node &node) {
2225 for (pugi::xml_node element = node.first_child(); element;
2226 element = element.next_sibling()) {
2227 if (!strcmp(element.name(), "catalog")) LoadCatalog(root, element);
2228 }
2229
2230 return true;
2231}
2232
2233bool ChartDldrGuiAddSourceDlg::LoadCatalog(const wxTreeItemId &root,
2234 pugi::xml_node &node) {
2235 wxString name, type, location, dir;
2236 for (pugi::xml_node element = node.first_child(); element;
2237 element = element.next_sibling()) {
2238 if (!strcmp(element.name(), "name"))
2239 name = wxString::FromUTF8(element.first_child().value());
2240 else if (!strcmp(element.name(), "type"))
2241 type = wxString::FromUTF8(element.first_child().value());
2242 else if (!strcmp(element.name(), "location"))
2243 location = wxString::FromUTF8(element.first_child().value());
2244 else if (!strcmp(element.name(), "dir"))
2245 dir = wxString::FromUTF8(element.first_child().value());
2246 }
2247 ChartSource *cs = new ChartSource(name, location, dir);
2248 wxTreeItemId id = m_treeCtrlPredefSrcs->AppendItem(root, name, 1, 1, cs);
2249
2250 wxFont *pFont = OCPNGetFont(_("Dialog"));
2251 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(id, *pFont);
2252
2253 return true;
2254}
2255
2256ChartDldrGuiAddSourceDlg::~ChartDldrGuiAddSourceDlg() {}
2257
2258wxString ChartDldrGuiAddSourceDlg::FixPath(wxString path) {
2259 wxString sep(wxFileName::GetPathSeparator());
2260 wxString s = path;
2261 s.Replace(_T("/"), sep, true);
2262 s.Replace(_T(USERDATA), m_base_path);
2263 s.Replace(sep + sep, sep);
2264 return s;
2265}
2266
2267void ChartDldrGuiAddSourceDlg::OnChangeType(wxCommandEvent &event) {
2268 m_treeCtrlPredefSrcs->Enable(m_nbChoice->GetSelection() == 0);
2269 m_tSourceName->Enable(m_nbChoice->GetSelection() == 1);
2270 m_tChartSourceUrl->Enable(m_nbChoice->GetSelection() == 1);
2271}
2272
2273void ChartDldrGuiAddSourceDlg::OnSourceSelected(wxTreeEvent &event) {
2274 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2275 ChartSource *cs = (ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2276 if (cs) {
2277 m_dirExpanded = FixPath(cs->GetDir());
2278
2279 m_tSourceName->SetValue(cs->GetName());
2280 m_tChartSourceUrl->SetValue(cs->GetUrl());
2281 if (m_tcChartDirectory->GetValue() == m_last_path) {
2282 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2283 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2284
2285 m_buttonChartDirectory->Enable();
2286 m_last_path = m_tcChartDirectory->GetValue();
2287 }
2288 }
2289 event.Skip();
2290}
2291
2292void ChartDldrGuiAddSourceDlg::SetSourceEdit(std::unique_ptr<ChartSource> &cs) {
2293 m_nbChoice->SetSelection(1);
2294 m_tChartSourceUrl->Enable();
2295 m_treeCtrlPredefSrcs->Disable();
2296 m_tSourceName->SetValue(cs->GetName());
2297 m_tChartSourceUrl->SetValue(cs->GetUrl());
2298 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2299 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2300
2301 m_buttonChartDirectory->Enable();
2302}
2303
2304ChartDldrPrefsDlgImpl::ChartDldrPrefsDlgImpl(wxWindow *parent)
2305 : ChartDldrPrefsDlg(parent) {}
2306
2307ChartDldrPrefsDlgImpl::~ChartDldrPrefsDlgImpl() {}
2308
2309void ChartDldrPrefsDlgImpl::SetPath(const wxString path) {
2310 // if( !wxDirExists(path) )
2311 // if( !wxFileName::Mkdir(path, 0755, wxPATH_MKDIR_FULL) )
2312 //{
2313 // OCPNMessageBox_PlugIn(this, wxString::Format(_("Directory %s can't be
2314 // created."), m_dpDefaultDir->GetTextCtrlValue().c_str()), _("Chart
2315 // Downloader")); return;
2316 //}
2317 m_tcDefaultDir->SetValue(path);
2318}
2319
2320void ChartDldrPrefsDlgImpl::GetPreferences(bool &preselect_new,
2321 bool &preselect_updated,
2322 bool &bulk_update) {
2323 preselect_new = m_cbSelectNew->GetValue();
2324 preselect_updated = m_cbSelectUpdated->GetValue();
2325 bulk_update = m_cbBulkUpdate->GetValue();
2326}
2327void ChartDldrPrefsDlgImpl::SetPreferences(bool preselect_new,
2328 bool preselect_updated,
2329 bool bulk_update) {
2330 m_cbSelectNew->SetValue(preselect_new);
2331 m_cbSelectUpdated->SetValue(preselect_updated);
2332 m_cbBulkUpdate->SetValue(bulk_update);
2333}
2334
2335void ChartDldrGuiAddSourceDlg::OnOkClick(wxCommandEvent &event) {
2336 wxString msg = wxEmptyString;
2337
2338 if (m_nbChoice->GetSelection() == 0) {
2339 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2340 if (m_treeCtrlPredefSrcs->GetSelection().IsOk()) {
2341 ChartSource *cs =
2342 (ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2343 if (!cs)
2344 msg +=
2345 _("You must select one of the predefined chart sources or create "
2346 "one of your own.\n");
2347 } else
2348 msg +=
2349 _("You must select one of the predefined chart sources or create one "
2350 "of your own.\n");
2351 }
2352 if (m_nbChoice->GetSelection() == 1 &&
2353 m_tSourceName->GetValue() == wxEmptyString)
2354 msg += _("The chart source must have a name.\n");
2355 wxURI url(m_tChartSourceUrl->GetValue());
2356 if (m_nbChoice->GetSelection() == 1 &&
2357 (m_tChartSourceUrl->GetValue() == wxEmptyString ||
2358 !ValidateUrl(m_tChartSourceUrl->GetValue())))
2359 msg += _("The chart source must have a valid URL.\n");
2360 if (m_tcChartDirectory->GetValue() == wxEmptyString)
2361 msg += _("You must select a local folder to store the charts.\n");
2362 else if (!wxDirExists(m_tcChartDirectory->GetValue()))
2363 if (!wxFileName::Mkdir(m_tcChartDirectory->GetValue(), 0755,
2364 wxPATH_MKDIR_FULL))
2365 msg += wxString::Format(_("Directory %s can't be created."),
2366 m_tcChartDirectory->GetValue().c_str()) +
2367 _T("\n");
2368
2369 if (msg != wxEmptyString)
2370 OCPNMessageBox_PlugIn(this, msg, _("Chart source definition problem"),
2371 wxOK | wxCENTRE | wxICON_ERROR);
2372 else {
2373 event.Skip();
2374 SetReturnCode(wxID_OK);
2375 EndModal(wxID_OK);
2376 }
2377}
2378
2379void ChartDldrGuiAddSourceDlg::OnCancelClick(wxCommandEvent &event) {
2380 SetReturnCode(wxID_CANCEL);
2381 EndModal(wxID_CANCEL);
2382}
2383
2384void ChartDldrPrefsDlgImpl::OnOkClick(wxCommandEvent &event) {
2385 if (!wxDirExists(m_tcDefaultDir->GetValue())) {
2386 if (!wxFileName::Mkdir(m_tcDefaultDir->GetValue(), 0755,
2387 wxPATH_MKDIR_FULL)) {
2388 OCPNMessageBox_PlugIn(
2389 this,
2390 wxString::Format(_("Directory %s can't be created."),
2391 m_tcDefaultDir->GetValue().c_str()),
2392 _("Chart Downloader"));
2393 return;
2394 }
2395 }
2396
2397 if (g_pi) {
2398 g_pi->UpdatePrefs(this);
2399 }
2400
2401 event.Skip();
2402 EndModal(wxID_OK);
2403
2404 // Hide();
2405 // Close();
2406}
2407
2408void ChartDldrPrefsDlg::OnCancelClick(wxCommandEvent &event) {
2409 event.Skip();
2410 EndModal(wxID_CANCEL);
2411 // Close();
2412}
2413
2414void ChartDldrPrefsDlg::OnOkClick(wxCommandEvent &event) {
2415 event.Skip();
2416 // Close();
2417}
2418
2419bool ChartDldrGuiAddSourceDlg::ValidateUrl(const wxString Url,
2420 bool catalog_xml) {
2421 wxRegEx re;
2422 if (catalog_xml)
2423 re.Compile(
2424 _T("^https?\\://[a-zA-Z0-9\\./_-]*\\.[xX][mM][lL]$")); // TODO: wxRegEx
2425 // sucks a bit,
2426 // this RE is
2427 // way too naive
2428 else
2429 re.Compile(
2430 _T("^https?\\://[a-zA-Z0-9\\./_-]*$")); // TODO: wxRegEx sucks a bit,
2431 // this RE is way too naive
2432 return re.Matches(Url);
2433}
2434
2435void ChartDldrPanelImpl::onDLEvent(OCPN_downloadEvent &ev) {
2436 // wxString msg;
2437 // msg.Printf(_T("onDLEvent %d %d"),ev.getDLEventCondition(),
2438 // ev.getDLEventStatus()); wxLogMessage(msg);
2439
2440 switch (ev.getDLEventCondition()) {
2442 m_bTransferComplete = true;
2443 m_bTransferSuccess =
2444 (ev.getDLEventStatus() == OCPN_DL_NO_ERROR) ? true : false;
2445 break;
2446
2448 if (ev.getTransferred() > m_transferredsize) {
2449 m_totalsize = ev.getTotal();
2450 m_transferredsize = ev.getTransferred();
2451 }
2452
2453 break;
2454 default:
2455 break;
2456 }
2457 wxYieldIfNeeded();
2458}
Class AddSourceDlg.
Implementing ChartDldrPanel.
Class ChartDldrPanel.
Class ChartDldrPrefsDlg.
int GetPlugInVersionMinor()
Returns the minor version number of the plugin itself.
wxString GetLongDescription()
Get detailed plugin information.
wxBitmap * GetPlugInBitmap()
Get the plugin's icon bitmap.
void ShowPreferencesDialog(wxWindow *parent)
Shows the plugin preferences dialog.
int Init(void)
Initialize the plugin and declare its capabilities.
int GetAPIVersionMinor()
Returns the minor version number of the plugin API that this plugin supports.
bool DeInit(void)
Clean up plugin resources.
void OnCloseToolboxPanel(int page_sel, int ok_apply_cancel)
Handles preference page closure.
int GetAPIVersionMajor()
Returns the major version number of the plugin API that this plugin supports.
wxString GetShortDescription()
Get a brief description of the plugin.
wxString GetCommonName()
Get the plugin's common (short) name.
int GetPlugInVersionMajor()
Returns the major version number of the plugin itself.
void OnSetupOptions(void)
Allows plugin to add pages to global Options dialog.
Base class for OpenCPN plugins.
@ OCPN_DL_EVENT_TYPE_PROGRESS
Download progress update.
@ OCPN_DL_EVENT_TYPE_END
Download has completed.
_OCPN_DLStatus
Status codes for HTTP file download operations.
@ OCPN_DL_NO_ERROR
Download completed successfully.
@ OCPN_DL_USER_TIMEOUT
Download timed out waiting for user action.
@ OCPN_DL_STARTED
Download has begun but not yet complete.
@ OCPN_DL_FAILED
Download failed (general error)
@ OCPN_DL_UNKNOWN
Unknown or uninitialized status.
@ OCPN_DL_ABORTED
Download was cancelled by user.
@ OCPN_DLDS_URL
The dialog shows the URL involved in the transfer.
@ OCPN_DLDS_AUTO_CLOSE
The dialog auto closes when transfer is complete.
@ OCPN_DLDS_CAN_ABORT
The transfer can be aborted by the user.
@ OCPN_DLDS_ESTIMATED_TIME
The dialog shows the estimated total time.
@ OCPN_DLDS_SIZE
The dialog shows the size of the resource to download/upload.
@ OCPN_DLDS_REMAINING_TIME
The dialog shows the remaining time.
@ OCPN_DLDS_SPEED
The dialog shows the transfer speed.
@ OCPN_DLDS_ELAPSED_TIME
The dialog shows the elapsed time.
@ OCPN_DLDS_CAN_PAUSE
The transfer can be paused.
#define INSTALLS_TOOLBOX_PAGE
Plugin will add pages to the toolbox/settings dialog.
#define WANTS_PREFERENCES
Plugin will add page(s) to global preferences dialog.
#define WANTS_CONFIG
Plugin requires persistent configuration storage.
@ PI_OPTIONS_PARENT_CHARTS
Charts section.
wxString * GetpSharedDataLocation(void)
Gets shared application data location.
wxWindow * GetOCPNCanvasWindow()
Gets OpenCPN's main canvas window.
wxFont * OCPNGetFont(wxString TextElement, int default_size)
Gets a font for UI elements.
wxFont GetOCPNGUIScaledFont_PlugIn(wxString item)
Gets a uniquely scaled font copy for responsive UI elements.
wxScrolledWindow * AddOptionsPage(OptionsParentPI parent, wxString title)
Adds a new preferences page to OpenCPN options dialog.
void AddChartDirectory(wxString &path)
Adds a chart directory to OpenCPN's chart database.
wxArrayString GetChartDBDirArrayString()
Gets chart database directory list.
bool DeleteOptionsPage(wxScrolledWindow *page)
Remove a previously added options page.
bool AddLocaleCatalog(wxString catalog)
Adds a locale catalog for translations.
wxFileConfig * GetOCPNConfigObject(void)
Gets OpenCPN's configuration object.
void ForceChartDBUpdate()
Forces an update of the chart database.
wxString * GetpPrivateApplicationDataLocation(void)
Gets private application data directory.