55import asyncio
66from dataclasses import dataclass , field
77import inspect
8+ import math
89import time
910import traceback
1011from typing import Any , Callable , Dict , List , Optional , Tuple
@@ -33,6 +34,86 @@ def summarize_error_message(message: object, *, max_length: int = 180) -> str:
3334 has_hidden_detail = clipped or len (text .splitlines ()) > 1
3435 return f"{ first_line } 。详情见日志" if has_hidden_detail else first_line
3536
37+
38+ class AsyncTaskProgressReporter :
39+ def __init__ (self , emit : Callable [[str ], None ], * , throttle_seconds : float = 0.2 , min_percent_step : int = 1 ):
40+ self ._emit = emit
41+ self ._throttle_seconds = throttle_seconds
42+ self ._min_percent_step = min_percent_step
43+ self ._label = ""
44+ self ._bytes_downloaded = 0
45+ self ._total_bytes : Optional [int ] = None
46+ self ._last_emit_at = 0.0
47+ self ._last_percent = - 1
48+
49+ def __call__ (self , message : str ):
50+ self ._emit (message )
51+
52+ def download_reset (self , * , label : Optional [str ] = None ):
53+ self ._label = label or self ._label
54+ self ._bytes_downloaded = 0
55+ self ._total_bytes = None
56+ self ._last_emit_at = 0.0
57+ self ._last_percent = - 1
58+
59+ def download_start (self , * , label : str , total_bytes : Optional [int ] = None ):
60+ self .download_reset (label = label )
61+ self ._total_bytes = total_bytes if total_bytes is not None and total_bytes > 0 else None
62+ if self ._total_bytes is None :
63+ self ._emit (f"{ self ._label } 0B" )
64+ return
65+ self ._emit (f"{ self ._label } 0% { self ._format_ratio (0 , self ._total_bytes )} " )
66+
67+ def download_advance (self , chunk_size : int , * , label : Optional [str ] = None , total_bytes : Optional [int ] = None ):
68+ if chunk_size < 0 :
69+ raise ValueError ("chunk_size must be >= 0" )
70+ if label :
71+ self ._label = label
72+ if total_bytes is not None :
73+ self ._total_bytes = total_bytes if total_bytes > 0 else None
74+ self ._bytes_downloaded += chunk_size
75+ now = time .monotonic ()
76+ if self ._total_bytes is None :
77+ if now - self ._last_emit_at < self ._throttle_seconds :
78+ return
79+ self ._last_emit_at = now
80+ self ._emit (f"{ self ._label } { self ._format_bytes (self ._bytes_downloaded )} " )
81+ return
82+
83+ percent = min (100 , math .floor ((self ._bytes_downloaded * 100 ) / self ._total_bytes ))
84+ should_emit = percent >= self ._last_percent + self ._min_percent_step or now - self ._last_emit_at >= self ._throttle_seconds
85+ if not should_emit :
86+ return
87+ self ._last_emit_at = now
88+ self ._last_percent = percent
89+ self ._emit (f"{ self ._label } { percent } % { self ._format_ratio (self ._bytes_downloaded , self ._total_bytes )} " )
90+
91+ def download_finish (self , * , label : Optional [str ] = None ):
92+ if label :
93+ self ._label = label
94+ if self ._total_bytes is None :
95+ self ._emit (f"{ self ._label } done { self ._format_bytes (self ._bytes_downloaded )} " )
96+ return
97+ done_bytes = max (self ._bytes_downloaded , self ._total_bytes )
98+ self ._emit (f"{ self ._label } done { self ._format_bytes (done_bytes )} " )
99+
100+ @staticmethod
101+ def _format_bytes (size_bytes : int ) -> str :
102+ if size_bytes < 1024 :
103+ return f"{ size_bytes } B"
104+ if size_bytes < 1024 * 1024 :
105+ return f"{ size_bytes / 1024 :.1f} K"
106+ return f"{ size_bytes / 1024 / 1024 :.1f} M"
107+
108+ @classmethod
109+ def _format_ratio (cls , downloaded_bytes : int , total_bytes : int ) -> str :
110+ if total_bytes < 1024 :
111+ return f"{ downloaded_bytes } /{ total_bytes } B"
112+ if total_bytes < 1024 * 1024 :
113+ return f"{ downloaded_bytes / 1024 :.1f} /{ total_bytes / 1024 :.1f} K"
114+ return f"{ downloaded_bytes / 1024 / 1024 :.1f} /{ total_bytes / 1024 / 1024 :.1f} M"
115+
116+
36117class AsyncTaskThread (QThread ):
37118 success_signal = Signal (object )
38119 error_signal = Signal (str )
@@ -52,7 +133,7 @@ def run(self):
52133 return
53134 code_obj = getattr (self .task_func , "__code__" , None )
54135 if code_obj is not None and "progress_callback" in code_obj .co_varnames :
55- self .kwargs ["progress_callback" ] = self .emit_progress
136+ self .kwargs ["progress_callback" ] = AsyncTaskProgressReporter ( self .emit_progress )
56137 result = self .task_func (* self .args , ** self .kwargs )
57138 if inspect .isawaitable (result ):
58139 result = asyncio .run (result )
@@ -201,13 +282,8 @@ def _show(self, factory: Callable[..., Optional[InfoBar]], title: str, content:
201282 if self ._gui is None :
202283 return None
203284 infobar = factory (
204- title = title ,
205- content = content ,
206- orient = Qt .Horizontal ,
207- isClosable = True ,
208- position = InfoBarPosition .TOP ,
209- duration = duration ,
210- parent = self ._gui ,
285+ title = title , content = content , orient = Qt .Horizontal , isClosable = True , position = InfoBarPosition .TOP ,
286+ duration = duration , parent = self ._gui ,
211287 )
212288 if infobar is None :
213289 return None
@@ -263,11 +339,7 @@ def execute_task(self, task_id: str, config: TaskConfig) -> bool:
263339 )
264340 if config .show_tooltip :
265341 self ._tooltip_stack .show (
266- task_id ,
267- config .tooltip_title ,
268- config .tooltip_content ,
269- config .tooltip_position ,
270- config .tooltip_parent ,
342+ task_id , config .tooltip_title , config .tooltip_content , config .tooltip_position , config .tooltip_parent
271343 )
272344 thread .start ()
273345 return True
@@ -301,21 +373,11 @@ def execute_simple_task(
301373 return self .execute_task (
302374 task_id ,
303375 TaskConfig (
304- task_func = task_func ,
305- success_callback = success_callback ,
306- error_callback = error_callback ,
307- progress_callback = progress_callback ,
308- tooltip_title = tooltip_title ,
309- tooltip_content = tooltip_content ,
310- show_success_info = show_success_info ,
311- show_error_info = show_error_info ,
312- success_message = success_message ,
313- auto_hide_tooltip = auto_hide_tooltip ,
314- show_tooltip = show_tooltip ,
315- tooltip_position = tooltip_position ,
316- tooltip_parent = tooltip_parent ,
317- args = args ,
318- kwargs = kwargs ,
376+ task_func = task_func , success_callback = success_callback , error_callback = error_callback ,
377+ progress_callback = progress_callback , tooltip_title = tooltip_title , tooltip_content = tooltip_content ,
378+ show_success_info = show_success_info , show_error_info = show_error_info , success_message = success_message ,
379+ auto_hide_tooltip = auto_hide_tooltip , show_tooltip = show_tooltip , tooltip_position = tooltip_position ,
380+ tooltip_parent = tooltip_parent , args = args , kwargs = kwargs ,
319381 ),
320382 )
321383
0 commit comments