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