Skip to content

Commit b62e115

Browse files
DownloadManager cleanups
1 parent 13747b8 commit b62e115

File tree

4 files changed

+100
-34
lines changed

4 files changed

+100
-34
lines changed

internal_filesystem/builtin/apps/com.micropythonos.appstore/assets/appstore.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ async def download_app_index(self, json_url):
6464
response = await DownloadManager.download_url(json_url)
6565
except Exception as e:
6666
print(f"Failed to download app index: {e}")
67-
self.please_wait_label.set_text(f"Could not download app index from\n{json_url}\nError: {e}")
67+
if DownloadManager.is_network_error(e):
68+
self.please_wait_label.set_text(f"Network error - check your WiFi connection\nand try again.")
69+
else:
70+
self.please_wait_label.set_text(f"Could not download app index from\n{json_url}\nError: {e}")
6871
return
6972
print(f"Got response text: {response[0:20]}")
7073
try:
@@ -203,6 +206,8 @@ async def fetch_badgehub_app_details(self, app_obj):
203206
response = await DownloadManager.download_url(details_url)
204207
except Exception as e:
205208
print(f"Could not download app details from {details_url}: {e}")
209+
if DownloadManager.is_network_error(e):
210+
print("Network error while fetching app details")
206211
return
207212
print(f"Got response text: {response[0:20]}")
208213
try:
@@ -494,7 +499,10 @@ async def download_and_install(self, app_obj, dest_folder):
494499
self.progress_bar.set_value(90, True)
495500
except Exception as e:
496501
print(f"Download failed with exception: {e}")
497-
self.install_label.set_text(f"Download failed")
502+
if DownloadManager.is_network_error(e):
503+
self.install_label.set_text(f"Network error - check WiFi")
504+
else:
505+
self.install_label.set_text(f"Download failed: {str(e)[:30]}")
498506
self.install_button.remove_state(lv.STATE.DISABLED)
499507
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
500508
self.progress_bar.set_value(0, False)

internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def show_update_info(self, timer=None):
212212
except Exception as e:
213213
print(f"show_update_info got exception: {e}")
214214
# Check if this is a network connectivity error
215-
if self.update_downloader._is_network_error(e):
215+
if DownloadManager.is_network_error(e):
216216
# Network not available - wait for it to come back
217217
print("OSUpdate: Network error while checking for updates, waiting for WiFi")
218218
self.set_state(UpdateState.WAITING_WIFI)
@@ -461,35 +461,6 @@ def __init__(self, partition_module=None, connectivity_manager=None, download_ma
461461
print("UpdateDownloader: Partition module not available, will simulate")
462462
self.simulate = True
463463

464-
def _is_network_error(self, exception):
465-
"""Check if exception is a network connectivity error that should trigger pause.
466-
467-
Args:
468-
exception: Exception to check
469-
470-
Returns:
471-
bool: True if this is a recoverable network error
472-
"""
473-
error_str = str(exception).lower()
474-
error_repr = repr(exception).lower()
475-
476-
# Check for common network error codes and messages
477-
# -113 = ECONNABORTED (connection aborted)
478-
# -104 = ECONNRESET (connection reset by peer)
479-
# -110 = ETIMEDOUT (connection timed out)
480-
# -118 = EHOSTUNREACH (no route to host)
481-
# -202 = DNS/connection error (network not ready)
482-
network_indicators = [
483-
'-113', '-104', '-110', '-118', '-202', # Error codes
484-
'econnaborted', 'econnreset', 'etimedout', 'ehostunreach', # Error names
485-
'connection reset', 'connection aborted', # Error messages
486-
'broken pipe', 'network unreachable', 'host unreachable',
487-
'failed to download chunk' # From download_manager OSError(-110)
488-
]
489-
490-
return any(indicator in error_str or indicator in error_repr
491-
for indicator in network_indicators)
492-
493464
def _setup_partition(self):
494465
"""Initialize the OTA partition for writing."""
495466
if not self.simulate and self._current_partition is None:
@@ -678,7 +649,7 @@ async def chunk_handler(chunk):
678649
result['bytes_written'] = self.bytes_written_so_far
679650
result['total_size'] = self.total_size_expected
680651
# Check if this is a network error that should trigger pause
681-
elif self._is_network_error(e):
652+
elif DownloadManager.is_network_error(e):
682653
print(f"UpdateDownloader: Network error ({e}), pausing download")
683654

684655
# Clear buffer - we'll re-download this data on resume

internal_filesystem/lib/mpos/battery_voltage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
_cached_raw_adc = None
1212
_last_read_time = 0
1313
CACHE_DURATION_ADC1_MS = 30000 # 30 seconds (cheaper: no WiFi interference)
14-
CACHE_DURATION_ADC2_MS = 300000 # 300 seconds (expensive: requires WiFi disable)
14+
CACHE_DURATION_ADC2_MS = 600000 # 600 seconds (expensive: requires WiFi disable)
1515
#CACHE_DURATION_ADC2_MS = CACHE_DURATION_ADC1_MS # trigger frequent disconnections for debugging OSUpdate resume
1616

1717
def _is_adc2_pin(pin):

internal_filesystem/lib/mpos/net/download_manager.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
- Progress tracking with 2-decimal precision
1515
- Download speed reporting
1616
- Resume support via Range headers
17+
- Network error detection utilities
18+
19+
Utility Functions:
20+
is_network_error(exception) - Check if error is recoverable network error
21+
get_resume_position(outfile) - Get file size for resume support
1722
1823
Example:
1924
from mpos import DownloadManager
@@ -44,6 +49,19 @@ async def process_chunk(chunk):
4449
"https://example.com/stream",
4550
chunk_callback=process_chunk
4651
)
52+
53+
# Error handling with retry
54+
try:
55+
await DownloadManager.download_url(url, outfile="/sdcard/file.bin")
56+
except Exception as e:
57+
if DownloadManager.is_network_error(e):
58+
# Wait and retry with resume
59+
await asyncio.sleep(2)
60+
resume_from = DownloadManager.get_resume_position("/sdcard/file.bin")
61+
headers = {'Range': f'bytes={resume_from}-'} if resume_from > 0 else None
62+
await DownloadManager.download_url(url, outfile="/sdcard/file.bin", headers=headers)
63+
else:
64+
raise # Fatal error
4765
"""
4866

4967
# Constants
@@ -174,6 +192,75 @@ async def close_session():
174192
_session_lock.release()
175193

176194

195+
def is_network_error(exception):
196+
"""Check if exception is a recoverable network error.
197+
198+
Recognizes common network error codes and messages that indicate
199+
temporary connectivity issues that can be retried.
200+
201+
Args:
202+
exception: Exception to check
203+
204+
Returns:
205+
bool: True if this is a network error that can be retried
206+
207+
Example:
208+
try:
209+
await DownloadManager.download_url(url)
210+
except Exception as e:
211+
if DownloadManager.is_network_error(e):
212+
# Retry or pause
213+
await asyncio.sleep(2)
214+
# retry...
215+
else:
216+
# Fatal error
217+
raise
218+
"""
219+
error_str = str(exception).lower()
220+
error_repr = repr(exception).lower()
221+
222+
# Common network error codes and messages
223+
# -113 = ECONNABORTED (connection aborted)
224+
# -104 = ECONNRESET (connection reset by peer)
225+
# -110 = ETIMEDOUT (connection timed out)
226+
# -118 = EHOSTUNREACH (no route to host)
227+
# -202 = DNS/connection error (network not ready)
228+
network_indicators = [
229+
'-113', '-104', '-110', '-118', '-202', # Error codes
230+
'econnaborted', 'econnreset', 'etimedout', 'ehostunreach', # Error names
231+
'connection reset', 'connection aborted', # Error messages
232+
'broken pipe', 'network unreachable', 'host unreachable',
233+
'failed to download chunk' # From download_manager OSError(-110)
234+
]
235+
236+
return any(indicator in error_str or indicator in error_repr
237+
for indicator in network_indicators)
238+
239+
240+
def get_resume_position(outfile):
241+
"""Get the current size of a partially downloaded file.
242+
243+
Useful for implementing resume functionality with Range headers.
244+
245+
Args:
246+
outfile: Path to file
247+
248+
Returns:
249+
int: File size in bytes, or 0 if file doesn't exist
250+
251+
Example:
252+
resume_from = DownloadManager.get_resume_position("/sdcard/file.bin")
253+
if resume_from > 0:
254+
headers = {'Range': f'bytes={resume_from}-'}
255+
await DownloadManager.download_url(url, outfile=outfile, headers=headers)
256+
"""
257+
try:
258+
import os
259+
return os.stat(outfile)[6] # st_size
260+
except OSError:
261+
return 0
262+
263+
177264
async def download_url(url, outfile=None, total_size=None,
178265
progress_callback=None, chunk_callback=None, headers=None,
179266
speed_callback=None):

0 commit comments

Comments
 (0)