def tickle_iterator( path ): thetime = datetime.now() for box, subdirs, _ in walk( path, topdown=True ): # remove maildir meta directories from top-level subdirs.remove('cur') subdirs.remove('tmp') subdirs.remove('new') src_mbox = Maildir(box, factory=None) src_mbox.lock() for key in src_mbox.iterkeys(): path = '/'.join([box, src_mbox._toc[key]]) try: tickle_time = None start_time = thetime if src_mbox[key]['X-Tickler']: # already tickling (probably past due). get tickler time from tickler header. try: tickle_time = parse_time( src_mbox[key]['X-Tickler'] ) except Exception, e: print e else: # compute tickle time from mailbox name start_time = datetime.fromtimestamp(stat(path).st_ctime) tickle_time = parse_time(basename( box ).replace('-', ' '), start_time=start_time) due_in = thetime - tickle_time yield { 'src' : src_mbox, 'key' : key, 'boxname' : basename( box ), 'path' : path, 'due_in' : format_timedelta( due_in ), 'due' : due_in > timedelta(seconds=1), 'start_time' : start_time, 'tickle_time' : tickle_time, } except DateTimeParseException: # support for free-form todo box names yield { 'src' : src_mbox, 'key' : key, 'boxname' : basename( box ), 'path' : path, 'due_in' : None, 'start_time' : None, 'tickle_time' : None, 'due' : False, }
def add_maildir(self, maildir_path): """ Load up a maildir and compute hash for each mail found. """ maildir_path = self.canonical_path(maildir_path) logger.info("Opening maildir at {} ...".format(maildir_path)) # Maildir parser requires a string, not a unicode, as path. maildir = Maildir(str(maildir_path), factory=None, create=False) # Group folders by hash. logger.info("{} mails found.".format(len(maildir))) if self.conf.progress: bar = ProgressBar(widgets=[Percentage(), Bar()], max_value=len(maildir), redirect_stderr=True, redirect_stdout=True) else: def bar(x): return x for mail_id in bar(maildir.iterkeys()): self.stats['mail_found'] += 1 mail_path = self.canonical_path( os.path.join(maildir._path, maildir._lookup(mail_id))) mail = Mail(mail_path, self.conf) try: mail_hash = mail.hash_key except (InsufficientHeadersError, MissingMessageID) as expt: logger.warning("Rejecting {}: {}".format( mail_path, expt.args[0])) self.stats['mail_rejected'] += 1 else: logger.debug("Hash is {} for mail {!r}.".format( mail_hash, mail_id)) # Use a set to deduplicate entries pointing to the same file. self.mails.setdefault(mail_hash, set()).add(mail_path) self.stats['mail_kept'] += 1
def add_maildir(self, maildir_path): """ Load up a maildir and compute hash for each mail found. """ maildir_path = self.canonical_path(maildir_path) logger.info("Opening maildir at {} ...".format(maildir_path)) # Maildir parser requires a string, not a unicode, as path. maildir = Maildir(str(maildir_path), factory=None, create=False) # Group folders by hash. logger.info("{} mails found.".format(len(maildir))) if self.conf.progress: bar = ProgressBar(widgets=[Percentage(), Bar()], max_value=len(maildir), redirect_stderr=True, redirect_stdout=True) else: def bar(x): return x for mail_id in bar(maildir.iterkeys()): self.stats['mail_found'] += 1 mail_path = self.canonical_path(os.path.join( maildir._path, maildir._lookup(mail_id))) mail = Mail(mail_path, self.conf) try: mail_hash = mail.hash_key except (InsufficientHeadersError, MissingMessageID) as expt: logger.warning( "Rejecting {}: {}".format(mail_path, expt.args[0])) self.stats['mail_rejected'] += 1 else: logger.debug( "Hash is {} for mail {!r}.".format(mail_hash, mail_id)) # Use a set to deduplicate entries pointing to the same file. self.mails.setdefault(mail_hash, set()).add(mail_path) self.stats['mail_kept'] += 1
class BitMessageMailManService(GService): def __init__(self, mailDirPath: Path, mailBoxesPath: Path): super().__init__() self.clearMailDirPath = mailDirPath self.clearMailDirPath.mkdir(parents=True, exist_ok=True) self.clearMailDir = Maildir(str(self.clearMailDirPath)) self.mailBoxesPath = mailBoxesPath self.mailBoxes = {} # Signals self.sNewMessage = AsyncSignal(str) async def loadMailBoxes(self): for root, dirs, files in os.walk(str(self.mailBoxesPath)): for dir in dirs: bma = dir if not bmAddressValid(bma): continue fp = Path(root).joinpath(bma) await self._cMailBox(bma, fp) log.debug(f'Loaded mailbox {dir}') def mailBoxExists(self, bmAddr): return bmAddr in self.mailBoxes def mailBoxGet(self, bmAddr): return self.mailBoxes.get(bmAddr) async def on_start(self) -> None: await super().on_start() log.debug('Starting BM mailman ..') @GService.task async def watchMailDir(self): while True: await asyncio.sleep(cParentGet('mdirWatcher.sleepInterval')) log.debug(f'NotBit maildir ({self.clearMailDirPath}): reading') for key in self.clearMailDir.iterkeys(): try: message = self.clearMailDir[key] recipient = message['To'] bmAddr, domain = recipient.split('@') assert domain == 'bitmessage' assert bmAddressValid(bmAddr) except email.errors.MessageParseError as err: log.debug(f'Error parsing message {key}: {err}') continue except Exception as err: log.debug(f'Unknown error parsing message {key}: {err}') continue else: # Check if we have a mailbox for this recipient mbox = self.mailBoxGet(bmAddr) if not mbox: log.debug(f'No mailbox for {recipient}') await asyncio.sleep(0.05) continue log.debug(f'Found mailbox for {recipient}') if await mbox.store(message): # Delete the message from notbit's maildir log.debug('Transferred message to destination maildir') self.clearMailDir.remove(key) else: log.debug('Error transferring message to maildir') await asyncio.sleep(0.1) async def send(self, bmSource: str, bmDest: str, subject: str, message: str, mailDir=None, contentType='text/plain', textMarkup='markdown', encoding='utf-8'): """ Send a BitMessage via notbit-sendmail We use text/plain as the default content-type, with markup field set to 'markdown' """ msg = EmailMessage() msg['From'] = f'{bmSource}@bitmessage' msg['To'] = f'{bmDest}@bitmessage' msg['Subject'] = subject msg['Content-Type'] = \ f'{contentType}; charset={encoding.upper()}; markup={textMarkup}' msg.set_content(message) msgBytes = msg.as_bytes() retCode, result = await shellExec('notbit-sendmail', input=msgBytes, returnStdout=False) log.debug(f'BM send: {bmSource} => {bmDest}: ' f'exit code: {retCode}, output: {result}') if retCode == 0: log.debug(f'BM send: {bmSource} => {bmDest}: OK') if mailDir: await mailDir.storeSent(message) else: log.debug(f'BM send: {bmSource} => {bmDest}: failed') return retCode == 0 async def on_stop(self) -> None: log.debug('Stopping BM mailer ..') async def createKey(self): """ Create a BitMessage key via notbit-keygen """ code, data = await shellExec('notbit-keygen') if data: key = data.strip() log.debug(f'Bitmessage key generate result: {key}') if bmAddressValid(key): log.debug(f'Generate bitmessage key {key}') return key else: log.debug(f'Invalid key: {key}') else: log.debug('Bitmessage key generatation: exec failed') async def _cMailBox(self, bmKey, path: Path): if bmKey in self.mailBoxes: return self.mailBoxGet(bmKey) maildir = RegularMailDir(bmKey, path) await maildir.setup() self.mailBoxes[bmKey] = maildir return maildir async def createMailBox(self): key = await self.createKey() if not key: # raise Exception('Could not generate BM key') log.debug('Could not generate BM key') return None, None fp = self.mailBoxesPath.joinpath(key) maildir = await self._cMailBox(key, fp) if maildir: return key, maildir async def getMailBox(self, key): fp = self.mailBoxesPath.joinpath(key) maildir = await self._cMailBox(key, fp) if maildir: return key, maildir