From 6f65bf3f34d41c5a344b5ea4293bd78e2e4c21bb Mon Sep 17 00:00:00 2001 From: Martin Tranberg Date: Thu, 26 Mar 2026 14:51:12 +0100 Subject: [PATCH] Enhance progress reporting with specific actions (Scanning/Checking/Downloading) and immediate counter updates. --- download_sharepoint.py | 53 ++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/download_sharepoint.py b/download_sharepoint.py index 1b52b47..0df2327 100644 --- a/download_sharepoint.py +++ b/download_sharepoint.py @@ -16,19 +16,20 @@ stats = { "failed": 0 } -def print_status(current_item=""): - """Prints a single-line progress update with optional current item.""" +def print_status(current_item="", action="Processing"): + """Prints a single-line progress update with optional current item and action.""" # Build status string status_line = f"Checked: {stats['total_checked']} | Downloaded: {stats['downloaded']} | Skipped: {stats['skipped']} | Failed: {stats['failed']}" if current_item: - # Show a truncated version of the current path to keep it on one line - max_len = 50 + # Show a truncated version of the current path + max_len = 40 display_item = current_item if len(current_item) <= max_len else "..." + current_item[-(max_len-3):] - status_line += f" | Current: {display_item}" + status_line += f" | {action}: {display_item}" # Use \r to return to start of line, and ljust to clear old text - sys.stdout.write(f"\r{status_line.ljust(150)}") + # We use a slightly smaller ljust to avoid wrapping on narrow terminals + sys.stdout.write(f"\r{status_line.ljust(120)}") sys.stdout.flush() def sanitize_filename(name): @@ -104,7 +105,7 @@ def get_drive_id(app, site_id, drive_name): return drive['id'] raise Exception(f"Drive '{drive_name}' not found in site.") -def download_file(download_url, local_path, expected_size): +def download_file(download_url, local_path, expected_size, item_path): try: long_local_path = get_long_path(local_path) @@ -121,6 +122,9 @@ def download_file(download_url, local_path, expected_size): elif not os.path.isdir(target_dir): return False, f"Parent path exists but is not a directory: {target_dir}" + # Update status before starting actual download + print_status(item_path, action="Downloading") + response = requests.get(download_url, stream=True, timeout=60) response.raise_for_status() with open(long_local_path, 'wb') as f: @@ -139,7 +143,7 @@ def download_file(download_url, local_path, expected_size): def download_folder_recursive(app, drive_id, item_path, local_root_path, report): try: # Show progress for every folder we enter - print_status(item_path) + print_status(item_path, action="Scanning") headers = get_headers(app) encoded_path = quote(item_path) @@ -162,32 +166,40 @@ def download_folder_recursive(app, drive_id, item_path, local_root_path, report) download_folder_recursive(app, drive_id, sub_item_path, local_path, report) elif 'file' in item: stats["total_checked"] += 1 - download_url = item.get('@microsoft.graph.downloadUrl') + full_item_path = f"{item_path}/{item_name}".strip('/') + download_url = item.get('@microsoft.graph.downloadUrl') if not download_url: stats["failed"] += 1 - report.append({"Path": f"{item_path}/{item_name}", "Error": "No URL", "Timestamp": datetime.now().isoformat()}) + report.append({"Path": full_item_path, "Error": "No URL", "Timestamp": datetime.now().isoformat()}) continue - # Update status for the file we are about to check/download - print_status(f"{item_path}/{item_name}") + # Update status showing we are checking this file + print_status(full_item_path, action="Checking") + + success, status = download_file(download_url, local_path, item['size'], full_item_path) - success, status = download_file(download_url, local_path, item['size']) if success: if status == "Downloaded": stats["downloaded"] += 1 - # Force a newline for actual downloads so they stay in history - sys.stdout.write(f"\nDownloaded: {item_path}/{item_name}\n") + # Clear line and print permanent log for actual download + sys.stdout.write(f"\r{' ' * 120}\r") + print(f"Downloaded: {full_item_path}") else: stats["skipped"] += 1 else: stats["failed"] += 1 - sys.stdout.write(f"\nFAILED: {item_path}/{item_name} - {status}\n") - report.append({"Path": f"{item_path}/{item_name}", "Error": status, "Timestamp": datetime.now().isoformat()}) + sys.stdout.write(f"\r{' ' * 120}\r") + print(f"FAILED: {full_item_path} - {status}") + report.append({"Path": full_item_path, "Error": status, "Timestamp": datetime.now().isoformat()}) + + # Refresh status line after file is handled + print_status(full_item_path, action="Done") except Exception as e: err_msg = f"Folder error: {str(e)}" - sys.stdout.write(f"\nFAILED FOLDER: {item_path} - {err_msg}\n") + sys.stdout.write(f"\r{' ' * 120}\r") + print(f"FAILED FOLDER: {item_path} - {err_msg}") report.append({"Path": item_path, "Error": err_msg, "Timestamp": datetime.now().isoformat()}) def main(): @@ -228,7 +240,8 @@ def main(): print(f"\nCRITICAL ERROR: {e}") report.append({"Path": "GENERAL", "Error": str(e), "Timestamp": datetime.now().isoformat()}) - print_status("Done!") + # Clear the progress line before final summary + sys.stdout.write(f"\r{' ' * 120}\r") report_file = f"download_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" with open(report_file, 'w', newline='', encoding='utf-8') as f: @@ -236,7 +249,7 @@ def main(): writer.writeheader() writer.writerows(report) - print(f"\n\nProcess complete.") + print(f"\nProcess complete.") print(f"Summary: {stats['total_checked']} items checked.") print(f" - {stats['downloaded']} new files downloaded.") print(f" - {stats['skipped']} existing files skipped.")