-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
+mailbox.idle, +BaseMailBox.consume_until_tagged_response, +MailboxTa…
…ggedResponseError, -BaseMailBox.with_headers_only_allowed_errors
- Loading branch information
Showing
11 changed files
with
260 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,13 @@ My PayPal 💰 | |
| https://paypal.me/KaukinVK | ||
| [email protected] | ||
Star imap_tools project ⭐ | ||
https://github.com/ikvk/imap_tools | ||
|
||
Thanks to all who donated 🎉 | ||
It is really nice. | ||
|
||
Targeted fundraising 🎯 | ||
| 3k$ for create documentation. Style: https://alabaster.readthedocs.io/en/latest/ | ||
| Considering the dynamics - in ~100 years :D | ||
| So you'd better buy strings for my balalaika and meat for my bear. | ||
Do not forget to star imap_tools project ⭐ | ||
https://github.com/ikvk/imap_tools |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import time | ||
from imap_tools import MailBox, A | ||
|
||
# SIMPLE | ||
# waiting for msg in 60 sec, then print unseen if any update | ||
with MailBox('imap.far.mars').login('acc', 'pwd') as mailbox: | ||
# in idle mode | ||
mailbox.idle.start() | ||
responses = mailbox.idle.poll(timeout=60) | ||
mailbox.idle.stop() | ||
# in not idle mode | ||
if responses: | ||
for msg in mailbox.fetch(A(seen=False)): | ||
print(msg.date, msg.subject) | ||
else: | ||
print('no any updates') | ||
|
||
# reliable console notificator | ||
import time, socket, imaplib, traceback | ||
from imap_tools import A, MailBox, MailboxLoginError, MailboxLogoutError | ||
|
||
done = False | ||
while not done: | ||
connection_start_time = time.monotonic() | ||
connection_live_time = 0.0 | ||
try: | ||
with MailBox('imap.my.moon').login('acc', 'pwd', 'INBOX') as mailbox: | ||
print('@@ new connection', time.asctime()) | ||
while connection_live_time < 29 * 60: | ||
try: | ||
responses = mailbox.idle.wait(timeout=3 * 60) | ||
print(time.asctime(), 'IDLE responses:', responses) | ||
if responses: | ||
for msg in mailbox.fetch(A(seen=False)): | ||
print('->', msg.date, msg.subject) | ||
except KeyboardInterrupt: | ||
print('~KeyboardInterrupt') | ||
done = True | ||
break | ||
connection_live_time = time.monotonic() - connection_start_time | ||
except (TimeoutError, ConnectionError, | ||
imaplib.IMAP4.abort, MailboxLoginError, MailboxLogoutError, | ||
socket.herror, socket.gaierror, socket.timeout) as e: | ||
print(f'## Error\n{e}\n{traceback.format_exc()}\nreconnect in a minute...') | ||
time.sleep(60) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,4 @@ | |
from .utils import EmailAddress | ||
from .errors import * | ||
|
||
__version__ = '0.50.2' | ||
__version__ = '0.51.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import sys | ||
import socket | ||
import select | ||
import imaplib | ||
from typing import Optional, List | ||
|
||
from .utils import check_command_status | ||
from .errors import MailboxTaggedResponseError | ||
|
||
imaplib.Commands.setdefault("IDLE", ("NONAUTH", "AUTH", "SELECTED")) # noqa | ||
|
||
SUPPORTS_SELECT_POLL = hasattr(select, 'poll') | ||
|
||
|
||
def get_socket_poller(sock: socket.socket, timeout: Optional[int] = None): | ||
""" | ||
Polls the socket for events telling us it's available to read | ||
:param sock: socket.socket | ||
:param timeout: seconds or None - seconds | ||
:return: polling object | ||
""" | ||
if SUPPORTS_SELECT_POLL: | ||
# select.poll allows your process to have more than 1024 file descriptors | ||
poller = select.poll() | ||
poller.register(sock.fileno(), select.POLLIN) | ||
timeout = None if timeout is None else timeout * 1000 | ||
return poller.poll(timeout) | ||
else: | ||
# select.select fails if your process has more than 1024 file descriptors, needs for windows and some other | ||
return select.select([sock], [], [], timeout)[0] | ||
|
||
|
||
class IdleManager: | ||
""" | ||
Mailbox IDLE logic | ||
Info about IMAP4 IDLE command at rfc2177 | ||
Workflow examples: | ||
1. | ||
mailbox.idle.start() | ||
resps = mailbox.idle.poll(timeout=60) | ||
mailbox.idle.stop() | ||
2. | ||
resps = mailbox.idle.wait(timeout=60) | ||
""" | ||
|
||
def __init__(self, mailbox): | ||
self.mailbox = mailbox | ||
self._idle_tag = None | ||
|
||
def start(self): | ||
"""Switch on mailbox IDLE mode""" | ||
self._idle_tag = self.mailbox.box._command('IDLE') # b'KLIG3' | ||
result = self.mailbox.box._get_response() | ||
check_command_status((result, 'IDLE start'), MailboxTaggedResponseError, expected=None) | ||
return self # return self in favor of context manager, expected result is None => not save it | ||
|
||
def stop(self): | ||
"""Switch off mailbox IDLE mode""" | ||
self.mailbox.box.send(b"DONE\r\n") | ||
return self.mailbox.consume_until_tagged_response(self._idle_tag) | ||
|
||
def poll(self, timeout: Optional[float]) -> List[bytes]: | ||
""" | ||
Poll for IDLE responses | ||
timeout = None | ||
Blocks until an IDLE response is received | ||
timeout = float | ||
Blocks until IDLE response is received or the timeout will expire | ||
:param timeout: seconds or None | ||
:return: list of raw responses | ||
result examples: | ||
[b'* 36 EXISTS', b'* 1 RECENT'] | ||
[b'* 7 EXISTS'] | ||
""" | ||
if timeout is not None: | ||
timeout = float(timeout) | ||
if timeout > 29 * 60: | ||
raise ValueError( | ||
'rfc2177 are advised to terminate the IDLE ' | ||
'and re-issue it at least every 29 minutes to avoid being logged off.' | ||
) | ||
sock = self.mailbox.box.sock | ||
old_timeout = sock.gettimeout() | ||
# make socket non-blocking so the timeout can be implemented for this call | ||
sock.settimeout(None) | ||
sock.setblocking(0) | ||
try: | ||
response_set = [] | ||
events = get_socket_poller(sock, timeout) | ||
if events: | ||
while True: | ||
try: | ||
line = self.mailbox.box._get_line() | ||
except (socket.timeout, socket.error): | ||
break | ||
except imaplib.IMAP4.abort: # noqa | ||
import traceback | ||
etype, evalue, etraceback = sys.exc_info() | ||
if "EOF" in evalue.args[0]: | ||
break | ||
else: | ||
raise | ||
else: | ||
response_set.append(line) | ||
return response_set | ||
finally: | ||
sock.setblocking(1) | ||
if old_timeout is not None: | ||
sock.settimeout(old_timeout) | ||
|
||
def wait(self, timeout: Optional[float]) -> List[bytes]: | ||
""" | ||
Logic, step by step: | ||
1. Start idle mode | ||
2. Poll idle response | ||
3. Stop idle mode | ||
4. Return poll results | ||
:param timeout: for poll method | ||
:return: poll response | ||
""" | ||
with self.start() as idle: | ||
return idle.poll(timeout=timeout) | ||
|
||
def __enter__(self): | ||
return self | ||
|
||
def __exit__(self, exc_type, exc_value, exc_traceback): | ||
self.stop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import unittest | ||
|
||
from tests.utils import MailboxTestCase | ||
|
||
|
||
class IdleTest(MailboxTestCase): | ||
|
||
def test_idle(self): | ||
for mailbox_name, mailbox in self.mailbox_set.items(): | ||
if mailbox.mailbox_name in ('MAIL_RU', 'YAHOO'): | ||
continue | ||
mailbox.idle.wait(timeout=1) | ||
self.assertEqual(len(tuple(mailbox.fetch(limit=1))), 1) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |