|
@@ -22,15 +22,17 @@ import os
|
|
import sys
|
|
import sys
|
|
import tempfile
|
|
import tempfile
|
|
import shutil
|
|
import shutil
|
|
|
|
+import textwrap
|
|
import time
|
|
import time
|
|
|
|
|
|
try:
|
|
try:
|
|
from urllib2 import HTTPError, URLError
|
|
from urllib2 import HTTPError, URLError
|
|
- from urllib import urlopen, urlretrieve
|
|
|
|
|
|
+ from urllib import request, urlopen, urlretrieve
|
|
except ImportError:
|
|
except ImportError:
|
|
# there is also HTTPException, perhaps change to list
|
|
# there is also HTTPException, perhaps change to list
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import urlopen, urlretrieve
|
|
from urllib.request import urlopen, urlretrieve
|
|
|
|
+ from urllib import request
|
|
|
|
|
|
import wx
|
|
import wx
|
|
from wx.lib.newevent import NewEvent
|
|
from wx.lib.newevent import NewEvent
|
|
@@ -87,13 +89,18 @@ class DownloadError(Exception):
|
|
"""Error happened during download or when processing the file"""
|
|
"""Error happened during download or when processing the file"""
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
+
|
|
class RedirectText(object):
|
|
class RedirectText(object):
|
|
def __init__(self, window):
|
|
def __init__(self, window):
|
|
self.out = window
|
|
self.out = window
|
|
|
|
|
|
def write(self, string):
|
|
def write(self, string):
|
|
try:
|
|
try:
|
|
- wx.CallAfter(self.out.SetLabel, string)
|
|
|
|
|
|
+ if self.out:
|
|
|
|
+ string = self._wrap_string(string)
|
|
|
|
+ heigth = self._get_heigth(string)
|
|
|
|
+ wx.CallAfter(self.out.SetLabel, string)
|
|
|
|
+ self._resize(heigth)
|
|
except:
|
|
except:
|
|
# window closed -> PyDeadObjectError
|
|
# window closed -> PyDeadObjectError
|
|
pass
|
|
pass
|
|
@@ -101,6 +108,43 @@ class RedirectText(object):
|
|
def flush(self):
|
|
def flush(self):
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
+ def _wrap_string(self, string, width=40):
|
|
|
|
+ """Wrap string
|
|
|
|
+
|
|
|
|
+ :param str string: input string
|
|
|
|
+ :param int width: maximum length allowed of the wrapped lines
|
|
|
|
+
|
|
|
|
+ :return str: newline-separated string
|
|
|
|
+ """
|
|
|
|
+ wrapper = textwrap.TextWrapper(width=width)
|
|
|
|
+ return wrapper.fill(text=string)
|
|
|
|
+
|
|
|
|
+ def _get_heigth(self, string):
|
|
|
|
+ """Get widget new heigth
|
|
|
|
+
|
|
|
|
+ :param str string: input string
|
|
|
|
+
|
|
|
|
+ :return int: widget heigth
|
|
|
|
+ """
|
|
|
|
+ n_lines = string.count('\n')
|
|
|
|
+ attr = self.out.GetClassDefaultAttributes()
|
|
|
|
+ font_size = attr.font.GetPointSize()
|
|
|
|
+ heigth = int((n_lines + 2) * font_size // 0.75) # 1 px = 0.75 pt
|
|
|
|
+ return heigth
|
|
|
|
+
|
|
|
|
+ def _resize(self, heigth=-1):
|
|
|
|
+ """Resize widget heigth
|
|
|
|
+
|
|
|
|
+ :param int heigth: widget heigth
|
|
|
|
+ """
|
|
|
|
+ wx.CallAfter(self.out.GetParent().SetMinSize, (-1, -1))
|
|
|
|
+ wx.CallAfter(self.out.SetMinSize, (-1, heigth))
|
|
|
|
+ wx.CallAfter(
|
|
|
|
+ self.out.GetParent().parent.sizer.Fit,
|
|
|
|
+ self.out.GetParent().parent,
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+
|
|
# copy from g.extension, potentially move to library
|
|
# copy from g.extension, potentially move to library
|
|
def move_extracted_files(extract_dir, target_dir, files):
|
|
def move_extracted_files(extract_dir, target_dir, files):
|
|
"""Fix state of extracted file by moving them to different diretcory
|
|
"""Fix state of extracted file by moving them to different diretcory
|
|
@@ -177,7 +221,9 @@ def reporthook(count, block_size, total_size):
|
|
global start_time
|
|
global start_time
|
|
if count == 0:
|
|
if count == 0:
|
|
start_time = time.time()
|
|
start_time = time.time()
|
|
- sys.stdout.write("Download in progress, wait until it is finished\n0%")
|
|
|
|
|
|
+ sys.stdout.write(
|
|
|
|
+ _('Download in progress, wait until it is finished 0%'),
|
|
|
|
+ )
|
|
return
|
|
return
|
|
if count % 100 != 0: # be less verbose
|
|
if count % 100 != 0: # be less verbose
|
|
return
|
|
return
|
|
@@ -185,9 +231,13 @@ def reporthook(count, block_size, total_size):
|
|
progress_size = int(count * block_size)
|
|
progress_size = int(count * block_size)
|
|
speed = int(progress_size / (1024 * duration))
|
|
speed = int(progress_size / (1024 * duration))
|
|
percent = int(count * block_size * 100 / total_size)
|
|
percent = int(count * block_size * 100 / total_size)
|
|
- sys.stdout.write("Download in progress, wait until it is finished\n{0}%, {1} MB, {2} KB/s, {3:.0f} seconds passed".format(
|
|
|
|
- percent, progress_size / (1024 * 1024), speed, duration
|
|
|
|
- ))
|
|
|
|
|
|
+ sys.stdout.write(
|
|
|
|
+ _("Download in progress, wait until it is finished "
|
|
|
|
+ "{0}%, {1} MB, {2} KB/s, {3:.0f} seconds passed".format(
|
|
|
|
+ percent, progress_size / (1024 * 1024), speed, duration,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ )
|
|
|
|
|
|
# based on g.extension, potentially move to library
|
|
# based on g.extension, potentially move to library
|
|
def download_and_extract(source):
|
|
def download_and_extract(source):
|
|
@@ -195,9 +245,27 @@ def download_and_extract(source):
|
|
tmpdir = tempfile.mkdtemp()
|
|
tmpdir = tempfile.mkdtemp()
|
|
Debug.msg(1, 'Tmpdir: {}'.format(tmpdir))
|
|
Debug.msg(1, 'Tmpdir: {}'.format(tmpdir))
|
|
directory = os.path.join(tmpdir, 'location')
|
|
directory = os.path.join(tmpdir, 'location')
|
|
|
|
+ http_error_message = _(
|
|
|
|
+ "Download file from <{url}>, "
|
|
|
|
+ "return status code {code}, "
|
|
|
|
+ )
|
|
|
|
+ url_error_message = _(
|
|
|
|
+ "Download file from <{url}>, "
|
|
|
|
+ "failed. Check internet connection."
|
|
|
|
+ )
|
|
if source.endswith('.zip'):
|
|
if source.endswith('.zip'):
|
|
archive_name = os.path.join(tmpdir, 'location.zip')
|
|
archive_name = os.path.join(tmpdir, 'location.zip')
|
|
- filename, headers = urlretrieve(source, archive_name, reporthook)
|
|
|
|
|
|
+ try:
|
|
|
|
+ filename, headers = urlretrieve(source, archive_name, reporthook)
|
|
|
|
+ except HTTPError as err:
|
|
|
|
+ raise DownloadError(
|
|
|
|
+ http_error_message.format(
|
|
|
|
+ url=source,
|
|
|
|
+ code=err,
|
|
|
|
+ ),
|
|
|
|
+ )
|
|
|
|
+ except URLError:
|
|
|
|
+ raise DownloadError(url_error_message.format(url=source))
|
|
if headers.get('content-type', '') != 'application/zip':
|
|
if headers.get('content-type', '') != 'application/zip':
|
|
raise DownloadError(
|
|
raise DownloadError(
|
|
_("Download of <{url}> failed"
|
|
_("Download of <{url}> failed"
|
|
@@ -211,8 +279,17 @@ def download_and_extract(source):
|
|
else:
|
|
else:
|
|
ext = source.rsplit('.', 1)[1]
|
|
ext = source.rsplit('.', 1)[1]
|
|
archive_name = os.path.join(tmpdir, 'location.' + ext)
|
|
archive_name = os.path.join(tmpdir, 'location.' + ext)
|
|
- urlretrieve(source, archive_name, reporthook)
|
|
|
|
- # TODO: error handling for urlretrieve
|
|
|
|
|
|
+ try:
|
|
|
|
+ urlretrieve(source, archive_name, reporthook)
|
|
|
|
+ except HTTPError as err:
|
|
|
|
+ raise DownloadError(
|
|
|
|
+ http_error_message.format(
|
|
|
|
+ url=source,
|
|
|
|
+ code=err,
|
|
|
|
+ ),
|
|
|
|
+ )
|
|
|
|
+ except URLError:
|
|
|
|
+ raise DownloadError(url_error_message.format(url=source))
|
|
extract_tar(name=archive_name, directory=directory, tmpdir=tmpdir)
|
|
extract_tar(name=archive_name, directory=directory, tmpdir=tmpdir)
|
|
else:
|
|
else:
|
|
# probably programmer error
|
|
# probably programmer error
|
|
@@ -285,10 +362,13 @@ class LocationDownloadPanel(wx.Panel):
|
|
"""
|
|
"""
|
|
wx.Panel.__init__(self, parent=parent)
|
|
wx.Panel.__init__(self, parent=parent)
|
|
|
|
|
|
|
|
+ self.parent = parent
|
|
self._last_downloaded_location_name = None
|
|
self._last_downloaded_location_name = None
|
|
self._download_in_progress = False
|
|
self._download_in_progress = False
|
|
self.database = database
|
|
self.database = database
|
|
self.locations = locations
|
|
self.locations = locations
|
|
|
|
+ self._abort_btn_label = _('Abort')
|
|
|
|
+ self._abort_btn_tooltip = _('Abort download location')
|
|
|
|
|
|
self.label = StaticText(
|
|
self.label = StaticText(
|
|
parent=self,
|
|
parent=self,
|
|
@@ -300,11 +380,7 @@ class LocationDownloadPanel(wx.Panel):
|
|
self.choice = wx.Choice(parent=self, choices=choices)
|
|
self.choice = wx.Choice(parent=self, choices=choices)
|
|
|
|
|
|
self.choice.Bind(wx.EVT_CHOICE, self.OnChangeChoice)
|
|
self.choice.Bind(wx.EVT_CHOICE, self.OnChangeChoice)
|
|
-
|
|
|
|
- self.download_button = Button(parent=self, id=wx.ID_ANY,
|
|
|
|
- label=_("Do&wnload"))
|
|
|
|
- self.download_button.SetToolTip(_("Download selected location"))
|
|
|
|
- self.download_button.Bind(wx.EVT_BUTTON, self.OnDownload)
|
|
|
|
|
|
+ self.parent.download_button.Bind(wx.EVT_BUTTON, self.OnDownload)
|
|
# TODO: add button for a link to an associated website?
|
|
# TODO: add button for a link to an associated website?
|
|
# TODO: add thumbnail for each location?
|
|
# TODO: add thumbnail for each location?
|
|
|
|
|
|
@@ -338,13 +414,6 @@ class LocationDownloadPanel(wx.Panel):
|
|
flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10)
|
|
flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10)
|
|
vertical.Add(self.choice, proportion=0,
|
|
vertical.Add(self.choice, proportion=0,
|
|
flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10)
|
|
flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10)
|
|
-
|
|
|
|
- button_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
- button_sizer.AddStretchSpacer()
|
|
|
|
- button_sizer.Add(self.download_button, proportion=0)
|
|
|
|
-
|
|
|
|
- vertical.Add(button_sizer, proportion=0,
|
|
|
|
- flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT | wx.ALIGN_RIGHT, border=10)
|
|
|
|
vertical.AddStretchSpacer()
|
|
vertical.AddStretchSpacer()
|
|
vertical.Add(self.message, proportion=0,
|
|
vertical.Add(self.message, proportion=0,
|
|
flag=wx.ALIGN_CENTER_VERTICAL |
|
|
flag=wx.ALIGN_CENTER_VERTICAL |
|
|
@@ -355,14 +424,28 @@ class LocationDownloadPanel(wx.Panel):
|
|
self.Layout()
|
|
self.Layout()
|
|
self.SetMinSize(self.GetBestSize())
|
|
self.SetMinSize(self.GetBestSize())
|
|
|
|
|
|
|
|
+ def _change_download_btn_label(self, label=_('Do&wnload'),
|
|
|
|
+ tooltip=_('Download selected location')):
|
|
|
|
+ """Change download button label/tooltip"""
|
|
|
|
+ if self.parent.download_button:
|
|
|
|
+ self.parent.download_button.SetLabel(label)
|
|
|
|
+ self.parent.download_button.SetToolTip(tooltip)
|
|
|
|
+
|
|
def OnDownload(self, event):
|
|
def OnDownload(self, event):
|
|
"""Handle user-initiated action of download"""
|
|
"""Handle user-initiated action of download"""
|
|
- Debug.msg(1, "OnDownload")
|
|
|
|
- if self._download_in_progress:
|
|
|
|
- self._warning(_("Download in progress, wait until it is finished"))
|
|
|
|
- index = self.choice.GetSelection()
|
|
|
|
- self.DownloadItem(self.locations[index])
|
|
|
|
- self.download_button.Enable(False)
|
|
|
|
|
|
+ button_label = self.parent.download_button.GetLabel()
|
|
|
|
+ if button_label in (_('Download'), _('Do&wnload')) :
|
|
|
|
+ self._change_download_btn_label(
|
|
|
|
+ label=self._abort_btn_label,
|
|
|
|
+ tooltip=self._abort_btn_tooltip,
|
|
|
|
+ )
|
|
|
|
+ Debug.msg(1, "OnDownload")
|
|
|
|
+ if self._download_in_progress:
|
|
|
|
+ self._warning(_("Download in progress, wait until it is finished"))
|
|
|
|
+ index = self.choice.GetSelection()
|
|
|
|
+ self.DownloadItem(self.locations[index])
|
|
|
|
+ else:
|
|
|
|
+ self.parent.OnCancel()
|
|
|
|
|
|
def DownloadItem(self, item):
|
|
def DownloadItem(self, item):
|
|
"""Download the selected item"""
|
|
"""Download the selected item"""
|
|
@@ -374,6 +457,7 @@ class LocationDownloadPanel(wx.Panel):
|
|
if os.path.exists(destination):
|
|
if os.path.exists(destination):
|
|
self._error(_("Location named <%s> already exists,"
|
|
self._error(_("Location named <%s> already exists,"
|
|
" download canceled") % dirname)
|
|
" download canceled") % dirname)
|
|
|
|
+ self._change_download_btn_label()
|
|
return
|
|
return
|
|
|
|
|
|
def download_complete_callback(event):
|
|
def download_complete_callback(event):
|
|
@@ -386,12 +470,21 @@ class LocationDownloadPanel(wx.Panel):
|
|
self._warning(_("Download completed. The downloaded sample data is listed "
|
|
self._warning(_("Download completed. The downloaded sample data is listed "
|
|
"in the location/mapset tabs upon closing of this window")
|
|
"in the location/mapset tabs upon closing of this window")
|
|
)
|
|
)
|
|
|
|
+ self._change_download_btn_label()
|
|
|
|
+
|
|
|
|
+ def terminate_download_callback(event):
|
|
|
|
+ self._download_in_progress = False
|
|
|
|
+ request.urlcleanup()
|
|
|
|
+ sys.stdout.write("Download aborted")
|
|
|
|
+ self.thread = gThread()
|
|
|
|
+ self._change_download_btn_label()
|
|
|
|
|
|
self._download_in_progress = True
|
|
self._download_in_progress = True
|
|
self._warning(_("Download in progress, wait until it is finished"))
|
|
self._warning(_("Download in progress, wait until it is finished"))
|
|
self.thread.Run(callable=download_location,
|
|
self.thread.Run(callable=download_location,
|
|
url=url, name=dirname, database=self.database,
|
|
url=url, name=dirname, database=self.database,
|
|
- ondone=download_complete_callback)
|
|
|
|
|
|
+ ondone=download_complete_callback,
|
|
|
|
+ onterminate=terminate_download_callback)
|
|
|
|
|
|
def OnChangeChoice(self, event):
|
|
def OnChangeChoice(self, event):
|
|
"""React to user changing the selection"""
|
|
"""React to user changing the selection"""
|
|
@@ -407,6 +500,7 @@ class LocationDownloadPanel(wx.Panel):
|
|
if os.path.exists(destination):
|
|
if os.path.exists(destination):
|
|
self._warning(_("Location named <%s> already exists,"
|
|
self._warning(_("Location named <%s> already exists,"
|
|
" rename it first") % dirname)
|
|
" rename it first") % dirname)
|
|
|
|
+ self.parent.download_button.SetLabel(label=_('Download'))
|
|
return
|
|
return
|
|
else:
|
|
else:
|
|
self._clearMessage()
|
|
self._clearMessage()
|
|
@@ -426,7 +520,7 @@ class LocationDownloadPanel(wx.Panel):
|
|
_clearMessage() when you know that there is everything
|
|
_clearMessage() when you know that there is everything
|
|
correct.
|
|
correct.
|
|
"""
|
|
"""
|
|
- self.message.SetLabel(text)
|
|
|
|
|
|
+ sys.stdout.write(text)
|
|
self.sizer.Layout()
|
|
self.sizer.Layout()
|
|
|
|
|
|
def _error(self, text):
|
|
def _error(self, text):
|
|
@@ -440,7 +534,7 @@ class LocationDownloadPanel(wx.Panel):
|
|
_clearMessage() when you know that there is everything
|
|
_clearMessage() when you know that there is everything
|
|
correct.
|
|
correct.
|
|
"""
|
|
"""
|
|
- self.message.SetLabel(_("Error: {text}").format(text=text))
|
|
|
|
|
|
+ sys.stdout.write(_("Error: {text}").format(text=text))
|
|
self.sizer.Layout()
|
|
self.sizer.Layout()
|
|
|
|
|
|
def _clearMessage(self):
|
|
def _clearMessage(self):
|
|
@@ -463,22 +557,37 @@ class LocationDownloadDialog(wx.Dialog):
|
|
:param title: window title if the default is not appropriate
|
|
:param title: window title if the default is not appropriate
|
|
"""
|
|
"""
|
|
wx.Dialog.__init__(self, parent=parent, title=title)
|
|
wx.Dialog.__init__(self, parent=parent, title=title)
|
|
|
|
+ cancel_button = Button(self, id=wx.ID_CANCEL)
|
|
|
|
+ self.download_button = Button(parent=self, id=wx.ID_ANY,
|
|
|
|
+ label=_("Do&wnload"))
|
|
|
|
+ self.download_button.SetToolTip(_("Download selected location"))
|
|
self.panel = LocationDownloadPanel(parent=self, database=database)
|
|
self.panel = LocationDownloadPanel(parent=self, database=database)
|
|
- close_button = Button(self, id=wx.ID_CLOSE)
|
|
|
|
- # TODO: terminate download process
|
|
|
|
- close_button.Bind(wx.EVT_BUTTON, self.OnClose)
|
|
|
|
|
|
+ cancel_button.Bind(wx.EVT_BUTTON, self.OnCancel)
|
|
|
|
+ self.Bind(wx.EVT_CLOSE, self.OnCancel)
|
|
|
|
|
|
- sizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
- sizer.Add(self.panel, proportion=1, flag=wx.EXPAND)
|
|
|
|
|
|
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
+ self.sizer.Add(self.panel, proportion=1, flag=wx.EXPAND)
|
|
|
|
|
|
button_sizer = wx.StdDialogButtonSizer()
|
|
button_sizer = wx.StdDialogButtonSizer()
|
|
- button_sizer.Add(close_button)
|
|
|
|
|
|
+ button_sizer.Add(
|
|
|
|
+ cancel_button,
|
|
|
|
+ proportion=0,
|
|
|
|
+ flag=wx.EXPAND | wx.LEFT | wx.RIGHT,
|
|
|
|
+ border=5,
|
|
|
|
+ )
|
|
|
|
+ button_sizer.Add(
|
|
|
|
+ self.download_button,
|
|
|
|
+ proportion=0,
|
|
|
|
+ flag=wx.EXPAND | wx.LEFT | wx.RIGHT,
|
|
|
|
+ border=5,
|
|
|
|
+ )
|
|
button_sizer.Realize()
|
|
button_sizer.Realize()
|
|
|
|
|
|
- sizer.Add(button_sizer, proportion=0,
|
|
|
|
- flag=wx.ALIGN_RIGHT | wx.BOTTOM, border=10)
|
|
|
|
- self.SetSizer(sizer)
|
|
|
|
- sizer.Fit(self)
|
|
|
|
|
|
+ self.sizer.Add(button_sizer, proportion=0,
|
|
|
|
+ flag=wx.ALIGN_RIGHT | wx.TOP | wx.BOTTOM,
|
|
|
|
+ border=10)
|
|
|
|
+ self.SetSizer(self.sizer)
|
|
|
|
+ self.sizer.Fit(self)
|
|
|
|
|
|
self.Layout()
|
|
self.Layout()
|
|
|
|
|
|
@@ -486,7 +595,7 @@ class LocationDownloadDialog(wx.Dialog):
|
|
"""Get the name of the last location downloaded by the user"""
|
|
"""Get the name of the last location downloaded by the user"""
|
|
return self.panel.GetLocation()
|
|
return self.panel.GetLocation()
|
|
|
|
|
|
- def OnClose(self, event):
|
|
|
|
|
|
+ def OnCancel(self, event=None):
|
|
if self.panel._download_in_progress:
|
|
if self.panel._download_in_progress:
|
|
# running thread
|
|
# running thread
|
|
dlg = wx.MessageDialog(parent=self,
|
|
dlg = wx.MessageDialog(parent=self,
|
|
@@ -498,11 +607,15 @@ class LocationDownloadDialog(wx.Dialog):
|
|
ret = dlg.ShowModal()
|
|
ret = dlg.ShowModal()
|
|
dlg.Destroy()
|
|
dlg.Destroy()
|
|
|
|
|
|
- # TODO: terminate download process on wx.ID_YES
|
|
|
|
if ret == wx.ID_NO:
|
|
if ret == wx.ID_NO:
|
|
return
|
|
return
|
|
|
|
+ else:
|
|
|
|
+ self.panel.thread.Terminate()
|
|
|
|
+ self.panel._change_download_btn_label()
|
|
|
|
+
|
|
|
|
+ if event:
|
|
|
|
+ self.EndModal(wx.ID_CANCEL)
|
|
|
|
|
|
- self.Close()
|
|
|
|
|
|
|
|
def main():
|
|
def main():
|
|
"""Tests the download dialog"""
|
|
"""Tests the download dialog"""
|