From d26dd5f6b3b96ec65e870c915a8368b77805a67e Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sun, 4 May 2025 13:26:29 -0500 Subject: [PATCH] Add better display in the m3u table for the user to see what we are doing. --- apps/m3u/tasks.py | 43 +++++- frontend/src/components/tables/M3UsTable.jsx | 148 +++++++++++++++++-- 2 files changed, 175 insertions(+), 16 deletions(-) diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index 56528bb5..2b7cd0ec 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -530,6 +530,7 @@ def refresh_single_m3u_account(account_id): total_batches = len(batches) completed_batches = 0 + streams_processed = 0 # Track total streams processed logger.debug(f"Dispatched {len(batches)} parallel tasks for account_id={account_id}.") # result = task_group.apply_async() @@ -542,18 +543,47 @@ def refresh_single_m3u_account(account_id): if async_result.ready() and async_result.id not in completed_task_ids: # If the task has completed and we haven't counted it task_result = async_result.result # The result of the task logger.debug(f"Task completed with result: {task_result}") + + # Extract stream counts from result string if available + if isinstance(task_result, str): + try: + created_match = re.search(r"(\d+) created", task_result) + updated_match = re.search(r"(\d+) updated", task_result) + + if created_match and updated_match: + created_count = int(created_match.group(1)) + updated_count = int(updated_match.group(1)) + streams_processed += created_count + updated_count + except (AttributeError, ValueError): + pass + completed_batches += 1 completed_task_ids.add(async_result.id) # Mark this task as processed # Calculate progress progress = int((completed_batches / total_batches) * 100) + # Calculate elapsed time and estimated remaining time + current_elapsed = time.time() - start_time + if progress > 0: + estimated_total = (current_elapsed / progress) * 100 + time_remaining = max(0, estimated_total - current_elapsed) + else: + time_remaining = 0 + # Send progress update via Channels # Don't send 100% because we want to clean up after if progress == 100: progress = 99 - send_m3u_update(account_id, "parsing", progress) + send_m3u_update( + account_id, + "parsing", + progress, + elapsed_time=current_elapsed, + time_remaining=time_remaining, + streams_processed=streams_processed + ) # Optionally remove completed task from the group to prevent processing it again result.remove(async_result) @@ -567,7 +597,16 @@ def refresh_single_m3u_account(account_id): # Now run cleanup cleanup_streams(account_id) - send_m3u_update(account_id, "parsing", 100) + # Send final update with complete metrics + elapsed_time = time.time() - start_time + send_m3u_update( + account_id, + "parsing", + 100, + elapsed_time=elapsed_time, + time_remaining=0, + streams_processed=streams_processed + ) end_time = time.time() diff --git a/frontend/src/components/tables/M3UsTable.jsx b/frontend/src/components/tables/M3UsTable.jsx index b114b0fe..6e018b95 100644 --- a/frontend/src/components/tables/M3UsTable.jsx +++ b/frontend/src/components/tables/M3UsTable.jsx @@ -43,17 +43,18 @@ const M3UTable = () => { return buildDownloadingStats(data); case 'processing_groups': - return 'Processing groups...'; + return buildGroupProcessingStats(data); + + case 'parsing': + return buildParsingStats(data); default: - return buildParsingStats(data); + return data.status === 'error' ? buildErrorStats(data) : `${data.action || 'Processing'}...`; } }; const buildDownloadingStats = (data) => { if (data.progress == 100) { - // fetchChannelGroups(); - // fetchPlaylists(); return 'Download complete!'; } @@ -61,21 +62,89 @@ const M3UTable = () => { return 'Downloading...'; } + // Format time remaining in minutes:seconds + const timeRemaining = data.time_remaining ? + `${Math.floor(data.time_remaining / 60)}:${String(Math.floor(data.time_remaining % 60)).padStart(2, '0')}` : + 'calculating...'; + + // Format speed with appropriate unit (KB/s or MB/s) + const speed = data.speed >= 1024 ? + `${(data.speed / 1024).toFixed(2)} MB/s` : + `${Math.round(data.speed)} KB/s`; + return ( - Downloading: {parseInt(data.progress)}% - {/* Speed: {parseInt(data.speed)} KB/s - Time Remaining: {parseInt(data.time_remaining)} */} + + + Downloading: + {parseInt(data.progress)}% + + + Speed: + {speed} + + + Time left: + {timeRemaining} + + + + ); + }; + + const buildGroupProcessingStats = (data) => { + if (data.progress == 100) { + return 'Groups processed!'; + } + + if (data.progress == 0) { + return 'Processing groups...'; + } + + // Format time displays if available + const elapsedTime = data.elapsed_time ? + `${Math.floor(data.elapsed_time / 60)}:${String(Math.floor(data.elapsed_time % 60)).padStart(2, '0')}` : + null; + + return ( + + + + Processing groups: + {parseInt(data.progress)}% + + {elapsedTime && ( + + Elapsed: + {elapsedTime} + + )} + {data.groups_processed && ( + + Groups: + {data.groups_processed} + + )} + + + ); + }; + + const buildErrorStats = (data) => { + return ( + + + + Error: + + {data.error || "Unknown error occurred"} + ); }; const buildParsingStats = (data) => { if (data.progress == 100) { - // fetchStreams(); - // fetchChannelGroups(); - // fetchEPGData(); - // fetchPlaylists(); return 'Parsing complete!'; } @@ -83,7 +152,43 @@ const M3UTable = () => { return 'Parsing...'; } - return `Parsing: ${data.progress}%`; + // Format time displays + const timeRemaining = data.time_remaining ? + `${Math.floor(data.time_remaining / 60)}:${String(Math.floor(data.time_remaining % 60)).padStart(2, '0')}` : + 'calculating...'; + + const elapsedTime = data.elapsed_time ? + `${Math.floor(data.elapsed_time / 60)}:${String(Math.floor(data.elapsed_time % 60)).padStart(2, '0')}` : + '0:00'; + + return ( + + + + Parsing: + {parseInt(data.progress)}% + + {data.elapsed_time && ( + + Elapsed: + {elapsedTime} + + )} + {data.time_remaining && ( + + Remaining: + {timeRemaining} + + )} + {data.streams_processed && ( + + Streams: + {data.streams_processed} + + )} + + + ); }; const toggleActive = async (playlist) => { @@ -143,8 +248,23 @@ const M3UTable = () => { return generateStatusString(refreshProgress[row.id]); }, - size: 150, - minSize: 80, + size: 180, + minSize: 150, + Cell: ({ cell }) => { + const value = cell.getValue(); + + // Return a visual component for the status + if (typeof value === 'object') { + return value; + } + + // For simple string statuses + return ( + + {value} + + ); + }, }, { header: 'Active',