7
7
from .message import MailMessage
8
8
from .folder import MailBoxFolderManager
9
9
from .idle import IdleManager
10
- from .consts import UID_PATTERN , PYTHON_VERSION_MINOR
11
- from .utils import clean_uids , check_command_status , chunks , encode_folder , clean_flags , check_timeout_arg_support , \
12
- chunks_crop , StrOrBytes
10
+ from .consts import UID_PATTERN , PYTHON_VERSION_MINOR , MOVE_RESULT_TAG
11
+ from .utils import clean_uids , check_command_status , chunked , encode_folder , clean_flags , check_timeout_arg_support , \
12
+ chunked_crop , StrOrBytes
13
13
from .errors import MailboxStarttlsError , MailboxLoginError , MailboxLogoutError , MailboxNumbersError , \
14
14
MailboxFetchError , MailboxExpungeError , MailboxDeleteError , MailboxCopyError , MailboxFlagError , \
15
- MailboxAppendError , MailboxUidsError , MailboxTaggedResponseError
15
+ MailboxAppendError , MailboxUidsError , MailboxTaggedResponseError , MailboxMoveError
16
16
17
17
# Maximal line length when calling readline(). This is to prevent reading arbitrary length lines.
18
18
# 20Mb is enough for search response with about 2 000 000 message numbers
@@ -153,7 +153,7 @@ def _fetch_in_bulk(self, uid_list: Sequence[str], message_parts: str, reverse: b
153
153
return
154
154
155
155
if isinstance (bulk , int ) and bulk >= 2 :
156
- uid_list_seq = chunks_crop (uid_list , bulk )
156
+ uid_list_seq = chunked_crop (uid_list , bulk )
157
157
elif isinstance (bulk , bool ):
158
158
uid_list_seq = (uid_list ,)
159
159
else :
@@ -164,7 +164,7 @@ def _fetch_in_bulk(self, uid_list: Sequence[str], message_parts: str, reverse: b
164
164
check_command_status (fetch_result , MailboxFetchError )
165
165
if not fetch_result [1 ] or fetch_result [1 ][0 ] is None :
166
166
return
167
- for built_fetch_item in chunks ((reversed if reverse else iter )(fetch_result [1 ]), 2 ):
167
+ for built_fetch_item in chunked ((reversed if reverse else iter )(fetch_result [1 ]), 2 ):
168
168
yield built_fetch_item
169
169
170
170
def fetch (self , criteria : Criteria = 'ALL' , charset : str = 'US-ASCII' , limit : Optional [Union [int , slice ]] = None ,
@@ -204,62 +204,105 @@ def expunge(self) -> tuple:
204
204
check_command_status (result , MailboxExpungeError )
205
205
return result
206
206
207
- def delete (self , uid_list : Union [str , Iterable [str ]]) -> Optional [Tuple [tuple , tuple ]]:
207
+ def delete (self , uid_list : Union [str , Iterable [str ]], chunks : Optional [int ] = None ) \
208
+ -> Optional [List [Tuple [tuple , tuple ]]]:
208
209
"""
209
210
Delete email messages
210
211
Do nothing on empty uid_list
212
+ :param uid_list: UIDs for delete
213
+ :param chunks: Number of UIDs to process at once, to avoid server errors on large set. Proc all at once if None.
211
214
:return: None on empty uid_list, command results otherwise
212
215
"""
213
- uid_str = clean_uids (uid_list )
214
- if not uid_str :
216
+ cleaned_uid_list = clean_uids (uid_list )
217
+ if not cleaned_uid_list :
215
218
return None
216
- store_result = self .client .uid ('STORE' , uid_str , '+FLAGS' , r'(\Deleted)' )
217
- check_command_status (store_result , MailboxDeleteError )
218
- expunge_result = self .expunge ()
219
- return store_result , expunge_result
220
-
221
- def copy (self , uid_list : Union [str , Iterable [str ]], destination_folder : StrOrBytes ) -> Optional [tuple ]:
219
+ results = []
220
+ for cleaned_uid_list_i in chunked_crop (cleaned_uid_list , chunks ):
221
+ store_result = self .client .uid ('STORE' , ',' .join (cleaned_uid_list_i ), '+FLAGS' , r'(\Deleted)' )
222
+ check_command_status (store_result , MailboxDeleteError )
223
+ expunge_result = self .expunge ()
224
+ results .append ((store_result , expunge_result ))
225
+ return results
226
+
227
+ def copy (self , uid_list : Union [str , Iterable [str ]], destination_folder : StrOrBytes , chunks : Optional [int ] = None ) \
228
+ -> Optional [List [tuple ]]:
222
229
"""
223
- Copy email messages into the specified folder
224
- Do nothing on empty uid_list
230
+ Copy email messages into the specified folder.
231
+ Do nothing on empty uid_list.
232
+ :param uid_list: UIDs for copy
233
+ :param destination_folder: Folder for email copies
234
+ :param chunks: Number of UIDs to process at once, to avoid server errors on large set. Proc all at once if None.
225
235
:return: None on empty uid_list, command results otherwise
226
236
"""
227
- uid_str = clean_uids (uid_list )
228
- if not uid_str :
237
+ cleaned_uid_list = clean_uids (uid_list )
238
+ if not cleaned_uid_list :
229
239
return None
230
- copy_result = self .client .uid ('COPY' , uid_str , encode_folder (destination_folder )) # noqa
231
- check_command_status (copy_result , MailboxCopyError )
232
- return copy_result
233
-
234
- def move (self , uid_list : Union [str , Iterable [str ]], destination_folder : StrOrBytes ) -> Optional [Tuple [tuple , tuple ]]:
240
+ results = []
241
+ for cleaned_uid_list_i in chunked_crop (cleaned_uid_list , chunks ):
242
+ copy_result = self .client .uid (
243
+ 'COPY' , ',' .join (cleaned_uid_list_i ), encode_folder (destination_folder )) # noqa
244
+ check_command_status (copy_result , MailboxCopyError )
245
+ results .append (copy_result )
246
+ return results
247
+
248
+ def move (self , uid_list : Union [str , Iterable [str ]], destination_folder : StrOrBytes , chunks : Optional [int ] = None ) \
249
+ -> Optional [List [Tuple [tuple , tuple ]]]:
235
250
"""
236
- Move email messages into the specified folder
237
- Do nothing on empty uid_list
251
+ Move email messages into the specified folder.
252
+ Do nothing on empty uid_list.
253
+ :param uid_list: UIDs for move
254
+ :param destination_folder: Folder for move to
255
+ :param chunks: Number of UIDs to process at once, to avoid server errors on large set. Proc all at once if None.
238
256
:return: None on empty uid_list, command results otherwise
239
257
"""
240
- uid_str = clean_uids (uid_list )
241
- if not uid_str :
258
+ cleaned_uid_list = clean_uids (uid_list )
259
+ if not cleaned_uid_list :
242
260
return None
243
- copy_result = self .copy (uid_str , destination_folder )
244
- delete_result = self .delete (uid_str )
245
- return copy_result , delete_result
246
-
247
- def flag (self , uid_list : Union [str , Iterable [str ]], flag_set : Union [str , Iterable [str ]], value : bool ) \
248
- -> Optional [Tuple [tuple , tuple ]]:
261
+ if 'MOVE' in self .client .capabilities :
262
+ # server side move
263
+ results = []
264
+ for cleaned_uid_list_i in chunked_crop (cleaned_uid_list , chunks ):
265
+ move_result = self .client .uid (
266
+ 'MOVE' , ',' .join (cleaned_uid_list_i ), encode_folder (destination_folder )) # noqa
267
+ check_command_status (move_result , MailboxMoveError )
268
+ results .append ((move_result , MOVE_RESULT_TAG ))
269
+ return results
270
+ else :
271
+ # client side move
272
+ results = []
273
+ for cleaned_uid_list_i in chunked_crop (cleaned_uid_list , chunks ):
274
+ copy_result = self .copy (cleaned_uid_list_i , destination_folder )
275
+ delete_result = self .delete (cleaned_uid_list_i )
276
+ results .append ((copy_result , delete_result ))
277
+ return results
278
+
279
+ def flag (self , uid_list : Union [str , Iterable [str ]], flag_set : Union [str , Iterable [str ]], value : bool ,
280
+ chunks : Optional [int ] = None ) -> Optional [List [Tuple [tuple , tuple ]]]:
249
281
"""
250
- Set/unset email flags
251
- Do nothing on empty uid_list
282
+ Set/unset email flags.
283
+ Do nothing on empty uid_list.
252
284
System flags contains in consts.MailMessageFlags.all
285
+ :param uid_list: UIDs for set flag
286
+ :param flag_set: Flags for operate
287
+ :param value: Should the flags be set: True - yes, False - no
288
+ :param chunks: Number of UIDs to process at once, to avoid server errors on large set. Proc all at once if None.
253
289
:return: None on empty uid_list, command results otherwise
254
290
"""
255
- uid_str = clean_uids (uid_list )
256
- if not uid_str :
291
+ cleaned_uid_list = clean_uids (uid_list )
292
+ if not cleaned_uid_list :
257
293
return None
258
- store_result = self .client .uid (
259
- 'STORE' , uid_str , ('+' if value else '-' ) + 'FLAGS' , f'({ " " .join (clean_flags (flag_set ))} )' )
260
- check_command_status (store_result , MailboxFlagError )
261
- expunge_result = self .expunge ()
262
- return store_result , expunge_result
294
+ results = []
295
+ for cleaned_uid_list_i in chunked_crop (cleaned_uid_list , chunks ):
296
+ store_result = self .client .uid (
297
+ 'STORE' ,
298
+ ',' .join (cleaned_uid_list_i ),
299
+ ('+' if value else '-' ) + 'FLAGS' ,
300
+ f'({ " " .join (clean_flags (flag_set ))} )'
301
+ )
302
+ check_command_status (store_result , MailboxFlagError )
303
+ expunge_result = self .expunge ()
304
+ results .append ((store_result , expunge_result ))
305
+ return results
263
306
264
307
def append (self , message : Union [MailMessage , bytes ],
265
308
folder : StrOrBytes = 'INBOX' ,
0 commit comments