Пример #1
0
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,
                }
Пример #2
0
    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
Пример #3
0
    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
Пример #4
0
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