diff --git a/imapidle.py b/imapidle.py new file mode 100644 index 0000000..baa7e96 --- /dev/null +++ b/imapidle.py @@ -0,0 +1,43 @@ +from threading import Thread +from time import sleep, time + + +class IMAPIdle(Thread): + def __init__(self, client, interval=1680): + self.client = client + self.interval = interval + self._idle = False + self._exit = False + super(IMAPIdle, self).__init__() + + def run(self): + while self._exit is False: + if self._idle and (time()-self._idle) > self.interval: + self.restart_idle() + sleep(0.1) + + def exit(self): + self._exit = True + + def start_idle(self): + """ + start imap idle to hold the connection alive + """ + if self._idle is False: + #: must select a folder before invoking idle. we simply select the first folder to idle on + _, _, some_folder = self.client.list_folders()[0] + self.client.select_folder(some_folder, readonly=True) + self.client.idle() + self._idle = time() + + def stop_idle(self): + """ + stop idle mode to allow normal commands + """ + if self._idle: + self.client.idle_done() + self._idle = False + + def restart_idle(self): + self.stop_idle() + self.start_idle() diff --git a/pymap-copy.py b/pymap-copy.py index 8cd171f..2426282 100755 --- a/pymap-copy.py +++ b/pymap-copy.py @@ -4,6 +4,7 @@ from imapclient import IMAPClient, exceptions +from imapidle import IMAPIdle from utils import decode_mime, beautysized, imaperror_decode @@ -95,6 +96,8 @@ def login(client, user, password): parser.add_argument('--denied-flags', help='mails with this flags will be skipped', type=str) parser.add_argument('-r', '--redirect', help='redirect a folder (source:destination --denied-flags seen,recent -d)', action='append') +parser.add_argument('--idle-interval', help='time in defines the interval (in seconds) in which the idle process is ' + 'restarted (default: 1680)', type=int, default=1680) parser.add_argument('--ignore-quota', help='ignores insufficient quota', action='store_true') parser.add_argument('--ignore-folder-flags', help='do not link default IMAP folders automatically (like Drafts, ' 'Trash, etc.)', action='store_true') @@ -212,7 +215,15 @@ def login(client, user, password): print('\nAbort! Please fix the errors above.') exit() +print() +#: starting idle threads +print('Starting idle threads : ', end='', flush=True) +source_idle = IMAPIdle(source, interval=args.idle_interval) +destination_idle = IMAPIdle(destination, interval=args.idle_interval) +source_idle.start() +destination_idle.start() +print('{} (restarts every {} seconds)'.format(colorize('OK', color='green'), args.idle_interval)) print() #: get quota from source @@ -257,10 +268,7 @@ def login(client, user, password): print() - - - -start_imap_idle(destination) +destination_idle.start_idle() #: get source folders print(colorize('Getting source folders : loading (this can take a while)', clear=True), flush=True, end='') @@ -320,11 +328,6 @@ def login(client, user, password): print(colorize('Getting source folders : Progressing ({} mails): {}'. format(stats['source_mails'], name), clear=True), flush=True, end='') - # adding a check to refresh our imap idle session on the destination imap connection so we do not - # get logged out. This is possibly too frequently, but just taking a guess here. - if stats['source_mails'] % 10000 == 0: - restart_imap_idle(destination) - del mails[:args.buffer_size] print(colorize('Getting source folders : {} mails in {} folders ({})'. @@ -332,8 +335,8 @@ def login(client, user, password): beautysized(sum([f['size'] for f in db['source']['folders'].values()]))), clear=True)) -end_imap_idle(destination) -start_imap_idle(source) +destination_idle.stop_idle() +source_idle.start_idle() #: get destination folders @@ -395,6 +398,10 @@ def login(client, user, password): color='cyan'))) else: print('\n{}'.format(colorize('Everything skipped! (list mode)', color='cyan'))) + + #: stop idle threads & exit + source_idle.exit() + destination_idle.exit() exit() @@ -427,7 +434,7 @@ def login(client, user, password): print('\n{} Source folder not found: {}\n'.format(colorize('Error:', color='red', bold=True), ', '.join(not_found))) exit() -end_imap_idle(source) +source_idle.stop_idle() try: for sf_name in sorted(db['source']['folders'], key=lambda x: x.lower()): @@ -613,6 +620,11 @@ def login(client, user, password): print() print('Finish!\n') +#: stop idle threads +source_idle.exit() +destination_idle.exit() + +#: logout source try: print('Logout source...', end='', flush=True) source.logout()