From 092e241f8937f93221e10255305d0dcdc2edd4ac Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Mon, 1 Jun 2026 15:59:50 -0400 Subject: [PATCH] Implement stream-based download_file tool for downloading media files directly to flash or SD card --- download_util.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ mcp_server.py | 25 ++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 download_util.py diff --git a/download_util.py b/download_util.py new file mode 100644 index 0000000..c7009cc --- /dev/null +++ b/download_util.py @@ -0,0 +1,68 @@ +import os +import urequests +from sd_util import SDCardManager + +def download_file(url, dest_filename, use_sd=True): + """Downloads a file from a URL over the network in memory-efficient chunks. + + If use_sd is True, it automatically mounts the microSD card and saves to /sd/dest_filename. + Otherwise, it saves to the local flash storage. + + Returns: + str: The full path to the downloaded file on success, None on failure. + """ + dest_path = dest_filename + sd_manager = None + + if use_sd: + sd_manager = SDCardManager(mount_point='/sd') + if not sd_manager.mount(): + print("Download Error: Could not mount SD card.") + return None + dest_path = f"/sd/{dest_filename}" + + print(f"Starting download from: {url} -> {dest_path}") + + try: + res = urequests.get(url, stream=True) + except Exception as e: + print(f"HTTP Connection failed: {e}") + return None + + if res.status_code != 200: + print(f"HTTP Error: Received status code {res.status_code}") + res.close() + return None + + try: + # Read from socket stream in chunks to avoid OutOfMemory errors + chunk_size = 4096 + total_downloaded = 0 + + with open(dest_path, 'wb') as f: + while True: + # Read chunk from the raw socket connection stream + chunk = res.raw.read(chunk_size) + if not chunk: + break + f.write(chunk) + total_downloaded += len(chunk) + + # Dynamic logging + if total_downloaded % (chunk_size * 25) == 0: + print(f"Downloaded {total_downloaded // 1024} KB...") + + print(f"Download complete! Saved {total_downloaded} bytes to '{dest_path}'.") + return dest_path + + except Exception as e: + print(f"Error writing to file: {e}") + # Clean up partial file on failure + try: + os.remove(dest_path) + except: + pass + return None + + finally: + res.close() diff --git a/mcp_server.py b/mcp_server.py index dfdb487..69d70c7 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -263,6 +263,19 @@ class MCPServer: }, "required": ["wav_base64"] } + }, + { + "name": "download_file", + "description": "Download a file from a URL over Wi-Fi directly to the board storage or microSD card.", + "inputSchema": { + "type": "object", + "properties": { + "url": {"type": "string", "description": "The URL of the file to download"}, + "filename": {"type": "string", "description": "The destination filename (e.g. 'podcast1.wav')"}, + "use_sd": {"type": "boolean", "description": "Save to microSD card if true, or local flash if false (default true)", "default": True} + }, + "required": ["url", "filename"] + } } ] }, @@ -522,5 +535,17 @@ class MCPServer: else: raise RuntimeError("Failed to play decoded audio stream. Check format (16kHz 16-bit PCM WAV recommended).") + elif name == "download_file": + url = str(args.get("url")) + filename = str(args.get("filename")) + use_sd = bool(args.get("use_sd", True)) + + from download_util import download_file + saved_path = download_file(url, filename, use_sd=use_sd) + if saved_path: + return f"Successfully downloaded file to '{saved_path}'." + else: + raise RuntimeError(f"Failed to download file from '{url}'.") + else: raise ValueError(f"Unknown tool: {name}")