@@ -37,12 +37,14 @@ def __init__(self, uid: str, file: FileMetadata):
3737
3838 @classmethod
3939 async def create (cls , uid : str , file : FileMetadata ):
40+ """Create a new transfer using the provided identifier and file metadata."""
4041 transfer = cls (uid , file )
4142 await transfer .store .set_metadata (file .to_json ())
4243 return transfer
4344
4445 @classmethod
4546 async def get (cls , uid : str ):
47+ """Fetch a transfer from the store using the provided identifier."""
4648 store = Store (uid )
4749 metadata_json = await store .get_metadata ()
4850 if not metadata_json :
@@ -58,122 +60,81 @@ def _format_uid(uid: str):
5860 def get_file_info (self ):
5961 return self .file .name , self .file .size , self .file .type
6062
61- async def wait_for_event (self , event_name : str , timeout : float = 300.0 ):
62- await self .store .wait_for_event (event_name , timeout )
63+ async def notify_receiver_connected (self ):
64+ """Notify sender that receiver connected."""
65+ await self .store .set_event ('receiver_connected' )
6366
64- async def set_client_connected (self ):
65- self .debug (f"▼ Notifying sender that receiver is connected..." )
66- await self .store .set_event ('client_connected' )
67+ async def wait_for_receiver (self ):
68+ """Wait for receiver to connect."""
69+ self .info (f"△ Waiting for receiver..." )
70+ await self .store .wait_for_event ('receiver_connected' )
71+ self .debug (f"△ Receiver connected" )
6772
68- async def wait_for_client_connected (self ):
69- self .info (f"△ Waiting for client to connect..." )
70- await self .wait_for_event ('client_connected' )
71- self .debug (f"△ Received client connected notification." )
72-
73- async def is_receiver_connected (self ) -> bool :
74- return await self .store .is_receiver_connected ()
75-
76- async def set_receiver_connected (self ) -> bool :
77- return await self .store .set_receiver_connected ()
78-
79- async def is_interrupted (self ) -> bool :
80- return await self .store .is_interrupted ()
81-
82- async def set_interrupted (self ):
83- await self .store .set_interrupted ()
84-
85- async def is_completed (self ) -> bool :
86- return await self .store .is_completed ()
87-
88- async def set_completed (self ):
89- await self .store .set_completed ()
90-
91- async def collect_upload (self , stream : AsyncIterator [bytes ], on_error : Callable [[Exception | str ], Awaitable [None ]]) -> None :
73+ async def consume_upload (self , stream : AsyncIterator [bytes ], on_error : Callable [[Exception | str ], Awaitable [None ]]) -> None :
74+ """Consume upload stream and add chunks to Redis stream."""
9275 self .bytes_uploaded = 0
9376
9477 try :
9578 async for chunk in stream :
9679 if not chunk :
97- self .debug (f"△ Empty chunk received, ending upload." )
9880 break
9981
100- if await self .is_interrupted ():
101- raise TransferError ("Transfer was interrupted by the receiver." , propagate = False )
102-
103- await self .store .put_in_queue (chunk )
82+ await self .store .add_chunk (chunk )
10483 self .bytes_uploaded += len (chunk )
10584
10685 if self .bytes_uploaded < self .file .size :
107- raise TransferError ("Received less data than expected. " , propagate = True )
86+ raise TransferError ("Incomplete upload " , propagate = True )
10887
109- self .debug ( f"△ End of upload, sending done marker." )
110- await self .store . put_in_queue ( self .DONE_FLAG )
88+ await self .store . add_chunk ( self . DONE_FLAG )
89+ self .debug ( f"△ Upload complete: { self .bytes_uploaded } bytes" )
11190
112- except (ClientDisconnect , WebSocketDisconnect ) as e :
113- self .error (f"△ Unexpected upload error: { e } " )
114- await self .store .put_in_queue (self .DEAD_FLAG )
91+ except (ClientDisconnect , WebSocketDisconnect ):
92+ self .error (f"△ Sender disconnected " )
93+ await self .store .add_chunk (self .DEAD_FLAG )
11594
116- except TimeoutError as e :
117- self .warning (f"△ Timeout during upload." , exc_info = True )
118- await on_error ("Timeout during upload. " )
95+ except TimeoutError :
96+ self .warning (f"△ Upload timeout" )
97+ await on_error ("Upload timeout " )
11998
12099 except TransferError as e :
121- self .warning (f"△ Upload error: { e } " )
122100 if e .propagate :
123- await self .store .put_in_queue (self .DEAD_FLAG )
124- else :
125- await on_error (e )
101+ await self .store .add_chunk (self .DEAD_FLAG )
102+ await on_error (e )
126103
127- finally :
128- await anyio .sleep (1.0 )
104+ async def produce_download (self , on_error : Callable [[Exception | str ], Awaitable [None ]]) -> AsyncIterator [bytes ]:
105+ """Produce download stream from Redis stream."""
106+ self .bytes_downloaded = await self .store .get_progress ()
129107
130- async def supply_download ( self , on_error : Callable [[ Exception | str ], Awaitable [ None ]]) -> AsyncIterator [ bytes ] :
131- self .bytes_downloaded = 0
108+ if self . bytes_downloaded > 0 :
109+ self .info ( f"▼ Resuming from byte { self . bytes_downloaded } " )
132110
133111 try :
134- while True :
135- chunk = await self .store .get_from_queue ()
136-
112+ async for chunk in self .store .stream_chunks ():
137113 if chunk == self .DEAD_FLAG :
138- raise TransferError ("Sender disconnected." )
139-
140- if chunk == self .DONE_FLAG and self .bytes_downloaded < self .file .size :
141- raise TransferError ("Received less data than expected." )
114+ raise TransferError ("Sender disconnected" )
142115
143- elif chunk == self .DONE_FLAG :
144- self .debug (f"▼ Done marker received, ending download." )
116+ if chunk == self .DONE_FLAG :
117+ if self .bytes_downloaded >= self .file .size :
118+ self .debug (f"▼ Download complete: { self .bytes_downloaded } bytes" )
145119 break
146120
147121 self .bytes_downloaded += len (chunk )
122+ await self .store .save_progress (self .bytes_downloaded )
148123 yield chunk
149124
150- except Exception as e :
151- self .error (f"▼ Unexpected download error!" , exc_info = True )
152- self .debug ("Debug info:" , stack_info = True )
153- await on_error (e )
154-
155125 except TransferError as e :
156- self .warning (f"▼ Download error" )
126+ await on_error (e )
127+ except Exception as e :
128+ self .error (f"▼ Download error" , exc_info = True )
157129 await on_error (e )
158130
159131 async def cleanup (self ):
160- try :
161- with anyio .fail_after (30.0 ):
162- await self .store .cleanup ()
163- except TimeoutError :
164- self .warning (f"- Cleanup timed out." )
165- pass
132+ """Clean up transfer data."""
133+ await self .store .cleanup ()
166134
167135 async def finalize_download (self ):
168- # self.debug("▼ Finalizing download...")
169- if self .bytes_downloaded < self .file .size and not await self .is_interrupted ():
170- self .warning ("▼ Client disconnected before download was complete." )
171- await self .set_interrupted ()
172-
173- await self .cleanup ()
174- # self.debug("▼ Finalizing download...")
175- if self .bytes_downloaded < self .file .size and not await self .is_interrupted ():
176- self .warning ("▼ Client disconnected before download was complete." )
177- await self .set_interrupted ()
178-
179- await self .cleanup ()
136+ """Finalize download and cleanup if complete."""
137+ if self .bytes_downloaded < self .file .size :
138+ self .info (f"▼ Download paused at { self .bytes_downloaded } /{ self .file .size } bytes" )
139+ else :
140+ await self .cleanup ()
0 commit comments